写点什么

在 K8s 里部署 Eureka 集群

作者:xiaoboey
  • 2021 年 12 月 08 日
  • 本文字数:6759 字

    阅读完需:约 22 分钟

在K8s里部署Eureka集群

在《搭建 K8s 容器化应用的开发调试环境》一文里,我们基于 K3s、Telepresence 和 Skaffold,搭建了一个基本的 K8s 环境,这里就来实操一下,在 K8s 里部署 Spring Cloud 的服务发现 Eureka Server。


1、Clone 代码

GitHub: https://github.com/xiaoboey/from-zero-to-n

from-zero-to-n 是笔者写微服务相关文章的项目,为了方便大家结合文章阅读,尽量把各个主题的代码分开存放,没有从一个简单的项目逐渐演化。本文使用的 from-zero-to-n/dev-in-k8s 基于 from-zero-to-n/two 的代码进行开发。


git clone https://github.com/xiaoboey/from-zero-to-n.gitcd from-zero-to-n/dev-in-k8s
复制代码


查看 from-zero-to-n/dev-in-k8s 下的 eureka-server 项目,目录结构如下:


这个项目的代码非常简单,原本只有 pom.xml、EurekaServerApplication.java 和 application.yml 这三个文件。为了结合 Skaffold 来把它部署到 K8s,增加了 skaffold.yaml、Dockerfile 和 k8s/k8s-*.yaml 等文件,并且把 application.yml 进行拆分重组,dev 和 k8s 两种场景分别配置,这些调整只是增加和修改了配置文件,java 代码没做任何修改。这大概也是大多数 Coder 不关心 K8s 的一个原因了,埋头肝代码就是了,其它由运维去操心吧😂

2、部署到 K8s 要解决的问题

结合原版的 application.yml 文件,我们来分析一下,要把这个应用容器化并部署到 K8s,需要解决哪些问题。

server:  port: 8761  servlet:    context-path: /
spring: profiles: active: dev application: name: eureka-server
eureka: instance: hostname: localhost client: register-with-eureka: false fetch-registry: false service-url: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
management: endpoints: web: exposure: include: ["health","info", "shutdown"] endpoint: health: show-details: always shutdown: enabled: true server: port: 7761
复制代码


主要问题如下:

  • 在部署时可以调整 server.port 和 management.server.port 这两个 ip 端口的值,这个问题属于锦上添花,不解决也不影响;

  • 在部署时如何指定 eureka.client.service-url.defaultZone 的值?K8s 的 Pod 的 ip 地址会变化,这是个难点,也是要解决的关键问题。


像 server.port 这类直接给参数指定固定值的问题,Docker 的镜像机制已经解决了,在构建镜像的时候通过 EVN 设置环境变量,实例化镜像时用-e 参数赋值。


Spring Boot 对环境变量也提供了支持,在 application.yml 文件中可以用${环境变量名}的方式取环境变量的值,还可以指定默认值:${环境变量名:默认值}


至于 Pod 地址会变,无法设置 defaultZone 的问题,在互联网世界早已有成熟的方案,那就是域名。只要域名固定下来,域名指向的 ip 地址发生变化,不会影响终端用户的访问。K8s 也提供了 DNS 服务,在创建 Service 时提供了一个 Headless 的机制(clusterIP: None),不给 cluster 分配具体的 ip 地址,通过域名访问服务。


在 application-k8s.yml 文件里,使用环境变量,并按集群的方式配置 Eureka:

eureka:  instance:    hostname: ${EUREKA_INSTANCE_HOSTNAME}    prefer-ip-address: false  client:    register-with-eureka: true    fetch-registry: true    service-url:      defaultZone: ${EUREKA_INSTANCE_LIST}
复制代码



3、Dockerfile

先从应用的容器化开始,也就是把应用打包为 Docker 镜像。


Dockerfile 是 Docker 用来构建容器镜像的源文件,此处尽量注解说明了每一条命令,更多 Dockerfile 的知识请自行搜索其它资料进行学习。

