轉載自: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 + Jdk
、Centos + Jdk + Tomcat
、Centos + 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、服務日志輸出處理
對於日志處理,之前我們一般會使用 Log4j
或 Logstash
等日志框架將日志輸出到服務器指定目錄,容器化部署后,日志會生成到容器內某個配置的目錄上,外部是沒法訪問的,所以需要將容器內日志掛載到宿主機某個目錄 (例如:/opt/logs
目錄),這樣方便直接查看,或者配置 Filebeat
、Fluent
等工具抓取到 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、配置容器服務暴露目標端口
首先需要提供容器服務需要暴露的目標端口號,例如 Http
、Https
、Grpc
等服務端口,創建 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 暴露服務類型有三種:ClusterIP
、NodePort
、LoadBalancer
,三種類型分別有不同的應用場景。
- 對內服務發現,可以使用
ClusterIP
方式對內暴露服務,因為存在 Service 重新創建 IP 會更改的情況,所以不建議直接使用分配的ClusterIP
方式來內部訪問,可以使用 K8S DNS 方式解析,DNS 命名規則為:<svc_name>.<namespace_name>.svc.cluster.local
,按照該方式可以直接在集群內部訪問對應服務。 - 對外服務暴露,可以采用
NodePort
、LoadBalancer
方式對外暴露服務,NodePort
方式使用集群固定IP
,但是端口號是指定范圍內隨機選擇的,每次更新 Service 該Port
就會更改,不太方便,當然也可以指定固定的NodePort
,但是需要自己維護Port
列表,也不方便。LoadBalancer
方式使用集群固定IP
和NodePort
,會額外申請申請一個負載均衡器來轉發到對應服務,但是需要底層平台支撐。如果使用Aliyun
、GCE
等雲平台商,可以使用該種方式,他們底層會提供LoadBalancer
支持,直接使用非常方便。
以上方式或多或少都會存在一定的局限性,所以建議如果在公有雲上運行,可以使用 LoadBalancer
、 Ingress
方式對外提供服務,私有雲的話,可以使用 Ingress
通過域名解析來對外提供服務。Ingress
配置使用,可以參考 初試 Kubernetes 暴漏服務類型之 Nginx Ingress 和 初試 Kubernetes 集群中使用 Traefik 反向代理 文章。
5、服務健康監測配置
K8s 提供存活探針和就緒探針,來實時檢測服務的健康狀態,如果健康檢測失敗,則會自動重啟該 Pod 服務,檢測方式支持 exec
、httpGet
、tcpSocket
三種。對於 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 更新策略有 Recreate
和 RollingUpdate
兩種,Recreate
方式在創建出新的 Pod 之前會先殺掉所有已存在的 Pod,這種方式不友好,會存在服務中斷,中斷的時間長短取決於新 Pod 的啟動就緒時間。RollingUpdate
滾動更新方式,通過配合指定 maxUnavailable
和 maxSurge
參數來控制更新過程,使用該策略更新時會新啟動 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
。