- 知识概念
- 了解Spring Cloud
- Spring Boot交付实践
- 打镜像并推送到仓库
$ docker build . -t harbor.od.com/base/tools:v4 -f Dockerfile
$ docker push harbor.od.com/base/tools:v4 - Spring Cloud交付实践
#配置Project Metadata
#选择版本
#配置项目目录
#删除蓝色区域无效文件
#pom.xml文件中引入依赖
参考官档说明:https://spring.io/projects/spring-cloud#overview
##spring-cloud依赖">打开IDEA,依次点击 file → new → project → Spring Initializr
#配置Project Metadata
#选择版本
#配置项目目录
#删除蓝色区域无效文件
#pom.xml文件中引入依赖
参考官档说明:https://spring.io/projects/spring-cloud#overview
##spring-cloud依赖- 配置文件
##默认的是application.properties文件,此内容用yml文件
##application.yml - 启动类EurekaApplication.java配置
#访问 http://localhost:8761/
">右键run运行项目
#访问 http://localhost:8761/
- pom文件中添加依赖
- application.yml
- pom.xml添加
- application.yml(ps:此步骤可忽略,直接看下文给出的yml更完善)
- 启动类
- 关闭csrf
##新版本security默认开启csrf,当项目user-service启动后向eureka注册会报错,如下: - 页面显示完善
服务启动后会自动注册到eureka,但是自动注册到eureka中心的服务在页面显示不规则,不易辨认,故配置辨识信息 - ">服务启动后将自动注册到eureka,访问页面如下

- 修改pom.xml
eureka-ha-peer1 - http://${spring.security.user.name}:${spring.security.user.password}@peer1:8762/eureka/,http://${spring.security.user.name}:${spring.security.user.password}@peer2:8763/eureka/}
fetch-registry: true
instance:
instance-id: ${eureka.instance.hostname}:${server.port}
hostname: peer1
spring:
security:
user:
name: ${EUREKA_USER:admin}
password: ${EUREKA_PASS:admin}
application:
name: eureka-cluster">修改配置文件application.yml,注意集群服务,需要各个eureka的spring.application.name相同
server:
port: ${EUREKA_PORT:8762}
eureka:
client:
service-url:
defaultZone: ${EUREKA_SERVER:http://${spring.security.user.name}:${spring.security.user.password}@peer1:8762/eureka/,http://${spring.security.user.name}:${spring.security.user.password}@peer2:8763/eureka/}
fetch-registry: true
instance:
instance-id: ${eureka.instance.hostname}:${server.port}
hostname: peer1
spring:
security:
user:
name: ${EUREKA_USER:admin}
password: ${EUREKA_PASS:admin}
application:
name: eureka-cluster - 配置host
##因为都是在电脑本机,故将2服务都解析到本地
127.0.0.1 peer1 peer2 - 分别启动peer1和peer2
在先启动peer1的时候会报错连接不到peer2,这是正常现象,因为peer2还没启动,当peer1启动完后启动peer2,就正常了。 - ">访问页面 peer1:8762 or peer2:8763

- Eureka服务注册中心K8S交付
- nginx-sts.yaml
- 无头服务Headless Service
#ng-headless.yaml - 应用
]# kubectl apply -f .
]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-statefulset-0 1/1 Running 0 2m6s
nginx-statefulset-1 1/1 Running 0 83s
nginx-statefulset-2 1/1 Running 0 45s - pod之间的访问
]# kubectl exec -it nginx-statefulset-0 sh
/ # curl nginx-statefulset-1.nginx - 无头服务,供Pod之间的访问
eureka-headless-service.yaml - 有头服务,可通过ingress访问
- 所需要的文件:
- 在pom.xml中重写jar包名称
- 在项目根目录下新建Dockerfile
- 在项目根目录下新建Jenkinsfile
- 在项目根目录下新建deploy目录,用于存放资源清单,目录下新建文件statefulset.yaml head-service.yaml headless-service.yaml ingress.yaml ,四个yaml文件以模板身份存在,故其中使用变量以便捷适用于多环境。
statefulset.yaml - 项目根目录下新建
sonar-project.properties - 修改项目中文件application.yml。部署k8s集群时,将eureka的集群地址通过参数的形式传递到pod内部,因此本地开发时,直接按照单点模式进行。上线后也不需特定修改。
#点击创建后,根据提示完成项目推送,为区别多分支情况,此内容将推送到develop分支
cd existing_folder #进入到eureka项目根目录
git init
git remote add origin http://gitlab.crab.com/springcloud/eureka.git
git add .
git commit -m “Initial commit”
git checkout -b develop
git push —set-upstream origin develop">指定group下新建项目,名为eureka
#点击创建后,根据提示完成项目推送,为区别多分支情况,此内容将推送到develop分支
cd existing_folder #进入到eureka项目根目录
git init
git remote add origin http://gitlab.crab.com/springcloud/eureka.git
git add .
git commit -m “Initial commit”
git checkout -b develop
git push —set-upstream origin develop- ">刷新项目页面,可看到项目已推送到代码仓库的develop分支

