Spring Boot 項目轉容器化 K8S 部署實用經驗分享


轉載自:https://cloud.tencent.com/developer/article/1477003

我們知道 Kubernetes 是 Google 開源的容器集群管理系統,它構建在目前流行的 Docker 技術之上,為容器化的應用提供資源調度、部署運行、服務發現、擴容縮容等一整套功能,用於容器集群的自動化部署、擴容以及運維的開源平台。Spring Boot 是 Spring 框架的集成,通過該框架可以大大簡化應用的搭建、開發及部署過程,從而深受大家喜愛。之前 Spring Boot 項目大多以傳統虛擬機或物理機部署方式,轉容器化 K8S 集群部署的話,也是非常簡單的,這里給大家分享一下我在工作中,實際操作中的一些使用經驗。

1、服務配置文件處理方式

對於各個項目分環境部署,最麻煩的就是配置文件的問題,不同的環境需要加載不同的配置,好在 Spring Boot 框架加載配置是非常方便的,具體如何加載配置文件可以參考 這里,我們可以針對不同的環境分別配置不同的配置文件,這里有兩個地方要注意一下:

  • 構建鏡像的時候,盡量實現一個鏡像支持所有環境(即所有配置都打到一個鏡像里面去),在容器啟動時指定加載哪個環境配置即可,例如:在部署 Deployment 時指定 args: ["--spring.profiles.active=prod"] 參數啟動。
  • 盡量不要每個環境打出來一個鏡像版本,傳統方式在構建的時候指定 -D prod 配置 Profile 來指定加載哪個配置,來生成不同的產物 jar,容器化部署后不需要這樣,那樣后期控制各鏡像版本發布會比較麻煩。

2、服務鏡像相關配置

容器化部署服務,肯定少不了鏡像制作這一步,鏡像可以分為基礎鏡像和應用鏡像。

2.1 基礎鏡像

基礎鏡像要求體積盡量小,方便拉取,同時安裝一些必要的軟件,方便后期進入容器內排查問題,我們需要准備好服務運行的底層系統鏡像,比如 Centos、Ubuntu 等常見 Linux 操作系統,然后基於該系統鏡像,構建服務運行需要的環境鏡像,比如一些常見組合:Centos + JdkCentos + Jdk + TomcatCentos + nginx 等,由於不同的服務運行依賴的環境版本不一定一致,所以還需要制作不同版本的環境鏡像,例如如下基礎鏡像版本。

  • Centos6.5 + Jdk1.8: registry.docker.com/baseimg/centos-jdk:6.5_1.8
  • Centos7.5 + Jdk1.8: registry.docker.com/baseimg/centos-jdk:7.5_1.8
  • Centos7.5 + Jdk1.7: registry.docker.com/baseimg/centos-jdk:7.5_1.7
  • Centos7 + Tomcat8 + Jdk1.8: registry.docker.com/baseimg/centos-tomcat-jdk:7.5_8.5_1.8
  • Centos7 + Nginx: registry.docker.com/baseimg/centos-tomcat-jdk:7.5_1.10.2

這樣,就可以標識該基礎鏡像的系統版本及軟件版本,方便后邊選擇對應的基礎鏡像來構建應用鏡像。基礎鏡像的制作方法之一,可以參考 使用 febootstrap 制作自定義基礎鏡像 方式。

2.2 應用鏡像

有了上邊的基礎鏡像后,就很容易構建出對應的應用鏡像了,例如一個簡單的應用鏡像 Dockerfile 如下:

FROM registry.docker.com/baseimg/centos-jdk:7.5_1.8

COPY app-name.jar /opt/project/app.jar
EXPOSE 8080
ENTRYPOINT ["/java", "-jar", "/opt/project/app.jar"]

當然,這里我建議使用另一種方式來啟動服務,將啟動命令放在統一 shell 啟動腳本執行,例如如下Dockerfile 示例:

