由于本文档记录时使用公司环境,项目命名也是依照公司标准,为了避免不必要的麻烦,文中涉及到公司内部字眼进行了转义或打码处理,造成阅读影响望海涵
本次实战内容是实现eureka集群移植到K8S环境中,实现K8S环境中eureka双节点运行,并通过ingress实现外部访问
通过本次实战,预计将有以下收获
1、理解K8S中Statefulset资源类型
2、理解K8S中ingress资源类型的使用
3、理解springcloud工程在IDEA环境下的docker打包及如何推送到镜像仓库中的方式与过程
4、理解springcloud工程中的配置项如何与K8S的yaml部署脚本中定义的环境变量关联
5、巩固eureka参数配置
目前部门自己搭建了一套harbor私有仓库,这个仓库的访问地址是:http://10.1.12.29/
如上图所示,k8s集群中由2个master节点:10.1.12.27 10.1.12.28,3个node节点:10.1.12.42 10.1.12.43 10.1.12.29组成,其中43可以ssh免登陆到其他所有节点,且43安装了sealos工具,可以执行简单的命令就可以对集群进行扩缩容
<properties>
<docker.image.prefix>eurekaxxxdocker.image.prefix>
<docker.repostory>10.1.12.29docker.repostory>
properties>
<build>
<finalName>${project.artifactId}finalName>
<plugins>
<plugin>
<groupId>com.spotifygroupId>
<artifactId>dockerfile-maven-pluginartifactId>
<version>1.4.10version>
<configuration>
<username>xxxusername>
<password>xxxpassword>
<contextDirectory>${project.basedir}contextDirectory>
<dockerfile>Dockerfiledockerfile>
<repository>${docker.repostory}/${docker.image.prefix}/${project.artifactId}repository>
<tag>${project.version}tag>
<buildArgs>
<JAR_FILE>./target/${project.build.finalName}.jarJAR_FILE>
buildArgs>
configuration>
plugin>
plugins>
build>
docker镜像的制作以及推送操作,都是由Docker来完成的,所以,必须要安装Docker环境。简单的说dockerfile-maven-plugin只是简化了直接操作Docker的复杂度,该是Docker完成的事情,还得由Docker来完成,因此我们开发的机器必须要与一个Docker环境相通,这里有两个选择:一个是在开发的本机环境装docker,那么无需环境变量(DOCKER_HOST)配置;第二种方式是本机链接到远程机器上的docker环境,那么就需要在本机配置DOCKER_HOST环境变量,并且远程的docker环境需要开启远程连接TCP端口。下面步骤2以远程DOCKER环境进行环境准备
若需要把镜像上传到私有仓库,镜像名称的组成格式必须为:私库地址/私库中项目名称/你的项目名,那么在我们的harbor环境中,可能的镜像名称为:10.1.12.29/eurekaxxx/eureka(eurekaxxx在harbor中必须有项目存在)
注意,使用该插件时,我们指定了tag为${project.version},也就是把项目的版本号作为镜像的版本号,但是镜像版本号是不允许有字符串的,也就是说不允许是这样的版本号:1.0.0-RELEASE,因此我修改了项目的版本号,改为1.0.0的格式
文件地址:/lib/systemd/system/docker.service,找到ExecStart最后加上 -H tcp://0.0.0.0:2357
systemctl daemon-reload
sudo service docker restart
执行完以上步骤后,docker环境就已经与harbor仓库打通,可以在docker环境中执行docker push操作进行进行镜像上传(如果不通,可能是防火墙原因)
开发本机环境变量指向docker环境的ip及远程端口,执行完该配置后,本机环境就可以进行镜像构建与推送了
其中JAR_FILE参数是dockerfile-maven-plugin插件中传入的项目jar包的全路径,其他部分不进行细说(编码、时区等是必须设置的)
1、eureka.instance.prefer-ip-address:表示eureka集群节点在eureka界面上访问时是否使用IP来跳转
2、eureka.instance.instance-id:标识注册到eureka中的实例名称,显示在eureka界面的application status字段里
3、eureka.instance.hostname:当prefer-ip-address为true时无效,表示相互注册的名字(显示在DS Replicas里面),同时也作为
4、eureka页面上点击跳转的地址,这里我配置了支持读取环境变量EUREKA_INSTANCE_HOSTNAME
5、eureka.client.register-with-eureka:表示是否把eureka节点注册到服务清单列表中,这里本来是设置false的(因为eureka不需要提供服务给其他服务调用,所以理论上不需要注册,但是在我实验下来,如果设置成false的话,在eureka页面上,center info中所有eureka节点都是unavailable-replicas)
6、eureka.client.fetch-registry:表示是否要去拉取服务清单,因为eureka不需要,所以设置成false
7、eureka.client.service-url.defaultZone:这个参数是关键中的关键,这里要列出所有eureka集群的节点信息,我配置了支持读取环境变量EUREKA_SERVER
执行完成后,在docker服务器上使用docker images可以查看到对应的镜像信息
如上图所示,执行完打包后,在我的虚拟机docker环境中,就已经有了该镜像了,此时在harbor还没有
这里要注意,K8S环境在执行这个脚本的时候,K8S集群中所有机器上的docker环境必须与harbor私库连通(必须配置daemon.json文件及进行docker login登录,详见步骤二中的2,3两点)
# file name eureka.yaml
# headless service
apiVersion: v1
kind: Service
metadata:
name: eureka-headless
namespace: development
labels:
app: eureka-headless
spec:
clusterIP: None
ports:
- port: 8761
name: eureka-service
targetPort: 8761
selector:
app: eureka
---
# 有状态pod,会自动根据metadata.name及实例数量生产xxx-0,xxx-1的pod
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: eureka
namespace: development
spec:
serviceName: eureka-headless
replicas: 2
selector:
matchLabels:
app: eureka
template:
metadata:
labels:
app: eureka
spec:
containers:
- name: eureka
image: 10.1.12.29/eurekaxxx/xxx-eureka:1.1.0 # 配置镜像路径,也就是我们刚才push好的镜像
imagePullPolicy: Always
ports:
- containerPort: 8761
resources: # 限制容器的资源
limits:
# jvm会自动发现该限制
memory: 512Mi
env: # 环境变量设置
- name: MY_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name # 取值metadata.name,这里注意,statefulset类型的对象取到的是有-0,-1序号后缀的
- 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 #环境变量,这个变量在我们项目的配置文件中有配置,作用是配置eureka集群的所有地址,注意相同namespace底下使用dns访问时,不需要配置全路径(全路径为podname.headless-name.namespace.cluster.svc.local),只要到podname.headless-name
value: "http://eureka-0.eureka-headless:8761/eureka,http://eureka-1.eureka-headless:8761/eureka"
- name: EUREKA_INSTANCE_HOSTNAME #环境变量,这个变量在我们项目的配置文件中有配置,作用是指定注册到eureka集群中的hostname
value: ${
MY_POD_NAME}.eureka-headless
podManagementPolicy: "Parallel" # 以并行方式创建pod,默认是串行的
如文件所示,其中EUREKA_SERVER的值表示的是eureka集群的所有节点地址,这个地址必须是在pod内部能够访问到的(因为pod中的eureka进程会去访问),那么这里就需要说道一个概念DNS,在K8S内部对象间的访问有2种方式:IP和DNS,首先pod的IP是动态的,每次对pod的伸缩都会导致IP变更,所以若一个pod需要访问另一个pod,那么IP访问方式肯定是不行的。
因此K8S提供了2种方式,一种是使用service来代理pod,要访问pod时其实访问的是service(该种方式本次不讨论);另一种就是使用DNS(使用headless service来代理),那么如何为pod实例生成DNS呢?答案是使用headless service。
为pod创建了headless service后,那么可以使用一个特定的规则来访问这个pod(规则为:...svc.cluster.local),那么这里想象一下,普通的podname的名称是随机的,是根据yaml配置的metadata.name拼接上一个随机数,如此一来,等于这个访问规则是随机的,集群内部对象又如何知道我要访问的podname呢?到这里,我们应该可以想到,statefulset也就是有状态的podname不是随机的,它是由metadata.name拼接上由0开始的序号,也就是说,如果我在yaml文件中设置它有3个实例,那么这3个实例的podname就是podname-0,podname-1,podname-2,因此这个podname的dns对于集群内部来说是可以访问的
什么是StatefulSet对象
应用程序的状态分为有状态和无状态两种,无状态类应用的pod资源可按需增加、减少或重构,而不会对由其提供的服务产生除了并发相应能力之外的其他严重影响;有状态的应用可能本身是分布式集群,各实例间存在关系,无法做到轻易的变更
应用程序与用户、设备、其他应用程序或外部组件进行通信时,根据其是否需要记录前一次或多次通信中的相关信息以作为下一次通信的分类标准,可以将那些需要记录信息的应用程序称为有状态(stateful)应用,而无须记录的则称为无状态(stateless)应用。
在K8S中,无状态的对象有deployment、replicaset、daemonset等,有状态的对象是statefulset
在微服务体系下,属于有状态的组件有:注册中心、配置中心等;属于无状态的有:网关,业务服务
statefulset简称sts,是pod资源控制器的一种实现,用于部署和扩展有状态应用的pod资源,确保他们的运行顺序及每个pod资源的唯一性。其与ReplicaSet控制器不同的是,虽然所有pod对象都基于同一个spec配置所创建,但statefulset需要为每个pod维持一个唯一且固定的标识符,必要时还要为其创建专用的存储卷。StatefulSet主要适用于那些依赖于下列类型资源的应用程序:
一般来说,一个典型、完整可用的StatefulSet通常由三个组件构成:Headless Service、StatefulSet和volumeClaimTemplate。其中,Headless Service用于为pod资源标识符生成可解析的DNS资源记录,StatefulSet用于管控pod资源,volumeClaimTemplate则基于静态或动态的PV供给方式为pod资源提供专有且固定的存储。
注意:创建ingress对象的前提是安装好了ingress controller对象,否则无法路由,如何安装ingress controller详见另一篇文章《K8s ingress安装教程》
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-eureka
namespace: development
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- host: xxx.bomc.com # 访问域名
http:
paths:
- path: /
backend:
serviceName: eureka-headless # 指向headless service
servicePort: 8761 # 指向headless service定义的port字段
记住这里访问域名需配置到hosts文件中,这样在外部主机才能访问
执行完以上步骤后,部署工作结束,我们可以通过ingress中定义的域名访问eureka
验证eureka是否安装成功,可以运行一个eureka客户端程序,把defaultZone字段指向eureka即可
若eureka客户端程序部署在k8s集群内部,那么同样是通过dns来访问;若eureka客户端部署在k8s集群外部,那么可以通过域名来访问
经过本次的实战过程,收获非常多,对k8s、eureka以及微服务打包方式有了更深入的了解,总结为以下几点
1、对eureka server的配置字段更加熟悉,主要是针对eureka.instance.perfer-ip-address,eureka.instance.hostname,eureka.client.service-url.defaultZone这三个字段
2、熟悉了idea+springboot+dockerfile-maven-plugin进行镜像的自动化构建及发布的过程
3、熟悉了k8s中的statefulset,headless service对象的作用
4、熟悉了ingress的基本用法
5、掌握了springboot中配置文件里如何获取环境变量参数以及k8s yaml脚本中如何设置环境变量参数