">新建项目,选择多分支流水线,配置如下

知识概念
微服务并没有一个官方的定义, 我们可以通过传统单体应用架构和微服务架构应用的对比,来理解什么是微服务。
单体应用架构
传统打车软件架构图:
这种单体应用比较适合于小项目,优点是:
- 开发简单直接,集中式管理
- 基本不会重复开发
- 功能都在本地,没有分布式的管理开销和调用开销
当然它的缺点也十分明显,特别对于互联网公司来说:
- 开发效率低:所有的开发在一个项目改代码,递交代码相互等待,代码冲突不断
- 代码维护难:代码功能耦合在一起,新人不知道何从下手
- 部署不灵活:构建时间长,任何小修改必须重新构建整个项目,这个过程往往很长
- 稳定性不高:一个微不足道的小问题,可以导致整个应用挂掉
- 扩展性不够:无法满足高并发情况下的业务需求
微服务应用架构
微服务架构的设计思路不是开发一个巨大的单体式应用,而是将应用分解为小的、互相连接的微服务。
比如,前面描述的系统可被分解为:
每个业务逻辑都被分解为一个微服务,微服务之间通过REST API通信。一些微服务也会向终端用户或客户端开发API接口。但通常情况下,这些客户端并不能直接访问后台微服务,而是通过API Gateway来传递请求。API Gateway一般负责服务路由、负载均衡、缓存、访问控制和鉴权等任务。
微服务架构优点:
- 解决了复杂性问题。它将单体应用分解为一组服务。虽然功能总量不变,但应用程序已被分解为可管理的模块或服务
- 体系结构使得每个服务都可以由专注于此服务的团队独立开发。只要符合服务API契约,开发人员可以自由选择开发技术。这就意味着开发人员可以采用新技术编写或重构服务,由于服务相对较小,所以这并不会对整体应用造成太大影响
- 微服务架构可以使每个微服务独立部署。这些更改可以在测试通过后立即部署。所以微服务架构也使得CI/CD成为可能
微服务架构问题及挑战
微服务的一个主要缺点是微服务的分布式特点带来的复杂性。开发人员需要基于RPC或者消息实现微服务之间的调用和通信,而这就使得服务之间的发现、服务调用链的跟踪和质量问题变得的相当棘手。
- 微服务的一大挑战是跨多个服务的更改
- 比如在传统单体应用中,若有A、B、C三个服务需要更改,A依赖B,B依赖C。我们只需更改相应的模块,然后一次性部署即可。
- 在微服务架构中,我们需要仔细规划和协调每个服务的变更部署。我们需要先更新C,然后更新B,最后更新A。
2. 部署基于微服务的应用也要复杂得多
- 单体应用可以简单的部署在一组相同的服务器上,然后前端使用负载均衡即可。
- 微服务由不同的大量服务构成。每种服务可能拥有自己的配置、应用实例数量以及基础服务地址。就需要不同的配置、部署、扩展和监控组件。此外,我们还需要服务发现机制,以便服务可以发现与其通信的其他服务的地址
以上问题和挑战可大体概括为:
- API Gateway
- 服务间调用
- 服务发现
- 服务容错
- 服务部署
- 数据调用
秒懂微服务: https://www.kancloud.cn/owenwangwen/open-capacity-platform/1480155
微服务框架
如何应对上述挑战,出现了如下微服务领域的框架:
- Spring Cloud(各个微服务基于Spring Boot实现)
- Dubbo
- Service Mesh
- Linkerd
- Envoy
- Conduit
- Istio
了解Spring Cloud
官档: https://spring.io
核心项目及组件:https://spring.io/projects
#Spring Cloud和Dubbo对比
| 服务注册中心 | Zookeeper | Spring Cloud Netflix Eureka |
|---|---|---|
| 核心要素 | Dubbo | Spring Cloud |
| 服务调用方式 | RPC | REST API |
| 服务监控 | Dubbo-monitor | Spring Boot Admin |
| 断路器 | 不完善 | Spring Cloud Netflix Hystrix |
| 服务网关 | 无 | Spring Cloud Netflix Zuul |
| 分布式配置 | 无 | Spring Cloud Config |
| 服务跟踪 | 无 | Spring Cloud Sleuth |
| 消息总线 | 无 | Spring Cloud Bus |
| 数据流 | 无 | Spring Cloud Stream |
| 批量任务 | 无 | Spring Cloud Task |
| …… | …… | …… |
从上图可以看出其实Dubbo的功能只是Spring Cloud体系的一部分。
首先Dubbo是SOA时代的产物,它的关注点主要在于服务的调用,流量分发、流量监控和熔断。而Spring Cloud诞生于微服务架构时代,考虑的是微服务治理的方方面面,另外由于依托了Spirng、Spirng Boot的优势之上,两个框架在开始目标就不一致,Dubbo定位服务治理、Spirng Cloud是一个生态。
Spring Boot交付实践
创建项目
#打开IntelliJ IDEA,依次点击File > New > Project > Spring Initializr
#配置Project Metadata
#配置Dependencies,Web中Spring web和Template Engines中Thymeleaf,版本2.3.7
#配置maven
默认使用IDE自带的maven,下载较慢,可以换成阿里云仓配置。
下载地址:
链接: https://pan.baidu.com/s/1z9dRGv_4bS1uxBtk5jsZ2Q 提取码: 3gva
解压后放到D:\software\apache-maven-3.6.3,修改D:\software\apache-maven-3.6.3\conf\settings.xml 文件:
<?xml version="1.0" encoding="UTF-8"?><settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"><localRepository>D:\opt\maven-repo</localRepository><pluginGroups></pluginGroups><proxies></proxies><servers></servers><mirrors><mirror><id>alimaven</id><mirrorOf>central</mirrorOf><name>aliyun maven</name><url>http://maven.aliyun.com/nexus/content/repositories/central/</url></mirror><mirror><id>nexus-aliyun</id><mirrorOf>*</mirrorOf><name>Nexus aliyun</name><url>http://maven.aliyun.com/nexus/content/groups/public</url></mirror></mirrors></settings>
依次点击 Files > Settings > Maven
#启动项目
编写代码功能
#创建controller(包)及HelloController.java(类)
HelloController.java
package com.example.demo.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String hello(String name) {
return "Hello, " + name;
}
}
#重启项目,带参数访问 http://localhost:8080/hello?name=crab
完善界面
#在resources/templates/目录下新建index.html
<!DOCTYPE html>
<html>
<head>
<title>Devops</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<div class="container">
<h3 th:text="${requestname}"></h3>
<a id="rightaway" href="#" th:href="@{/rightaway}" >立即返回</a>
<a id="sleep" href="#" th:href="@{/sleep}">延时返回</a>
</div>
</body>
</html>
#完善HelloController.java内容
package com.example.demo.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
@RestController
public class HelloController {
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String hello(String name) {
return "Hello, " + name;
}
@RequestMapping("/")
public ModelAndView index(ModelAndView mv) {
mv.setViewName("index");
mv.addObject("requestname", "This is index");
return mv;
}
@RequestMapping("/rightaway")
public ModelAndView returnRightAway(ModelAndView mv) {
mv.setViewName("index");
mv.addObject("requestname","This request is RightawayApi");
return mv;
}
@RequestMapping("/sleep")
public ModelAndView returnSleep(ModelAndView mv) throws InterruptedException {
Thread.sleep(2*1000);
mv.setViewName("index");
mv.addObject("requestname","This request is SleepApi"+",it will sleep 2s !");
return mv;
}
}
接入CI/CD流程
mvn环境镜像制作
将maven3集成到Jenkins slave所使用的tools镜像中
#将maven包解压到目录并按需配置对应settings.xml文件
conf/settings.xml
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<localRepository>/opt/maven-repo</localRepository>
<pluginGroups>
</pluginGroups>
<proxies>
</proxies>
<servers>
</servers>
<mirrors>
<mirror>
<id>alimaven</id>
<mirrorOf>central</mirrorOf>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/repositories/central/</url>
</mirror>
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>*</mirrorOf>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>
</mirrors>
</settings>
Dockerfile
FROM alpine
LABEL maintainer="kazihuo8@qq.com"
USER root
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories && \
apk update && \
apk add --no-cache openrc docker git curl tar gcc g++ make \
bash shadow openjdk8 python2 python2-dev py-pip openssl-dev libffi-dev \
libstdc++ harfbuzz nss freetype ttf-freefont chromium chromium-chromedriver && \
mkdir -p /root/.kube && \
usermod -a -G docker root
COPY config /root/.kube/
COPY config.json /root/.docker/
COPY requirements.txt /
RUN pip install -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com -r requirements.txt
RUN rm -rf /var/cache/apk/* && \
rm -rf ~/.cache/pip
#-----------------安装 kubectl--------------------#
COPY kubectl /usr/local/bin/
RUN chmod +x /usr/local/bin/kubectl
# ------------------------------------------------#
#---------------安装 sonar-scanner-----------------#
COPY sonar-scanner /usr/lib/sonar-scanner
RUN ln -s /usr/lib/sonar-scanner/bin/sonar-scanner /usr/local/bin/sonar-scanner && chmod +x /usr/local/bin/sonar-scanner
ENV SONAR_RUNNER_HOME=/usr/lib/sonar-scanner
# ------------------------------------------------#
#-----------------安装 maven--------------------#
COPY apache-maven-3.6.3 /usr/lib/apache-maven-3.6.3
RUN ln -s /usr/lib/apache-maven-3.6.3/bin/mvn /usr/local/bin/mvn && chmod +x /usr/local/bin/mvn
ENV MAVEN_HOME=/usr/lib/apache-maven-3.6.3
#------------------------------------------------#
打镜像并推送到仓库
$ docker build . -t harbor.od.com/base/tools:v4 -f Dockerfile
$ docker push harbor.od.com/base/tools:v4
jenkins slave配置修改
#更新Jenkins中的jnlp-slave-pod模板镜像
harbor.od.com/base/tools:v4
#新增挂载
由于镜像中maven的目录是/opt/maven-repo,而slave-pod是执行完任务后会销毁,因此需要将maven的数据目录挂载出来,不然每次构建都会重新拉取所有依赖的jar包
创建代码项目
#在gitlab上创建组(SpringCloud),在组下创建项目(springboot-demo)
#将上节交付实践中的代码推送到远程仓库
cd demo
git init
git remote add origin http://gitlab.crab.com/springcloud/springboot-demo.git
git add .
git commit -m "Initial commit"
git push -u origin master
#指定打出来的包名
#修改根目录下的pom.xml文件(第四行为增加行)
</dependencies>
<build>
<finalName>${project.artifactId}</finalName><!--打jar包去掉版本号-->
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
#在项目根目录添加Jenkinsfile文件和Dockerfile文件
Jenkinsfile
pipeline {
agent { label 'jnlp-slave'}
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
disableConcurrentBuilds()
timeout(time: 20, unit: 'MINUTES')
gitLabConnection('gitlab')
}
environment {
IMAGE_REPO = "harbor.od.com/app/spring"
NAMESPACE = "demo"
HOST = "springboot.crab.com"
}
stages {
stage('git-log') {
steps {
script{
sh "git log --oneline -n 1 > gitlog.file"
env.GIT_LOG = readFile("gitlog.file").trim()
}
sh 'printenv'
}
}
stage('checkout') {
steps {
container('tools') {
checkout scm
}
}
}
stage('mvn-package') {
steps {
container('tools') {
script{
sh 'mvn clean package'
}
}
}
}
stage('CI'){
failFast true
parallel {
stage('Unit Test') {
steps {
echo "Unit Test Stage Skip..."
}
}
}
}
stage('build-image') {
steps {
container('tools') {
retry(2) { sh 'docker build . -t ${IMAGE_REPO}:${GIT_COMMIT}'}
}
}
}
stage('push-image') {
steps {
container('tools') {
retry(2) { sh 'docker push ${IMAGE_REPO}:${GIT_COMMIT}'}
}
}
}
stage('deploy') {
steps {
container('tools') {
sh "sed -i 's#{{IMAGE_URL}}#${IMAGE_REPO}:${GIT_COMMIT}#g' deploy/*"
sh "sed -i 's#{{NAMESPACE}}#${NAMESPACE}#g' deploy/*"
sh "sed -i 's#{{INGRESS_SPRINGBOOTDEMO}}#${HOST}#g' deploy/*"
timeout(time: 1, unit: 'MINUTES') {
sh "kubectl apply -f deploy/;sleep 20;rm -rf deploy"
}
}
}
}
}
}
Dockerfile
FROM openjdk:8-jdk-alpine
COPY target/demo.jar app.jar
CMD [ "sh", "-c", "java -jar /app.jar" ]
#在项目根目录新建deploy目录,存放资源清单文件
项目地址:
https://gitee.com/crabluo/springboot-demo
项目构建及效果
在jenkins中添加对应的流水线,构建后,访问 http://springboot.crab.com/ ,效果如下:
Spring Cloud交付实践
本内容基于SpringBoot 2.3.6.RELEASE 和Spring Cloud Hoxton.SR9版本
微服务场景

Eureka服务注册中心交付
https://docs.spring.io/spring-cloud-netflix/docs/2.2.5.RELEASE/reference/html/
在SpringCloud体系中,服务之间的调用是通过http协议进行调用的。而注册中心的主要目的就是维护这些服务的服务列表。
创建spring cloud项目三部曲:
- 引入依赖包
- 修改application.yml配置文件
- 启动类添加注解
新建项目
打开IDEA,依次点击 file → new → project → Spring Initializr

#配置Project Metadata

#选择版本

#配置项目目录

#删除蓝色区域无效文件

#pom.xml文件中引入依赖
参考官档说明:https://spring.io/projects/spring-cloud#overview
##spring-cloud依赖
<properties>
<spring.cloud-version>Hoxton.SR9</spring.cloud-version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
eureka-server依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
说明
服务配置加载插件时会报错 Missing artifact com.fasterxml.jackson.core:jackson-core:bundle:2.5.0 ,添加如下依赖配置即可解决
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.5.0</version>
<type>bundle</type>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.0</version>
<type>bundle</type>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.5.0</version>
<type>bundle</type>
</dependency>
启动服务
配置文件
##默认的是application.properties文件,此内容用yml文件
##application.yml
server:
port: 8761
eureka:
client:
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
register-with-eureka: false
fetch-registry: false
instance:
hostname: localhost
启动类EurekaApplication.java配置
package com.crab.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
右键run运行项目

#访问 http://localhost:8761/

添加认证
默认是直接访问页面,为了安全性,添加账号登陆页面
pom文件中添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
application.yml
server:
port: 8761
eureka:
client:
service-url:
defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@${eureka.instance.hostname}:${server.port}/eureka/
register-with-eureka: false
fetch-registry: false
instance:
hostname: localhost
spring:
security:
user:
name: ${EUREKA_USER:admin}
password: ${EUREKA_PASS:admin}
服务注册
原始eureka是没有任何服务注册到数据里,所以新建服务并注册。服务名称为user-service
#服务部署
操作步骤按照上面新建eureka项目的流程来

pom.xml添加
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
application.yml(ps:此步骤可忽略,直接看下文给出的yml更完善)
server:
port: 7000
eureka:
client:
serviceUrl:
defaultZone: http://${EUREKA_USER:admin}:${EUREKA_PASS:admin}@localhost:8761/eureka/
启动类
package com.crab.user;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
//注意这里也可使用@EnableEurekaClient
//但由于springcloud是灵活的,注册中心支持eureka、consul、zookeeper等
//若写了具体的注册中心注解,则当替换成其他注册中心时,又需要替换成对应的注解了。
//所以 直接使用@EnableDiscoveryClient 启动发现。
//这样在替换注册中心时,只需要替换相关依赖即可。
@EnableDiscoveryClient
@SpringBootApplication
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
关闭csrf
##新版本security默认开启csrf,当项目user-service启动后向eureka注册会报错,如下:
c.n.d.s.t.d.RetryableEurekaHttpClient : Request execution failed with message: com.fasterxml.jackson.databind.exc.MismatchedInputException: Root name 'timestamp' does not match expected ('instance') for type [simple type, class com.netflix.appinfo.InstanceInfo]
解决方式是在eureka server端关闭
##在eureka server项目中新建类WebSecurityConfig.java来关闭
package com.crab.eureka;
import org.springframework.context.annotation.Configuration;
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
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable(); //关闭csrf
http.authorizeRequests().anyRequest().authenticated().and().httpBasic(); //开启认证
}
}
页面显示完善
服务启动后会自动注册到eureka,但是自动注册到eureka中心的服务在页面显示不规则,不易辨认,故配置辨识信息
server:
port: 7000
eureka:
client:
serviceUrl:
defaultZone: http://${EUREKA_USER:admin}:${EUREKA_PASS:admin}@localhost:8761/eureka/
instance:
instance-id: ${eureka.instance.hostname}:${server.port}
prefer-ip-address: true
hostname: user-service
spring:
application:
name: user-service
服务启动后将自动注册到eureka,访问页面如下

知识补充
Eurake有一个配置参数eureka.server.renewalPercentThreshold,定义了renews 和renews threshold的比值,默认值为0.85。当server在15分钟内,比值低于percent,即少了15%的微服务心跳,server会进入自我保护状态
默认情况下,如果Eureka Server在一定时间内没有接收到某个微服务实例的心跳,Eureka Server将会注销该实例(默认90秒)。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,这就可能变得非常危险了,因为微服务本身是健康的,此时本不应该注销这个微服务。
Eureka Server通过“自我保护模式”来解决这个问题,当Eureka Server节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式,Eureka Server就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该Eureka Server节点会自动退出自我保护模式。
自我保护模式是一种对网络异常的安全保护措施。使用自我保护模式,而让Eureka集群更加的健壮、稳定。
开发阶段可以通过配置:eureka.server.enable-self-preservation=false关闭自我保护模式。
生产阶段,理应以默认值进行配置。
至于具体具体的配置参数,可至官网查看:http://cloud.spring.io/spring-cloud-static/Finchley.RELEASE/single/spring-cloud.html#_appendix_compendium_of_configuration_properties
高可用
高可用:
- 优先保证可用性
- 各个节点都是平等的,1个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务
- 在向某个Eureka注册时如果发现连接失败,则会自动切换至其它节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性)
注意点:
- 多实例的话eureka.instance.instance-id需要保持不一样,否则会当成同一个
- eureka.instance.hostname要与defaultZone里的地址保持一致
- 各个eureka的spring.application.name相同
#高可用场景实现
##copy eureka项目目录,分别命名为eureka-ha-peer1和eureka-ha-peer2
说明
eureka-ha-peer1和eureka-ha-peer2都是相同配置,区别点就在于命名和端口,因为跑在同一台电脑上,所以端口进行了区别,故下文以peer1作为示例进行说明,标红部分为需要变动的标识点。
修改pom.xml
eureka-ha-peer1
修改配置文件application.yml,注意集群服务,需要各个eureka的spring.application.name相同
server:
port: ${EUREKA_PORT:8762}
eureka:
client:
service-url:
defaultZone: ${EUREKA_SERVER:http://${spring.security.user.name}:${spring.security.user.password}@peer1:8762/eureka/,http://${spring.security.user.name}:${spring.security.user.password}@peer2:8763/eureka/}
fetch-registry: true
instance:
instance-id: ${eureka.instance.hostname}:${server.port}
hostname: peer1
spring:
security:
user:
name: ${EUREKA_USER:admin}
password: ${EUREKA_PASS:admin}
application:
name: eureka-cluster
配置host
##因为都是在电脑本机,故将2服务都解析到本地
127.0.0.1 peer1 peer2
分别启动peer1和peer2
在先启动peer1的时候会报错连接不到peer2,这是正常现象,因为peer2还没启动,当peer1启动完后启动peer2,就正常了。
访问页面 peer1:8762 or peer2:8763

#当其他服务要连接到高可用的eureka,配置如下:
defaultZone: http://${EUREKA_USER:admin}:${EUREKA_PASS:admin}@peer1:8762/eureka/,http://${EUREKA_USER:admin}:${EUREKA_PASS:admin}@peer2:8763/eureka/
Eureka服务注册中心K8S交付
分析
eureka高可用互相注册,前提是每个节点的访问地址都是固定的。为了在K8S环境中保证服务的访问需求,使用statefulset管理。
statefulset管理有状态服务示例
此示例是为了演示statefulset服务的创建和使用(服务之间的访问)等。
nginx-sts.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx-statefulset
labels:
app: nginx-sts
spec:
replicas: 3
serviceName: "nginx"
selector:
matchLabels:
app: nginx-sts
template:
metadata:
labels:
app: nginx-sts
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
无头服务Headless Service
#ng-headless.yaml
kind: Service
apiVersion: v1
metadata:
name: nginx
spec:
selector:
app: nginx-sts
ports:
- protocol: TCP
port: 80
targetPort: 80
clusterIP: None
应用
]# kubectl apply -f .
]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-statefulset-0 1/1 Running 0 2m6s
nginx-statefulset-1 1/1 Running 0 83s
nginx-statefulset-2 1/1 Running 0 45s
pod之间的访问
]# kubectl exec -it nginx-statefulset-0 sh
/ # curl nginx-statefulset-1.nginx
说明
Pod之间的访问后面为啥加了.nginx,是因为直接访问pod name不行,加.nginx是因为无头服务配置里的name是nginx。
eureka应用
eureka-statefulset.yaml
# eureka-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: eureka-cluster
namespace: dev
spec:
serviceName: "eureka"
replicas: 3
selector:
matchLabels:
app: eureka-cluster
template:
metadata:
labels:
app: eureka-cluster
spec:
containers:
- name: eureka
image: 172.21.51.67:5000/spring-cloud/eureka-cluster:v1
ports:
- containerPort: 8761
resources:
requests:
memory: 400Mi
cpu: 50m
limits:
memory: 2Gi
cpu: 2000m
env:
- name: MY_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: JAVA_OPTS
value: -XX:+UnlockExperimentalVMOptions
-XX:+UseCGroupMemoryLimitForHeap
-XX:MaxRAMFraction=2
-XX:CICompilerCount=8
-XX:ActiveProcessorCount=8
-XX:+UseG1GC
-XX:+AggressiveOpts
-XX:+UseFastAccessorMethods
-XX:+UseStringDeduplication
-XX:+UseCompressedOops
-XX:+OptimizeStringConcat
- name: EUREKA_SERVER
value: "http://admin:admin@eureka-cluster-0.eureka:8761/eureka/,http://admin:admin@eureka-cluster-1.eureka:8761/eureka/,http://admin:admin@eureka-cluster-2.eureka:8761/eureka/"
- name: EUREKA_INSTANCE_HOSTNAME
value: ${MY_POD_NAME}.eureka
- name: EUREKA_PORT
value: "8761"
无头服务,供Pod之间的访问
eureka-headless-service.yaml
apiVersion: v1
kind: Service
metadata:
name: eureka
namespace: dev
labels:
app: eureka
spec:
ports:
- port: 8761
name: eureka
clusterIP: None
selector:
app: eureka-cluster
有头服务,可通过ingress访问
apiVersion: v1
kind: Service
metadata:
name: eureka-ingress
namespace: dev
labels:
app: eureka-cluster
spec:
ports:
- port: 8761
name: eureka-cluster
selector:
app: eureka-cluster
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: eureka-cluster
namespace: dev
spec:
rules:
- host: eureka-cluster.crab.com
http:
paths:
- backend:
serviceName: eureka-ingress
servicePort: 8761
path: /
status:
loadBalancer: {}
接入CICD流程
说明:下文基于前文在ideaJ中新建并配置 eureka 服务且能成功启动的基础上进行。
配置项目文件
所需要的文件:
- Jenkinsfile
- Dockerfile
- deploy/statefulset.yaml,service.yaml
- sonar-project.properties
在pom.xml中重写jar包名称
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
...
</plugins>
</build>
使得打出来的包和项目名称一致
##因为pom.xml文件中有 eureka ,故打出的包名为 eureka.jar
在项目根目录下新建Dockerfile
FROM openjdk:8-jdk-alpine
ADD target/eureka.jar app.jar
ENV JAVA_OPTS=""
CMD [ "sh", "-c", "java $JAVA_OPTS -jar /app.jar" ]
在项目根目录下新建Jenkinsfile
@Library('crab-devops') _
pipeline {
agent { label 'jnlp-slave'}
options {
timeout(time: 20, unit: 'MINUTES')
gitLabConnection('gitlab')
}
environment {
IMAGE_REPO = "harbor.od.com/spring-cloud/eureka-cluster"
IMAGE_CREDENTIAL = "credential-registry"
DINGTALK_CREDS = credentials('dingTalk')
PROJECT = "eureka-cluster"
}
stages {
stage('checkout') {
steps {
container('tools') {
checkout scm
}
}
}
stage('mvn-package') {
steps {
container('tools') {
script{
sh 'mvn clean package'
}
}
}
}
stage('CI'){
failFast true
parallel {
stage('Unit Test') {
steps {
echo "Unit Test Stage Skip..."
}
}
stage('Code Scan') {
steps {
container('tools') {
script {
devops.scan().start()
}
}
}
}
}
}
stage('docker-image') {
steps {
container('tools') {
script{
devops.docker(
"${IMAGE_REPO}",
"${GIT_COMMIT}",
IMAGE_CREDENTIAL
).build().push()
}
}
}
}
stage('deploy') {
steps {
container('tools') {
script{
devops.deploy("deploy",false,"deploy/statefulset.yaml").start()
}
}
}
}
}
post {
success {
script{
devops.notificationSuccess(PROJECT,"dingTalk")
}
}
failure {
script{
devops.notificationFailure(PROJECT,"dingTalk")
}
}
}
}
在项目根目录下新建deploy目录,用于存放资源清单,目录下新建文件statefulset.yaml head-service.yaml headless-service.yaml ingress.yaml ,四个yaml文件以模板身份存在,故其中使用变量以便捷适用于多环境。
statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: eureka-cluster
namespace: {{NAMESPACE}}
spec:
serviceName: "eureka"
replicas: 3
selector:
matchLabels:
app: eureka-cluster
template:
metadata:
labels:
app: eureka-cluster
spec:
containers:
- name: eureka
image: {{IMAGE_URL}}
ports:
- containerPort: 8761
resources:
requests:
memory: 400Mi
cpu: 50m
limits:
memory: 2Gi
cpu: 2000m
env:
- name: MY_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: JAVA_OPTS
value: -XX:+UnlockExperimentalVMOptions
-XX:+UseCGroupMemoryLimitForHeap
-XX:MaxRAMFraction=2
-XX:CICompilerCount=8
-XX:ActiveProcessorCount=8
-XX:+UseG1GC
-XX:+AggressiveOpts
-XX:+UseFastAccessorMethods
-XX:+UseStringDeduplication
-XX:+UseCompressedOops
-XX:+OptimizeStringConcat
- name: EUREKA_SERVER
value: "http://admin:admin@eureka-cluster-0.eureka:8761/eureka/,http://admin:admin@eureka-cluster-1.eureka:8761/eureka/,http://admin:admin@eureka-cluster-2.eureka:8761/eureka/"
- name: EUREKA_INSTANCE_HOSTNAME
value: ${MY_POD_NAME}.eureka
- name: EUREKA_PORT
value: "8761"
headless-service.yaml
apiVersion: v1
kind: Service
metadata:
name: eureka
namespace: {{NAMESPACE}}
labels:
app: eureka
spec:
ports:
- port: 8761
name: eureka
clusterIP: None
selector:
app: eureka-cluster
head-service.yaml 想通过ingress访问eureka,需要使用有头服务
apiVersion: v1
kind: Service
metadata:
name: eureka-ingress
namespace: {{NAMESPACE}}
labels:
app: eureka-cluster
spec:
ports:
- port: 8761
name: eureka-cluster
selector:
app: eureka-cluster
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: eureka-cluster
namespace: dev
spec:
rules:
- host: {{INGRESS_EUREKA_CLUSTER}}
http:
paths:
- backend:
serviceName: eureka-ingress
servicePort: 8761
path: /
status:
loadBalancer: {}
项目根目录下新建sonar-project.properties
sonar.projectKey=eureka-cluster
sonar.projectName=eureka-cluster
# if you want disabled the DTD verification for a proxy problem for example, true by default
# JUnit like test report, default value is test.xml
sonar.sources=src/main/java
sonar.language=java
sonar.tests=src/test/java
sonar.java.binaries=target/classes
修改项目中文件application.yml。部署k8s集群时,将eureka的集群地址通过参数的形式传递到pod内部,因此本地开发时,直接按照单点模式进行。上线后也不需特定修改。
server:
port: ${EUREKA_PORT:8761}
eureka:
client:
service-url:
defaultZone: ${EUREKA_SERVER:http://${spring.security.user.name}:${spring.security.user.password}@localhost:8761/eureka/}
fetch-registry: true
register-with-eureka: true
instance:
instance-id: ${eureka.instance.hostname}:${server.port}
hostname: ${EUREKA_INSTANCE_HOSTNAME:localhost}
prefer-ip-address: true
spring:
security:
user:
name: ${EUREKA_USER:admin}
password: ${EUREKA_PASS:admin}
application:
name: eureka-cluster
说明:${EUREKA_PORT} ${EUREKA_SERVER}等都是从环境变量中读取,能读取的前提是在资源清单statefulset.yaml文件中配置了对应的变量和值。
提交项目到代码仓库
指定group下新建项目,名为eureka

#点击创建后,根据提示完成项目推送,为区别多分支情况,此内容将推送到develop分支
cd existing_folder #进入到eureka项目根目录
git init
git remote add origin http://gitlab.crab.com/springcloud/eureka.git
git add .
git commit -m “Initial commit”
git checkout -b develop
git push —set-upstream origin develop
刷新项目页面,可看到项目已推送到代码仓库的develop分支

新建jenkins项目
新建项目,选择多分支流水线,配置如下


项目效果
将项目对应的jenkins项目配置好后会自动触发构建,构建成功后配置对应的域名解析 eureka-cluster.crab.com
访问效果如下(账号/密码:admin/admin):