FROM registry.docker.com/baseimg/centos-jdk:7.5_1.8

COPY app-name.jar /opt/project/app.jar
COPY entrypoint.sh /opt/project/entrypoint.sh
EXPOSE 8080
ENTRYPOINT ["/bin/sh", "/opt/project/entrypoint.sh"]

將服務啟動命令配置到 entrypoint.sh,這樣我們可以擴展做很多事情,比如啟動服務前做一些初始化操作等,還可以向容器傳遞參數到腳本執行一些特殊操作,而且這里變成腳本來啟動,這樣后續構建鏡像基本不需要改 Dockerfile 了。

#!/bin/bash
# do other things here
java -jar $JAVA_OPTS /opt/project/app.jar $1  > /dev/null 2>&1

上邊示例中,我們就注入 $JAVA_OPTS 環境變量,來優化 JVM 參數,還可以傳遞一個變量,這個變量大家應該就猜到了,就是服務啟動加載哪個配置文件參數,例如:--spring.profiles.active=prod 那么,在 Deployment 中就可以通過如下方式配置了:

...
spec:
  containers:
    - name: project-name
      image: registry.docker.com/project/app:v1.0.0
      args: ["--spring.profiles.active=prod"]
      env:
	   - name: JAVA_OPTS
	     value: "-XX:PermSize=512M -XX:MaxPermSize=512M -Xms1024M -Xmx1024M..."
...

是不是很方便,這里可擴展做的東西還很多,根據項目需求來配置。

3、服務日志輸出處理

對於日志處理,之前我們一般會使用 Log4jLogstash 等日志框架將日志輸出到服務器指定目錄,容器化部署后,日志會生成到容器內某個配置的目錄上,外部是沒法訪問的,所以需要將容器內日志掛載到宿主機某個目錄 (例如:/opt/logs 目錄),這樣方便直接查看,或者配置 FilebeatFluent 等工具抓取到 Elasticsearch 來提供日志查詢分析。在 Deployment 配置日志掛載方式也很簡單,配置如下:

...
    volumeMounts:
    - name: app-log
      mountPath: /data/logs/serviceA  #log4j 配置日志輸出到指定目錄
...
	volumes:
    - name: app-log
      hostPath:
        path: /opt/logs #宿主機指定目錄

這里有個地方需要特別注意一下:服務日志要關閉 Console 輸出,避免直接輸出到控制台。默認 Docker 會記錄控制台日志到宿主機指定目錄,日志默認輸出到 /var/lib/docker/containers/<container_id>/<container_id>-json.log,為了避免出現日志太多,占用磁盤空間,需要關閉 Console 輸出並定期清理日志文件。

4、容器服務訪問處理

4.1、配置容器服務暴露目標端口

首先需要提供容器服務需要暴露的目標端口號,例如 HttpHttpsGrpc 等服務端口,創建 Service 時需要指定匹配的容器端口號,Deployment 中配置容器暴露端口配置如下:

	ports:
    - containerPort: 8080
      name: http
      protocol: TCP
    - containerPort: 443
      name: https
      protocol: TCP
    - containerPort: 18989
      name: dubbo
      protocol: TCP

4.2、服務對內對外訪問方式選擇

K8S Service 暴露服務類型有三種:ClusterIPNodePortLoadBalancer,三種類型分別有不同的應用場景。

  • 對內服務發現,可以使用 ClusterIP 方式對內暴露服務,因為存在 Service 重新創建 IP 會更改的情況,所以不建議直接使用分配的 ClusterIP 方式來內部訪問,可以使用 K8S DNS 方式解析,DNS 命名規則為:<svc_name>.<namespace_name>.svc.cluster.local,按照該方式可以直接在集群內部訪問對應服務。
  • 對外服務暴露,可以采用 NodePortLoadBalancer 方式對外暴露服務,NodePort 方式使用集群固定 IP,但是端口號是指定范圍內隨機選擇的,每次更新 Service 該 Port 就會更改,不太方便,當然也可以指定固定的 NodePort,但是需要自己維護 Port 列表,也不方便。LoadBalancer 方式使用集群固定 IPNodePort,會額外申請申請一個負載均衡器來轉發到對應服務,但是需要底層平台支撐。如果使用 AliyunGCE 等雲平台商,可以使用該種方式,他們底層會提供 LoadBalancer 支持,直接使用非常方便。