# 基础镜像FROM openjdk:8-jdk-alpine# 作者信息LABEL maintainer="xiaoqb@foxmail.com"
# 修改alpine的源为阿里云的源RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories# 更新RUN apk upgrade --available && sync
# 创建文件夹RUN mkdir /app# 设置当前的工作路径,后续的操作在这个路径下进行WORKDIR /app# 把编译后的jar复制到容器里ADD ./target/*.jar eureka-server.jar
# 设置环境变量(这类变量可以在启动容器时设置,对应docker run的-e参数)ENV SPRING_PROFILES_ACTIVE=devENV HTTP_PORT=8761ENV ACTUATOR_PORT=7761
# 容器开放端口EXPOSE $HTTP_PORTEXPOSE $ACTUATOR_PORT
# 容器启动执行命令# -XX:+UseContainerSupport 让JVM在容器环境里分配合理的堆内存# -XX:MaxRAMPercentage 取值范围0.0到100.0,默认值25.0ENTRYPOINT java -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -jar eureka-server.jar --spring.profiles.active=$SPRING_PROFILES_ACTIVE
复制代码


Alpine 是因 Docker 才火起来的轻量 Linux 发行版,够小够安全,制作出来的镜像比较小。我们这个 Eureka Server 制作出来的镜像大小大概是 200M 左右。更改 Alpine 的源和进行系统升级,都是可选操作,在开发环境,为了提高构建速度,可以注释略过,生产环境则建议进行升级操作。


容器环境的 Java 内存参数,稍微有点不一样,UseContainerSupport 这个参数是 Java 适应容器化部署扩展出来的一个能力。就 K8s 而言,大致的意思是 Java 可以感知到 K8s 对资源的限制。不然的话,Java 参数这里限制了具体的内存,然后 K8s 做容器编排调整资源参数(这里主要是 memory 和 cpu),怎么调整也没用,或者 java 参数的内存值高于 K8s 分配的资源,直接就打爆内存。


手工用 Docker 测试一下这个 Dockerfile 是否配置正确:

# 打开PowerShell,先整体编译,后续可以只编译eureka-serverPS > cd from-zero-to-n\dev-in-k8s> PS > mvn clean install -DskipTests...[INFO] Reactor Summary for from-zero-to-two 0.0.1-SNAPSHOT:[INFO][INFO] from-zero-to-two ................................... SUCCESS [  0.778 s][INFO] eureka-server ...................................... SUCCESS [ 10.242 s][INFO] gateway ............................................ SUCCESS [  1.485 s][INFO] auth-server ........................................ SUCCESS [  3.289 s][INFO] service-one ........................................ SUCCESS [  1.567 s][INFO] service-two ........................................ SUCCESS [  1.234 s][INFO] ------------------------------------------------------------------------[INFO] BUILD SUCCESS[INFO] ------------------------------------------------------------------------
# 进入Eureka ServerPS > cd from-zero-to-n\dev-in-k8s\eureka-server
# 构建Docker镜像PS > docker build -t eureka-server:v1 .[+] Building 0.8s (11/11) FINISHED => [internal] load build definition from Dockerfile => => transferring dockerfile: 32B => [internal] load .dockerignore => => transferring context: 2B => [internal] load metadata for docker.io/library/openjdk:8-jdk-alpine => [internal] load build context => => transferring context: 89B => [1/6] FROM docker.io/library/openjdk:8-jdk-alpine => CACHED [2/6] RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories => CACHED [3/6] RUN apk upgrade --available && sync => CACHED [4/6] RUN mkdir /app => CACHED [5/6] WORKDIR /app => CACHED [6/6] ADD ./target/*.jar eureka-server.jar => exporting to image => => exporting layers => => writing image sha256:162305b3d7cde6e7f5e3ea4ea78048c71c313c5632d74dd1fd3e0a4a822c47b1 => => naming to docker.io/library/eureka-server:v1 # 查看构建结果PS > docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEeureka-server v1 162305b3d7cd 25 minutes ago 253MB...
# 用刚才构建的镜像启动容器化部署的EurekaServer# --rm 结束后自动删除容器实例# --it 运行后进入交互界面并使用标准输入,可以ctl+c终止运行PS > docker run --rm -it eureka-server:v1...... The following profiles are active: dev... Tomcat initialized with port(s): 8761 (http)...
# 指定环境变量参数# 这里只是测试环境变量是否生效,没有给application-k8s.yml文件里需要的两个关键环境变量,启动会失败PS > docker run -e SPRING_PROFILES_ACTIVE=k8s -e SERVER_PORT=9871 --rm -it eureka-server:v1...... The following profiles are active: k8s... Tomcat initialized with port(s): 9871 (http)...
复制代码


4、准备 K8s 部署文件

