一直以来,玩springcloud的,基本都是在玩Springboot1.x,Springcloud(Dalston版)的众多相关组件来做配置中心、服务注册与发现,网关用的是Netflix公司对springboot做的LB,等等,但是这些东西太过沉重,复杂了。在一个以k8s为基础的iaas服务系统,如果不用k8s的特性来做这些事,那是说不过去的。理由这就不重复述说了。一句话:减少系统服务的复杂性。
本文主要介绍springcloud结合k8s,做配置管理,避免更多服务组件的冗余,完美填坑版!
环境:
ubuntu16.04
docker18.04
k8s1.13.x +
maven3.5.3
java1.8 +
springboot 2.1.1
spring-cloud-kubernetes:1.0.1.RELEAS
1. 前提
Ubuntu下安装docker18.04 or 其它较高版本,k8s1.13.x及以上,jvm环境等。
2. 创建项目
基础依赖:
<code><parent>
<groupid>org.springframework.boot/<groupid>
<artifactid>spring-boot-starter-parent/<artifactid>
<version>2.1.8.RELEASE/<version>
<relativepath>
/<parent>
\t<properties>
\t\t<project.build.sourceencoding>UTF-8/<project.build.sourceencoding>
\t\t<project.reporting.outputencoding>UTF-8/<project.reporting.outputencoding>
\t\t<java.version>1.8/<java.version>
\t\t<swagger.version>2.6.1/<swagger.version>
\t\t<xstream.version>1.4.7/<xstream.version>
\t\t<pagehelper.version>4.1.6/<pagehelper.version>
\t\t<fastjson.version>1.2.51/<fastjson.version>
\t\t<shiro.version>1.3.0/<shiro.version>
\t\t
\t\t<kubernetes-client-version>5.0.0/<kubernetes-client-version>
\t\t<fabric8-kubernetes-client.version>4.6.1/<fabric8-kubernetes-client.version>
\t\t<springcloud.version>Greenwich.SR4/<springcloud.version>
\t\t<springcloud.kubernetes.version>1.1.1.RELEASE/<springcloud.kubernetes.version>
\t\t<mysql.version>5.1.46/<mysql.version>
\t/<properties>
\t<dependencymanagement>
\t\t<dependencies>
\t\t\t<dependency>
\t\t\t\t<groupid>org.springframework.cloud/<groupid>
\t\t\t\t<artifactid>spring-cloud-dependencies/<artifactid>
\t\t\t\t<version>${springcloud.version}/<version>
\t\t\t\t<type>pom/<type>
\t\t\t\t<scope>import/<scope>
\t\t\t/<dependency>
\t\t/<dependencies>
\t/<dependencymanagement>/<code>
核心依赖:
<code><dependency>
\t\t\t<groupid>org.springframework.boot/<groupid>
\t\t\t<artifactid>spring-boot-starter-web/<artifactid>
\t\t\t<exclusions>
\t\t\t\t<exclusion>
\t\t\t\t\t<groupid>org.springframework.boot/<groupid>
\t\t\t\t\t<artifactid>spring-boot-starter-tomcat/<artifactid>
\t\t\t\t/<exclusion>
\t\t\t/<exclusions>
\t\t/<dependency>
\t\t<dependency>
\t\t\t<groupid>org.springframework.boot/<groupid>
\t\t\t<artifactid>spring-boot-starter-undertow/<artifactid>
\t\t/<dependency>
\t\t
\t\t<dependency>
<groupid>org.springframework.boot/<groupid>
<artifactid>spring-boot-actuator/<artifactid>
/<dependency>
<dependency>
<groupid>org.springframework.boot/<groupid>
<artifactid>spring-boot-actuator-autoconfigure/<artifactid>
/<dependency>
<dependency>
<groupid>org.springframework.cloud/<groupid>
<artifactid>spring-cloud-starter-kubernetes-config/<artifactid>
/<dependency>
<dependency>
<groupid>org.springframework.boot/<groupid>
<artifactid>spring-boot-starter-test/<artifactid>
<scope>test/<scope>
/<dependency>
\t\t
\t\t<dependency>
<groupid>org.springframework.cloud/<groupid>
<artifactid>spring-cloud-commons/<artifactid>
/<dependency>
\t\t
\t\t<dependency>
<groupid>org.springframework.cloud/<groupid>
<artifactid>spring-cloud-kubernetes-core/<artifactid>
/<dependency>
<dependency>
<groupid>org.springframework.cloud/<groupid>
<artifactid>spring-cloud-kubernetes-discovery/<artifactid>
/<dependency>
\t\t
\t\t<dependency>
<groupid>org.springframework.cloud/<groupid>
<artifactid>spring-cloud-starter-kubernetes-ribbon/<artifactid>
/<dependency>
\t\t
\t\t<dependency>
<groupid>org.springframework.cloud/<groupid>
<artifactid>spring-cloud-starter-netflix-ribbon/<artifactid>
/<dependency>
<dependency>
<groupid>org.springframework.cloud/<groupid>
<artifactid>spring-cloud-starter-netflix-hystrix/<artifactid>
/<dependency>/<code>
**本次依赖引入配置管理、服务的发现(即消费者)。**
如果有操作redis和db的话,引入相应的依赖:
<code>
<dependency>
<groupid>org.mybatis.spring.boot/<groupid>
<artifactid>mybatis-spring-boot-starter/<artifactid>
<version>1.1.1/<version>
/<dependency>
<dependency>
\t\t\t<groupid>mysql/<groupid>
\t\t\t<artifactid>mysql-connector-java/<artifactid>
\t\t\t<version>${mysql.version}/<version>
\t\t/<dependency>
\t <dependency>
\t <groupid>com.github.pagehelper/<groupid>
\t <artifactid>pagehelper/<artifactid>
\t <version>${pageHelper.version}/<version>
\t /<dependency>
<dependency>
<groupid>com.alibaba/<groupid>
<artifactid>druid/<artifactid>
<version>1.1.3/<version>
/<dependency>
\t\t<dependency>
\t\t <groupid>org.apache.commons/<groupid>
\t\t <artifactid>commons-lang3/<artifactid>
\t\t /<dependency>
\t\t
\t\t<dependency>
\t\t <groupid>commons-collections/<groupid>
\t\t <artifactid>commons-collections/<artifactid>
\t\t <version>3.2.2/<version>
\t\t/<dependency>
\t\t<dependency>
\t\t\t<groupid>org.springframework.boot/<groupid>
\t\t\t<artifactid>spring-boot-starter-data-redis/<artifactid>
\t\t/<dependency>
\t\t<dependency>
\t\t\t<groupid>org.springframework.boot/<groupid>
\t\t\t<artifactid>spring-boot-starter-redis/<artifactid>
\t\t\t<version>1.4.7.RELEASE/<version>
\t\t/<dependency>
\t\t
\t\t<dependency>
<groupid>com.google.guava/<groupid>
<artifactid>guava/<artifactid>
<version>19.0/<version>
/<dependency>/<code>
剩下的就是构建镜像时的插件:
<code><build>
\t\t<finalname>${project.artifactId}/<finalname>
\t\t<plugins>
\t\t\t<plugin>
\t\t\t\t<groupid>org.springframework.boot/<groupid>
\t\t\t\t<artifactid>spring-boot-maven-plugin/<artifactid>
\t\t\t\t<configuration>
\t\t\t\t\t<jvmarguments>-Dfile.encoding=UTF-8/<jvmarguments>
\t\t\t\t\t<fork>true/<fork>
\t\t\t\t/<configuration>
\t\t\t/<plugin>
\t\t\t<plugin>
\t\t\t\t<groupid>org.jacoco/<groupid>
\t\t\t\t<artifactid>jacoco-maven-plugin/<artifactid>
\t\t\t\t<version>0.7.8/<version>
\t\t\t\t<executions>
\t\t\t\t\t<execution>
\t\t\t\t\t\t<goals>
\t\t\t\t\t\t\t<goal>prepare-agent/<goal>
\t\t\t\t\t\t\t<goal>report/<goal>
\t\t\t\t\t\t/<goals>
\t\t\t\t\t/<execution>
\t\t\t\t/<executions>
\t\t\t/<plugin>
\t\t/<plugins>
\t/<build>/<code>
接下来,我们创建主类:
<code>@SpringBootApplication(scanBasePackages = { "com.leinao" })
@EnableConfigurationProperties(EnvConfig.class)
@EnableDiscoveryClient
@EnableHystrix
@EnableScheduling
public class AdminApp {
\tpublic static void main(String[] args) {
\t\tSpringApplication.run(AdminApp.class, args);
\t}
}/<code>
注意这里创建启动类时,对springboot的项目进行了优化,避免启动时加载很多,启动繁重,具体深度优化,可参考:https://mp.weixin.qq.com/s?__biz=MzU2NjIzNDk5NQ==&mid=2247487954&idx=1&sn=2426451f3bd83161cfe1237f82d6b448&key=f8fb043b3d2681a794e51a46e142af77355722dff712776af12b1f3c831218df6dfc329df63c8e5e550b3d88d58f0f178c4c3c16b141733e0e3344fa595e2bc25241d864d45132753fd99279b832de85&ascene=1&uin=MzQzMzI2NjAxMQ%3D%3D&devicetype=Windows+10&version=62070158&lang=zh_CN&pass_ticket=pnSSI9jAq0M11V5hYMmkoVm5qO%2FWk9l3UUUJMglbdtdDOzLHa7iHsDmwSzs486sD。
然后我们在进行配置,注意:据官方说,项目的src\\main\\resources路径下不要创建application.yml文件,只创建名为bootstrap.yml的文件:
<code>management:
endpoint:
restart:
enabled: true
health:
enabled: true
info:
enabled: true
spring:
application:
name: edge-admin
cloud:
kubernetes:
config:
sources:
- name: ${spring.application.name}
namespace: default
discovery:
all-namespaces: true
reload:
#自动更新配置的开关设置为打开
enabled: true
#更新配置信息的模式:polling是主动拉取,event是事件通知
mode: polling
#主动拉取的间隔时间是500毫秒
period: 500
http:
encoding:
charset: UTF-8
enabled: true
force: true
mvc:
throw-exception-if-no-handler-found: true
main:
allow-bean-definition-overriding: true # 当遇到同样名称时,是否允许覆盖注册/<code>
这里,我创建了bootstrap文件,同时也加了application文件,启动时会先加载bootstrap,验证有效。
在application.yaml中,我们加入如下内容:
<code>server:
port: 9999
undertow:
accesslog:
enabled: false
pattern: combined
servlet:
session:
timeout: PT120M
logging:
path: /data/${spring.application.name}/logs
management:
endpoint:
restart:
enabled: true
health:
enabled: true
info:
enabled: true
client:
http:
request:
connectTimeout: 8000
readTimeout: 30000
mybatis:
mapperLocations: classpath:mapper/*.xml
typeAliasesPackage: com.demo.*.model
backend:
ribbon:
eureka:
enabled: false
client:
enabled: true
ServerListRefreshInterval: 5000
hystrix.command.BackendCall.execution.isolation.thread.timeoutInMilliseconds: 5000
hystrix.threadpool.BackendCallThread.coreSize: 5/<code>
注意:这里的server设置session的超时时间,对于springboot2.0与1.0版本完全不一样了,具体看内容。
其他的application-test.yaml等配置文件,配置的是日志的级别:
<code>logging:
level:
com:
demo: INFO
org:
springframework:
web: INFO/<code>
接下来配置环境配置:
EnvConfig.java类作为环境变量配置,注解ConfigurationProperties的prefix="spring_cloud",
表示该类用到的配置项都是名为"spring_cloud"的配置项的子内容 :
<code>package com.demo.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* 配置信息
* @author Damon
* @date 2019年10月25日 下午1:54:01
*
*/
@Configuration
@ConfigurationProperties(prefix = "greeting")
public class EnvConfig {
private String message = "This is a dummy message";
private String container_command;
private String model_dir_path;
private String so_path;
private String config_path;
private String task_role_name;
private String container_name;
private String container_workdir;
private String init_containers_image;
private String service_account_name;
private String spring_mq_host;
private String spring_mq_port;
private String spring_mq_user;
private String spring_mq_pwd;
private String jdbc_driverClassName;
private String jdbc_url;
private String jdbc_username;
private String jdbc_password;
private String spring_redis_host;
private String spring_redis_port;
private String spring_redis_pwd;
private String kube_apiserver_address;
private String image_path;
private String volume_image_path;
private String inference_job_namespace;
private String api_version;
private String remote_deployment_url;
private String remote_pods_url;
private String remote_deployment_pod_log_url;
private String base_path;
private String chunk_size;
private String cas_url;
private String create_job_url;
private String abnormal_data_dir;
private Long expire_time= 600000L;
public String getMessage() {
return this.message;
}
public void setMessage(String message) {
this.message = message;
}
\tpublic String getContainer_command() {
\t\treturn container_command;
\t}
\tpublic void setContainer_command(String container_command) {
\t\tthis.container_command = container_command;
\t}
\tpublic String getModel_dir_path() {
\t\treturn model_dir_path;
\t}
\tpublic void setModel_dir_path(String model_dir_path) {
\t\tthis.model_dir_path = model_dir_path;
\t}
\tpublic String getSo_path() {
\t\treturn so_path;
\t}
\tpublic void setSo_path(String so_path) {
\t\tthis.so_path = so_path;
\t}
\tpublic String getConfig_path() {
\t\treturn config_path;
\t}
\tpublic void setConfig_path(String config_path) {
\t\tthis.config_path = config_path;
\t}
\tpublic String getTask_role_name() {
\t\treturn task_role_name;
\t}
\tpublic void setTask_role_name(String task_role_name) {
\t\tthis.task_role_name = task_role_name;
\t}
\tpublic String getContainer_name() {
\t\treturn container_name;
\t}
\tpublic void setContainer_name(String container_name) {
\t\tthis.container_name = container_name;
\t}
\tpublic String getContainer_workdir() {
\t\treturn container_workdir;
\t}
\tpublic void setContainer_workdir(String container_workdir) {
\t\tthis.container_workdir = container_workdir;
\t}
\tpublic String getInit_containers_image() {
\t\treturn init_containers_image;
\t}
\tpublic void setInit_containers_image(String init_containers_image) {
\t\tthis.init_containers_image = init_containers_image;
\t}
\tpublic String getService_account_name() {
\t\treturn service_account_name;
\t}
\tpublic void setService_account_name(String service_account_name) {
\t\tthis.service_account_name = service_account_name;
\t}
\tpublic String getSpring_mq_host() {
\t\treturn spring_mq_host;
\t}
\tpublic void setSpring_mq_host(String spring_mq_host) {
\t\tthis.spring_mq_host = spring_mq_host;
\t}
\tpublic String getSpring_mq_port() {
\t\treturn spring_mq_port;
\t}
\tpublic void setSpring_mq_port(String spring_mq_port) {
\t\tthis.spring_mq_port = spring_mq_port;
\t}
\tpublic String getSpring_mq_user() {
\t\treturn spring_mq_user;
\t}
\tpublic void setSpring_mq_user(String spring_mq_user) {
\t\tthis.spring_mq_user = spring_mq_user;
\t}
\tpublic String getSpring_mq_pwd() {
\t\treturn spring_mq_pwd;
\t}
\tpublic void setSpring_mq_pwd(String spring_mq_pwd) {
\t\tthis.spring_mq_pwd = spring_mq_pwd;
\t}
\tpublic String getJdbc_driverClassName() {
\t\treturn jdbc_driverClassName;
\t}
\tpublic void setJdbc_driverClassName(String jdbc_driverClassName) {
\t\tthis.jdbc_driverClassName = jdbc_driverClassName;
\t}
\tpublic String getJdbc_url() {
\t\treturn jdbc_url;
\t}
\tpublic void setJdbc_url(String jdbc_url) {
\t\tthis.jdbc_url = jdbc_url;
\t}
\tpublic String getJdbc_username() {
\t\treturn jdbc_username;
\t}
\tpublic void setJdbc_username(String jdbc_username) {
\t\tthis.jdbc_username = jdbc_username;
\t}
\tpublic String getJdbc_password() {
\t\treturn jdbc_password;
\t}
\tpublic void setJdbc_password(String jdbc_password) {
\t\tthis.jdbc_password = jdbc_password;
\t}
\tpublic String getSpring_redis_host() {
\t\treturn spring_redis_host;
\t}
\tpublic void setSpring_redis_host(String spring_redis_host) {
\t\tthis.spring_redis_host = spring_redis_host;
\t}
\tpublic String getSpring_redis_port() {
\t\treturn spring_redis_port;
\t}
\tpublic void setSpring_redis_port(String spring_redis_port) {
\t\tthis.spring_redis_port = spring_redis_port;
\t}
\tpublic String getSpring_redis_pwd() {
\t\treturn spring_redis_pwd;
\t}
\tpublic void setSpring_redis_pwd(String spring_redis_pwd) {
\t\tthis.spring_redis_pwd = spring_redis_pwd;
\t}
\tpublic String getKube_apiserver_address() {
\t\treturn kube_apiserver_address;
\t}
\tpublic void setKube_apiserver_address(String kube_apiserver_address) {
\t\tthis.kube_apiserver_address = kube_apiserver_address;
\t}
\tpublic String getImage_path() {
\t\treturn image_path;
\t}
\tpublic void setImage_path(String image_path) {
\t\tthis.image_path = image_path;
\t}
\tpublic String getVolume_image_path() {
\t\treturn volume_image_path;
\t}
\tpublic void setVolume_image_path(String volume_image_path) {
\t\tthis.volume_image_path = volume_image_path;
\t}
\tpublic String getInference_job_namespace() {
\t\treturn inference_job_namespace;
\t}
\tpublic void setInference_job_namespace(String inference_job_namespace) {
\t\tthis.inference_job_namespace = inference_job_namespace;
\t}
\tpublic String getApi_version() {
\t\treturn api_version;
\t}
\tpublic void setApi_version(String api_version) {
\t\tthis.api_version = api_version;
\t}
\tpublic String getRemote_deployment_url() {
\t\treturn remote_deployment_url;
\t}
\tpublic void setRemote_deployment_url(String remote_deployment_url) {
\t\tthis.remote_deployment_url = remote_deployment_url;
\t}
\tpublic String getRemote_pods_url() {
\t\treturn remote_pods_url;
\t}
\tpublic void setRemote_pods_url(String remote_pods_url) {
\t\tthis.remote_pods_url = remote_pods_url;
\t}
\tpublic String getRemote_deployment_pod_log_url() {
\t\treturn remote_deployment_pod_log_url;
\t}
\tpublic void setRemote_deployment_pod_log_url(String remote_deployment_pod_log_url) {
\t\tthis.remote_deployment_pod_log_url = remote_deployment_pod_log_url;
\t}
\tpublic String getBase_path() {
\t\treturn base_path;
\t}
\tpublic void setBase_path(String base_path) {
\t\tthis.base_path = base_path;
\t}
\tpublic String getChunk_size() {
\t\treturn chunk_size;
\t}
\tpublic void setChunk_size(String chunk_size) {
\t\tthis.chunk_size = chunk_size;
\t}
\tpublic Long getExpire_time() {
\t\treturn expire_time;
\t}
\tpublic void setExpire_time(Long expire_time) {
\t\tthis.expire_time = expire_time;
\t}
\tpublic String getCas_url() {
\t\treturn cas_url;
\t}
\tpublic void setCas_url(String cas_url) {
\t\tthis.cas_url = cas_url;
\t}
\tpublic String getCreate_job_url() {
\t\treturn create_job_url;
\t}
\tpublic void setCreate_job_url(String create_job_url) {
\t\tthis.create_job_url = create_job_url;
\t}
\tpublic String getAbnormal_data_dir() {
\t\treturn abnormal_data_dir;
\t}
\tpublic void setAbnormal_data_dir(String abnormal_data_dir) {
\t\tthis.abnormal_data_dir = abnormal_data_dir;
\t}
}/<code>
测试demo类:
<code>/**
* @author Damon
* @date 2019年12月27日 上午9:16:41
*
*/
@RestController
public class DemoController {
\t
\t@Autowired
private EnvConfig envConfig;
\t
\t/**
\t *
\t * @author Damon
\t * @date 2019年12月26日
\t *
\t */
\t
\t@GetMapping(value = "/getTest")
\tpublic String getTest() {
\t\treturn envConfig.getBase_path();
\t}
}/<code>
重点:默认的svc是没有权限访问k8s的API Server的资源的,执行如下脚本,可以提升权限,允许其访问configmap的可读权限:
<code>#使用这个代表集群最高权限,deployment中无需引入serviceAccount: config-reader
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: fabric8-rbac
subjects:
- kind: ServiceAccount
# Reference to upper's `metadata.name`
name: default
# Reference to upper's `metadata.namespace`
namespace: default
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io/<code>
配置configmap:
<code>kind: ConfigMap
apiVersion: v1
metadata:
name: edge-admin
data:
application.yaml: |-
greeting:
message: Say Hello to the World
---
spring:
profiles: dev
greeting:
message: Say Hello to the Developers
---
spring:
profiles: test
greeting:
message: Say Hello to the Test
---
spring:
profiles: prod
greeting:
message: Say Hello to the Prod/<code>
接下来就是执行deployment启动项目了:
<code>apiVersion: apps/v1
kind: Deployment
metadata:
name: edge-admin-deployment
labels:
app: edge-admin
spec:
replicas: 1
selector:
matchLabels:
app: edge-admin
template:
metadata:
labels:
app: edge-admin
spec:
nodeSelector:
edge-admin: "true"
containers:
- name: edge-admin
image: 10.11.2.20:8000/harbor/edge-admin
imagePullPolicy: IfNotPresent
ports:
- name: admin01
containerPort: 1002
volumeMounts:
- mountPath: /home/edge-admin
name: edge-admin-path
- mountPath: /data/edge-admin
name: edge-admin-log-path
- mountPath: /etc/kubernetes
name: kube-config-path
- mountPath: /abnormal_data_dir
name: abnormal-data-dir
args: ["sh", "-c", "nohup java $JAVA_OPTS -jar -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m -Xms1024m -Xmx1024m -Xmn256m -Xss256k -XX:SurvivorRatio=8 -XX:+UseConcMarkSweepGC edge-admin.jar --spring.profiles.active=dev", "&"]
hostAliases:
- ip: "10.10.1.5"
hostnames:
- "k8s.api.server"
- "foo.remote"
- ip: "127.0.0.1"
hostnames:
- "foo.localhost"
- ip: "0.0.0.0"
hostnames:
- "foo.all"
#利用admin-rbac.yaml来获取权限
#serviceAccount: config-reader
#serviceAccountName: config-reader
volumes:
- name: edge-admin-path
hostPath:
path: /var/pai/edge-admin
- name: edge-admin-log-path
hostPath:
path: /data/edge-admin
- name: kube-config-path
hostPath:
path: /etc/kubernetes
- name: abnormal-data-dir
hostPath:
path: /data/images/detect_result/defect/<code>
其中,前面说的,项目启动参数对其性能优化,是对jvm的参数设置。分别执行kubectl apply -f deployment.yaml和configmap.yaml,创建demo时所用的configmap的资源以及利用k8s部署启动项目。
最后打开浏览器:执行ip:port/hello,即可看到configmap中对应的属性值,这里就不展示了,有兴趣的可以试试。
以上即是对springcloud和k8s首次结合后利用其configmap特性,来做配置管理,摒弃springcloud-config、spring-boot-starter-actuator的组件,减少系统的复杂性,毕竟k8s是肯定会被用到的,所以可以直接用其特性来做系统服务的环境配置管理。
閱讀更多 小碼哥Damon 的文章