以上方式或多或少都會存在一定的局限性,所以建議如果在公有雲上運行,可以使用 LoadBalancerIngress 方式對外提供服務,私有雲的話,可以使用 Ingress 通過域名解析來對外提供服務。Ingress 配置使用,可以參考 初試 Kubernetes 暴漏服務類型之 Nginx Ingress初試 Kubernetes 集群中使用 Traefik 反向代理 文章。

5、服務健康監測配置

K8s 提供存活探針和就緒探針,來實時檢測服務的健康狀態,如果健康檢測失敗,則會自動重啟該 Pod 服務,檢測方式支持 exechttpGettcpSocket 三種。對於 Spring Boot 后端 API 項目,建議采用 httpGet 檢測接口的方式,服務提供特定的健康檢測接口,如果服務正常則返回 200 狀態碼,一旦檢測到非 200 則會觸發自動重啟機制。K8S 健康監測配置示例如下:

 livenessProbe: # 是否存活檢測
    failureThreshold: 3
    httpGet:
      path: /api/healthz
      port: 8080
      scheme: HTTP
    initialDelaySeconds: 300
    periodSeconds: 60
    successThreshold: 1
    timeoutSeconds: 2
  readinessProbe: # 是否就緒檢測
    failureThreshold: 1
    httpGet:
      path: /api/healthz
      port: 8080
      scheme: HTTP
    periodSeconds: 5
    successThreshold: 1
    timeoutSeconds: 2 

其中一些參數的作用,這里就不解釋了,可以參照 官網文檔 來了解。

6、服務 CPU & Mem 請求/最大值配置

K8S 在部署 Deployment 時,可以為每個容器配置最小及最大 CPU & Mem 資源限制,這個是很有必要的,因為不配置資源限制的話,那么默認該容器服務可以無限制使用系統資源,這樣如果服務異常阻塞或其他原因,導致占用系統資源過多而影響其他服務的運行,同時 K8S 集群資源不足時,會優先干掉那些沒有配置資源限制的服務。當然,請求資源量和最大資源量要根據服務啟動實際需要來配置,如果不清楚需要配置多少,可以先將服務部署到 K8S 集群中,看正常調用時監控頁面顯示的請求值,在合理配置。

resources:
  limits:
    cpu: "1000m"
    memory: "1024Mi"
  requests:
    cpu: "500m"
    memory: "512Mi"

7、K8S 集群部署其它注意事項

7.1、部署前的一些准備工作

K8S 在部署服務前,需要做一些准備工作,例如提前創建好對應的 Namespace,避免首次直接創建 Deployment 出現 Namespace 不存在而創建失敗。如果我們使用的私有鏡像倉庫,那么還需要生成 Docker Repository 登錄認證 Secret,用來注入到 Pod 內拉取鏡像時認證需要。

# 包含登錄認證信息的 Secret
apiVersion: v1
kind: Secret
metadata:
  name: docker-regsecret
type: kubernetes.io/dockerconfigjson
data:
  .dockerconfigjson: InN1bmRhbmRhbjE4MDUyOEBjcmVkaXRoYdfe3JhdXRocyI6eyJyZWdpc3RyeS1pb

# Deployment 中注入該 Secret
	imagePullSecrets:
    - name: docker-regsecret

Secret 的生成方式可參考 官網文檔

7.2、靈活使用 ConfigMap 資源類型