Eureka Server 的单实例部署没什么好说的了,跟 bootcamp 的部署类似,但要部署为多个实例的集群,要涉及到 K8s 的 StatefulSet 和 Headless。因为 Eureka Server 集群需要相互注册并协同工作,要保持一定的状态(SatefulSet),并且需要其它微服通过 DNS 来找到 Eureka Server 并向 Eureka Server 注册,这需要部署 Headless 的 Service 来实现。


部署 eureka-server 的 StatefulSet 是这个文件:k8s/k8s-sts.yaml

apiVersion: apps/v1kind: StatefulSetmetadata:  namespace: default  name: eureka  labels:    app: eurekaspec:  serviceName: "eureka"  replicas: 3  selector:    matchLabels:      app: eureka  template:    metadata:      labels:        app: eureka    spec:      terminationGracePeriodSeconds: 10      imagePullSecrets:        - name: my-registry-key      containers:        - name: eureka          image: eureka-server:v1          ports:            - containerPort: 8761            - containerPort: 7761          env:            - name: SPRING_PROFILES_ACTIVE              value: k8s            - name: EUREKA_INSTANCE_HOSTNAME              value: ${HOSTNAME}.eureka            - name: EUREKA_INSTANCE_LIST              value: "http://eureka-0.eureka.default.svc.cluster.local:8761/eureka/,http://eureka-1.eureka.default.svc.cluster.local:8761/eureka/,http://eureka-2.eureka.default.svc.cluster.local:8761/eureka/"
# 健康检查 livenessProbe: httpGet: path: /actuator/health port: 7761 initialDelaySeconds: 20 timeoutSeconds: 5
复制代码


这里 env 传递了 3 个环境变量,SPRING_PROFILES_ACTIVE 是在 Dockerfile 里启动 Spring Boot 应用时指定 profile,另两个则是在 application-k8s.yml 里使用。


StatefulSet 里的 ${HOSTNAME}是以 ${metadata.name}-n 这样的格式取名,n 是从 0 开始的序列,此处就是 eureka-0、eureka-1、eureka-2,不但始终从 0 开始,还保证序号为 0 的实例先就绪,再依次是 1、2...,http://eureka-0.eureka.default.svc.cluster.local里的 eureka 是 headless Service 的名称,default 是 k8s 的 namespace。


因为要用 Skaffold 来部署,所以还需要配置访问镜像仓库的 Secret:

# 下述指令里的***和邮件,请替换你自己的信息# 如果是linux请把行尾的`换成\kubectl create secret `docker-registry my-registry-key `--docker-server=registry.cn-chengdu.aliyuncs.com/*** `--docker-username=*** `--docker-password=*** `--docker-email=a@b.com
复制代码

此操作同时隐含了对容器镜像仓库的准备,请参看《搭建 K8s 容器化应用的开发调试环境


部署 Headless Service 是这个文件:k8s/k8s-svc.yaml

kind: ServiceapiVersion: v1metadata:  name: eureka  namespace: defaultspec:  clusterIP: None  ports:    - name: http      port: 8761      protocol: TCP      targetPort: 8761  selector:    app: eureka
复制代码

Headless 的标志是那句“clusterIP: None”


部署一个负载均衡来提供统一的访问入口:k8s/k8s-lb.yaml

kind: ServiceapiVersion: v1metadata:  name: eureka-lb  namespace: defaultspec:  type: LoadBalancer  ports:    - name: http      port: 8761      protocol: TCP      targetPort: 8761  selector:    app: eureka
复制代码

重点是那句“type: LoadBalancer”。从配置文件看来,Headless 和 LoadBalancer 都是一种 Service,感觉 K8s 的水比较深啊


5、配置 Skaffold

Skaffold 的配置文件就很简单了:skaffold.yaml

apiVersion: skaffold/v2beta26kind: Configbuild:  artifacts:    - image: eureka-serverdeploy:  kubectl:    manifests:      - k8s/k8s-*
复制代码

k8s/k8s-*表示让 skaffold 应用 k8s 文件夹下 k8s-为前缀的那些 yaml 文件进行部署,本来想把 Dockerfile 和 skaffold.yaml 这两文件也放在 k8s 文件夹里,显得整齐一些,无赖 Dockerfile 里的 COPY 和 ADD 操作,只对该文件同级或者子路径的文件有效,暂时无解只能这样了。

6、构建、部署与测试

6.1 部署

用 Skaffold 进行构建与部署(这里按步骤列出了相关的操作,重复操作可以视情况忽略):

# PowerShell下进入项目路径PS > cd from-zero-to-n\dev-in-k8s\eureka-server
# 编译PS > mvn clean install -DskipTests
# 登录镜像仓库PS > docker login <你的镜像仓库地址>
# 确保k8s环境已经启动PS > multipass listPS > multipass start k3s-1
# multipass重启k3s-1后,虚拟机的地址可能会变,所以还需要修改~/.kube/config文件里的k8s集群地址# 验证一下kubectl是否配置正确PS > kubectl get pod
# 构建Docker镜像并部署到k8s# 注意这里的镜像仓库地址,是eureka-server镜像地址的上一级,否则可能导致无法推送镜像到仓库PS > skaffold run --default-repo=<你的镜像仓库地址>
复制代码


查看最后的部署情况:

PS > kubectl get podNAME                    READY   STATUS    RESTARTS   AGEsvclb-eureka-lb-lsgxf   1/1     Running   1          23heureka-2                1/1     Running   0          2m55seureka-1                1/1     Running   0          2m25seureka-0                1/1     Running   0          2m15sPS > kubectl get svcNAME              TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)             AGEeureka-lb         LoadBalancer   10.43.252.142   172.30.144.82   8761:31080/TCP      38meureka            ClusterIP      None            <none>          8761/TCP,7761/TCP   38m...
复制代码

可以看出来 StatefulSet 部署产生的主机名称是固定的,eureka-0、eureka-1 和 eureka-2 也是依次启动。


6.2 调试技巧

出现 Pod 部署失败,运行异常,或者反复重启等情况,大概有这些手段去定位错误:

kubectl logs 命令查看容器里应用的日志:

kubectl logs eureka-0 -f
复制代码

eureka-0 是 Pod 的名称,-f 则表示持续查看后续的日志输出


kubectl describe 查看 Pod 的信息及日志:

kubectl describe pod eureka-0
复制代码

describe 命令可以查看所有的 k8s 资源,pod、svc、secret 等


或者 kubectl exec 用 shell 连接 Pod 进行操作:

kubectl exec -it eureka-0 sh
复制代码

跟 docker 的 exec 类似

6.3 测试

这里就不劳 telepresence 的大驾了,直接用 kubectl 的端口转发(port-foward):

PS > kubectl port-forward --address=0.0.0.0 Pod/eureka-0 8761:8761Forwarding from 0.0.0.0:8761 -> 8761
复制代码

telepresence 开启代理成功后,可能会影响虚拟机对外网的访问,表现为域名解析失败,所以建议测试完后,记得及时 quit,退出 telepresence 的代理状态。或者尽量少用吧🤦‍♂️


在浏览器访问: http://localhost:8761


当然,通过 LoadBalance 访问,也是可以的:

PS > kubectl port-forward --address=0.0.0.0 Service/eureka-lb 8761:8761Forwarding from 0.0.0.0:8761 -> 8761
复制代码

6.4 加入 gateway

把 dev-in-k8s/gateway 也部署到 K8s,测试一下服务注册和发现。Gateway 的配置这里就不贴了,比较简单,不需要 StatefulSet 或者 Headless,只需要传一个环境变量给 eureka.client.service-url.defaultZone:


7、总结与体会

在 Mulitpass 启动的虚拟机里跑 K3s,然后向 K8s 集群部署 Eureka Server,一切顺利的话还是比较快的,就怕中间出什么幺蛾子^_^


虚拟机的 K3s 感觉不够稳定,有时行有时不行,可能是内存、cpu 资源这些的限制,也可能是本机环境的变化导致,也不排除 K8s 本身的问题。这比较烦人,需要折腾的时候多思考,卡壳的时候重启虚拟机,甚至重启本机😢


Telepresence 在 Windows 上确实有些渣,基本上可以稳定重现的一个问题,就是 Telepresence 启动代理后,会影响 K8s 集群里的 Pod 拉取互联网上镜像仓库里的镜像。


Skaffold 反而让人比较惊喜,可以稳定的工作,大厂出的产品在稳定性上还是有保障一些。

发布于: 2 小时前阅读数: 8
用户头像

xiaoboey

关注

IT老兵 2020.07.20 加入

资深Coder,爱好钓鱼逮鸟。

评论

发布
暂无评论
在K8s里部署Eureka集群