Overview
In a previous post I explained how you could create several components to build a Netflix stack for local development. Now, I want to explain how Pivotal Cloud Foundry makes this much easier. If you do not have a PCF instance to use, you can create a free PWS account or use the latest version of PCF Dev (make sure to use the -s scs
flag) to run PCF on your laptop (which still needs a PWS account sans credit card). And if you do have a PCF instance but do not see the Spring Configuration Server or Service Registry, you should ask your PCF Operator to install Spring Cloud Services for PCF.
The code for this tutorial is located here, note that it is on branch pcf-deployment
. The final outcome will be a very simplified version of a Netflix stack configured for Pivotal Cloud Foundry. Two PCF services will be created, the Service Registry, that will discover clients configured to be discovered, and a Spring Cloud Configuration server that will look for property files to serve to their respective, configured applications. PCF will also host a Zuul router and filter (zuul
), and an API (netflix-protected
) that uses a property file from the Cloud Configuration server. Finally, both zuul
and netflix-protected
will be discoverable by the Service Registry server.
Spring Cloud Configuration
We should update the local Spring Cloud Configuration server to use the same configuration files the PCF Spring Cloud Configuration server will use. Create a ~/zuulreka-config/configurations
folder to keep the properties files for the API. We can move ~/zuulreka-config/components/cloud-config/src/main/resources/netflix-protected.yml
to the new configurations
folder.
external:
property: hello world!
management:
endpoints:
web:
exposure:
include: refresh
~/zuulreka-config/configurations/netflix-protected.yml
I consolidated ~/zuulreka-config/components/cloud-config/src/main/resources/application.yml
and ~/zuulreka-config/components/cloud-config/src/main/resources/bootstrap.yml
and deleted ~/zuulreka-config/components/cloud-config/src/main/resources/application.yml
, because they were both configuring the config server and I like fewer files - you can leave them as is, if you prefer. Also note that the default native
profile was removed and the cloud.config.server.git.uri
property was added. Because the location is not in the ~/zuulreka-config/components/cloud-config/src/main/resources
anymore, the native
profile is unnecessary and the location needs to be defined.
server:
port: 9999
spring:
application:
name: cloud-config
cloud:
config:
server:
git:
uri: ${user.home}/workspace/zuulreka-config/configurations
output:
ansi:
enabled: always
eureka:
client:
serviceUrl:
defaultZone: ${EUREKA_HOST:http://localhost:8282}/eureka
~/zuulreka-config/components/cloud-config/src/main/resources/bootstrap.yml
Because Spring Cloud Configuration needs the config.server.git.uri
to be a Git repository, we can fake that by initializing the configurations directory as a Git repository and commit the latest properties we want the netflix-protected
API to use. This will create a submodule that we do not want to keep, so before you commit, make sure to run rm -rf ~/zuulreka-config/configurations/.git
.
Starting everything locally will have the same exact effect as before with the only difference being where the local Cloud Configuration server loads properties from.
Pivotal Cloud Foundry
To start interfacing with Pivotal Cloud Foundry, download the cf-cli. You can read through the linked cf-cli documentation or you can run cf help
from your terminal to see what commands are available. The cli will be how we will manage the services we create and applications we deploy.
Login to your PCF instance by using cf login
or cf login --sso
for a code to use if you are using a single sign-on solution.
To see what services and plans are available, run the command cf marketplace
.
The two services we will need specifically are p-service-registry
and p-config-server
, both with a plan of standard
. The service registry will run in PCF, waiting for clients to connect to it, so it will only need to be created with a plan and given a name. Running cf create-service help
will show how to create a service. To create the service registry, run cf create-service standard p-service-registry service-registry
. As the output suggests, run cf service service-registry
to check the status of the services creation.
To make the configurations for the Configuration Server easy to remember, create config-server-configuration.json
at the root of the project with this content:
{
"git": {
"uri": "https://github.com/bjstks/zuulreka-config",
"label": "pcf-deployment",
"searchPaths": "configurations"
}
}
~/zuulreka-config/config-server-configuration.json
The uri
will be the location of the GitHub repository where the configuration files are located, the label
is the branch name in this case, and the searchPaths
are the relative path from the root of that repository. You can find more about the Git configuration here. With those configurations, create the configuration server that will house your applications properties files with cf create-service p-config-server standard config-server -c config-server-configuration.yml
. Run cf service config-server
to check the status of the services creation.
These services can also be created through the user interface from the Marketplace
section or from a specific org
and space
from the Services
tab. On the services page, you can select Add a Service
to find and create the service registry and configuration server. Once you find and select the services, the wizard will allow you to input the service names and configurations.
Spring Boot Web application
If you want to see how to create this component from scratch, check out my previous post. For this post, however, I am only going to show how to update the netflix-protected
component. The only changes that will need to be made are to update the dependencies so that the application will connect to the PCS instances of the Service Registry and Cloud Configuration servers, update how it connects to those resources, disable security (opposed to configuring it because some of the added dependencies will add Spring Security to the classpath), and create a manifest.yml
file that will describe to PCF how this application should run.
To update the netflix-protected
component, add to the ~/zuulreka-config/components/netflix-protected/build.gradle
file by changing the Spring dependency management plugin to also import the spring-cloud-services-dependencies from io.pivotal.spring.cloud:spring-cloud-services-dependencies:2.0.1.RELEASE
. Also, include the io.pivotal.spring.cloud:spring-cloud-services-starter-service-registry
dependency within the dependencies
section.
buildscript {
ext {
springBootVersion = '2.0.2.RELEASE'
springCloudVersion = 'Finchley.SR1'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group = 'io.template'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
mavenBom "io.pivotal.spring.cloud:spring-cloud-services-dependencies:2.0.1.RELEASE"
}
}
dependencies {
runtime('org.springframework.boot:spring-boot-devtools')
compile(
'org.springframework.boot:spring-boot-starter-web',
'org.springframework.boot:spring-boot-starter-actuator',
'org.springframework.cloud:spring-cloud-starter-config',
'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client',
'io.pivotal.spring.cloud:spring-cloud-services-starter-service-registry')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
~/zuulreka-config/components/netflix-protected/build.gradle
Because this new dependency will add spring-security
to the classpath, we either need to disable security or configure a username and password. Security is not the focus of this tutorial, so we will disable security altogether by overriding the WebSecurityConfigurerAdapter
. While we are there, we should allow CSRF requests, because we will need to POST, through the Zuul router, to the /refresh
endpoint to refresh the Config Server properties.
package io.template.zuulrekaconfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
@EnableEurekaClient
@SpringBootApplication
public class NetflixProtectedApplication extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().anyRequest().permitAll();
}
public static void main(String[] args) {
SpringApplication.run(NetflixProtectedApplication.class, args);
}
}
~/zuulreka-config/components/netflix-protected/src/main/java/io/template/zuulrekaconfig/NetflixProtectedApplication.java
When running locally, we will want to connect to our localhost
servers, but when we deploy our API to PCF we will want our application to connect to the PCF Services we created earlier. Because all PCF services have a specific structure when the application binds, if the PCF Config Server exists, then the property vcap.services.config-server.credentials.uri
should exist and our application will connect to it, similarly for the Service Registry and the vcap.services.service-registry.credentials.uri
property. However, if the application is started locally, the application will try to connect to the localhost
environment.
server:
port: 8181
spring:
application:
name: netflix-protected
cloud:
config:
uri: ${vcap.services.config-server.credentials.uri:http://localhost:9999}
output:
ansi:
enabled: always
eureka:
client:
serviceUrl:
defaultZone: ${vcap.services.service-registry.credentials.uri:http://localhost:8282}/eureka
~/zuulreka-config/components/netflix-protected/src/main/resources/bootstrap.yml
Next we will create an Application Manifest that describes our application to PCF to cut down on the things we would manually have to configure each time we deployed. For our manifest, we will need to set our applications name, the buildpack, the path to the jar file to be deployed, and the names of the services to bind to once it is deployed.
applications:
- name: netflix-protected
buildpacks:
- java_buildpack_offline
path: build/libs/netflix-protected-0.0.1-SNAPSHOT.jar
services:
- config-server
- service-registry
~/zuulreka-config/components/netflix-protected/manifest.yml
Now we can build and push the netflix-protected
component from the ~/zuulreka-config/components/netflix-protected
directory with ./gradlew clean assemble && cf push -f manifest.yml
.
Spring Zuul Router & Filtering
If you want to see how to create a Zuul Router & Filter, check out my previous post.
First, update ~/zuulreka-config/components/zuul/build.gradle
file by changing the Spring dependency management plugin to also import the spring-cloud-services-dependencies from io.pivotal.spring.cloud:spring-cloud-services-dependencies:2.0.1.RELEASE
. Also, include the io.pivotal.spring.cloud:spring-cloud-services-starter-service-registry
dependency within the dependencies
section.
buildscript {
ext {
springBootVersion = '2.0.2.RELEASE'
springCloudVersion = 'Finchley.SR1'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group = 'io.template'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
mavenBom "io.pivotal.spring.cloud:spring-cloud-services-dependencies:2.0.1.RELEASE"
}
}
dependencies {
compile(
'org.springframework.cloud:spring-cloud-starter-config',
'org.springframework.cloud:spring-cloud-starter-netflix-zuul',
'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client',
'io.pivotal.spring.cloud:spring-cloud-services-starter-service-registry')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
Then to update the application to connect to the service registry when deployed to PCF and or your local instance when ran locally, change the property, defaultZone
at ~/zuulreka-config/components/zuul/src/main/resources/bootstrap.yml
.
eureka:
client:
serviceUrl:
defaultZone: ${vcap.services.service-registry.credentials.uri:http://localhost:8282}/eureka
~/zuulreka-config/components/zuul/src/main/resources/bootstrap.yml
More information on VCAP_SERVICES
can be found here, the most important part to note is that the name service-registry
should match the services name and the trailing /eureka
should be outside of the curly braces. The syntax ${SOME_ENVIRONMENT_VARIABLE:http://localhost:8282}
will try to use the environment variable SOME_ENVIRONMENT_VARIABLE
and if it does not find it, will use http://localhost:8282
explicitly.
We will configure the zuul
PCF deployment with ~/zuulreka-config/components/zuul/manifest.yml
that will configure the name of the application, buildpack for PCF to use first, location of the assembled jar to be deployed, number of instances it needs to run, and services it should bind to. We do not need to do a lot with our manifest but to learn about your options, check out these docs.
applications:
- name: zuul
buildpacks:
- java_buildpack_offline
path: build/libs/zuul-0.0.1-SNAPSHOT.jar
services:
- service-registry
~/zuulreka-config/components/zuul/manifest.yml
Now we can build and push the zuul
component from the ~/zuulreka-config/components/zuul
directory with ./gradlew clean assemble && cf push -f manifest.yml
.
Finale
The service registry needs about a minute and a half to register both applications. When it does, we can send a GET request to the Zuul router to get a response from the api we deployed. Using httpie, type http get https://zuul.apps.pcfone.io/netflix-protected/hello
to see the response. Note that your domain could differ from apps.pcfone.io
.
Now, to check that the @RefreshScope
annotation still works in PCF - change the external.property
for the netflix-protected
application to say hello universe!
. Also add, commit, and push those changes to your repository. Next, type http post https://netflix-protected.apps.pcfone.io/actuator/refresh
to have the API refetch the updated properties. Now typing http get https://zuul.apps.pcfone.io/netflix-protected/hello
should yield the updated properties.
I hope these tutorials have been useful! Please reach out and leave me some feedback or just ask for some clarification at [email protected].