K8S 提供 ConfigMap 資源類型來方便靈活控制配置信息,我們可以將服務需要的一些 ENV 信息或者配置信息放到 ConfigMap 中,然后注入到 Pod 中即可使用,非常方便。ConfigMap 使用方式有很多種,這里建議大家可以將一些經常更改的配置放到 ConfigMap 中,例如我在實際操作中,就發現有的項目 nginx.conf 配置,還有配置的 ENV 環境變量信息經常變動,那么就可以放在 ConfigMap 中配置,這樣 Deployment 就不需要重新部署了。

# 包含 nginx.conf 配置的 ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-conf-configmap
  data:
    www.conf: |
      server {
        listen 80;
        server_name 127.0.0.1

        root /opt/project/nginx/html/;
        error_page 405 =200 $uri;

        access_log  /opt/project/nginx/logs/http_accesss.log  main;
        error_log   /opt/project/nginx/logs/http_error.log;
      }

# 將 ConfigMap 掛載到容器指定目錄
volumes:
- name: nginx-config
  configMap:
    defaultMode: 420
    name: nginx-conf-configmap

這里有一個使用 ConfigMap 優雅加載 Spring Boot 配置文件實現方式的示例,可以參考 這里

7.3、Deployment 資源部署副本數及滾動更新策略

K8S 建議使用 Deployment 資源類型啟動服務,使用 Deployment 可以很方便的進行滾動更新、擴縮容/比例擴容、回滾、以及查看更新版本歷史記錄等。所以建議副本數至少 2 個,保證服務的可用性,要根據服務實際訪問量,來合理配置副本數,過多造成資源浪費,過少造成服務負荷高響應慢的問題,當然也可以根據服務訪問量,靈活擴縮容副本數。

Deployment 更新策略有 RecreateRollingUpdate 兩種,Recreate 方式在創建出新的 Pod 之前會先殺掉所有已存在的 Pod,這種方式不友好,會存在服務中斷,中斷的時間長短取決於新 Pod 的啟動就緒時間。RollingUpdate 滾動更新方式,通過配合指定 maxUnavailablemaxSurge 參數來控制更新過程,使用該策略更新時會新啟動 replicas 數量的 Pod,新 Pod 啟動完畢后,在干掉舊 Pod,如果更新過程中,新 Pod 啟動失敗,舊 Pod 依舊可以提供服務,直到啟動完成,服務才會切到新 Pod,保證服務不會中斷,建議使用該策略。

replicas: 2
strategy:
  rollingUpdate:
    maxSurge: 1  #也可以按比例配置,例如:20%
    maxUnavailable: 0 #也可以按比例配置,例如:20%
  type: RollingUpdate

7.4、要保證 K8S 資源 CPU & Mem & Disk 資源夠用

要時刻關注 K8S 集群資源使用情況,保證系統資源夠集群使用,否則會出現因為 CPU 、Mem、Disk 不夠用導致 Deployment 調度失敗的情況。

7.5、K8S 集群配置項優化

K8S 集群創建每個 Namespaces 時默認會創建一個名稱為 default 的 ServiceAccount,該 ServiceAccount 包含了名稱為 default-token-xxxx 的 Secret,該 Secret 包含集群 api-server 使用的根 CA 證書以及認證用的令牌 Token,而且默認新創建 Pod 時會自動將該 ServiceAccount 包含的信息自動注入到 Pod 中,在 Pod 中可以直接使用這些認證信息連接集群執行 api 相關操作,這樣會存在一定的風險,所以建議使用 automountServiceAccountToken: false 配置來關閉自動注入。

另一個配置 progressDeadlineSeconds,該配置用來指定在升級或部署時,由於各種原因導致卡住(還沒有表明升級或部署失敗),等待的 deadline 秒數,如果超過該 deadline 時間,那么將上報並標注 Deployment 狀態為 False 並注明失敗原因,然后 Deployment 繼續執行后續操作。默認為 600 秒,如果覺得改時間太長,可以按照可接受的時間來修改配置,例如配置為 120 秒 progressDeadlineSeconds: 120


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM