透過skaffold快速部署微服務

zaoying發表於2023-02-26

透過skaffold快速部署微服務

隨著技術的不斷髮展,程式設計師們熟悉的傳統單體應用開發流程,漸漸地無法適應當下微服務化的潮流趨勢。同時隨著雲原生開發的理念不斷推廣,越來越多的服務執行在不可變的基礎設施之上,隨之而來的是傳統單體應用開發流程與雲化程度日益加深服務之間的隔閡越發巨大,開發人員越來越難以容忍重複繁瑣且容易出錯的低效率開發流程。因此,一款面向開發人員而運維實施人員的持續構建與持續部署工具 skaffold 應運而生

skaffold簡介

[skaffold]() 是一款 Google 推出的持續構建與持續部署工具,它主要面向開發人員而非運維實施人員,目標是打破本地開發與雲化部署之間的隔閡,減輕開發人員的心智負擔,幫助開發人員專注於出色地完成日常開發工作,避免開發人員在紛亂繁雜的運維流程中過多消耗寶貴的精力與時間。

基本架構

skaffold 的工作流按照開發流程的不同階段,分為4個部分組成:

  1. 本地開發(檔案同步)
  2. 持續構建
  3. 持續測試
  4. 持續部署

以上四個部分均可以根據實際需求進行定製化修改。

本地開發

skaffold 對主流的程式語言以及配套使用的技術棧都有著非常不錯的支援,例如 GoJavaJavaScript

本地開發的核心內容是 檔案同步,檔案同步的監聽物件大概可以分為 原始碼編譯產物

skaffold 官方的推薦做法是監聽原始碼變動,然後自動化把原始碼複製到Docker容器中進行編譯和構建。

這種做法的問題不少,首先是原始碼變動非常頻繁,而編譯和構建過程往往非常耗時,因此自動觸發構建不太合理。

其次,在Docker容器中編譯和構建,需要掌握編寫 Multi Stage Dockerfile 技能,否則構建出來的映象大小會佔據非常大的空間,另外還要消耗本就不寬裕的頻寬進行映象傳輸。

最後,在Docker容器中編譯和構建,要解決環境變數,代理設定、快取構建中間結果等一系列問題,對新手非常不友好。

因此,個人推薦,在本地開發環節儘量採用手動觸發編譯構建,透過監聽編譯產物的方式來觸發熱更新等流程。

持續構建

因為選擇手動觸發編譯,所以本環節的內容主要講述如何打包映象的內容

目前 skaffold 官方支援的構建方式有三種:DockerJib(maven/gradle)Bazel

  • Docker
  • Jib(maven/gradle)
  • Bazel

這裡以最常見 Docker 為例:

build:
  local:
    push: false                        # 映象打包成功後是否推送到遠端的映象倉庫
  artifacts:                           # 支援打包多個不同元件的映象
    - image: datacenter-eureka         # 打包後的映象名稱
      context: "eureka"                # Dockerfile相對路徑,就放在eureka目錄下
      docker:
        dockerfile: Dockerfile
    - image: datacenter-school         # 打包後的映象名稱
      context: "school"                # Dockerfile相對路徑
      docker:
        dockerfile: Dockerfile
    - image: datacenter-teacher        # 打包後的映象名稱
      context: "teacher"               # Dockerfile相對路徑
      docker:
        dockerfile: Dockerfile
    - image: datacenter-student        # 打包後的映象名稱
      context: "student"               # Dockerfile相對路徑
      docker:
        dockerfile: Dockerfile

當執行 skaffold dev 時,會按照 編譯 —> 構建 -> 測試 -> 部署 的標準流程走一遍。
當監聽到指定路徑下的檔案發生變化時,skaffold工具會嘗試透過類似於 kubectl cp 命令的方式,直接把產生變化後的檔案複製到執行中的容器內部,避免重新走一遍編譯構建/上傳映象的步驟,減少同步程式碼更改而消耗的時間。

需要特別注意,這種方式對於支援 程式碼熱更新 的技術棧非常實用,例如 JavaJavascript,但對於 Go 這類不支援 熱更新 的技術棧來說效果十分有限,因為即便檔案同步完成,依然重啟主程式才能讓修改後的功能生效。

接著說 Jib(maven/gradle)Jib 也是由谷歌開發的一款專門針對 Java 生態的 CI/CD 工具,跟 skaffold 通用 CI/CD 不同,同時還有 VSCodeIDEA 外掛, 但作者本人並沒有用過,所以等以後有機會再展開講。

至於 Bazel 是微軟開發的全平臺構建工具,主要支援 C# 語言,甚至連前端相關 Javascript 專案也可以使用,但缺點就是非常笨重,這裡也不展開講。

此外, skaffold 還支援 Customize 自定義構建,這個方式的構建更自由可控,比如有些 WSL 環境的使用者不願意為了安裝 Docker 環境而反覆折騰,甚至有些企業內部不允許員工在開發電腦安裝虛擬機器等等,透過自定義構建流程都可以解決,放到文章最後再詳細講解。

持續測試

skaffold 官方的測試方案是把程式碼複製到定製化的測試環境容器中執行測試用例,這種方法非常麻煩,測試相關的內容這裡就不展開講。
感興趣的讀者可以檢視 skaffold官方配置文件

持續部署

skaffold 官方支援的部署方式有很多種,這裡以 helm 為例:

deploy:
  helm:
    releases:
    - name: datacenter
      chartPath: package
      artifactOverrides:
        image:
          eureka: datacenter-eureka      # 映象名稱要跟前面構建環節的映象名稱保持一致,但不能出現映象標籤
          school: datacenter-school      # 映象名稱要跟前面構建環節的映象名稱保持一致,但不能出現映象標籤
          teacher: datacenter-teacher    # 映象名稱要跟前面構建環節的映象名稱保持一致,但不能出現映象標籤
          student: datacenter-student    # 映象名稱要跟前面構建環節的映象名稱保持一致,但不能出現映象標籤
      imageStrategy:
        helm: {}

配置參考

完整的配置檔案可以參考:datacenter

上手實踐

前期準備

點選上述兩個元件的連結,下載到本地,再講二進位制加入 PATH 環境變數,詳細安裝過程不再贅述。

基本開發環境配置

JDKmavenGradle 這類Java開發必備的工具請自行安裝,這裡就不展開講了。

初始化helm chart

在一個空白目錄下執行 helm create datacenter 命令,即可快速建立 chart 包,包的目錄結構如下所示:

├── Chart.yaml
├── charts
├── templates
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   ├── hpa.yaml
│   ├── ingress.yaml
│   ├── service.yaml
│   ├── serviceaccount.yaml
│   └── tests
│       └── test-connection.yaml
└── values.yaml

可以根據自身的實際需求,增刪修改包的檔案內容,例如這裡用不到 hpa.yamlserviceaccount.yamltests/* ,所以都刪除了。

然後把 datacenter 重新命名為 package ,然後移動到原本的程式碼目錄下,這是約定成俗的習慣。

部署MySQL服務

經典的Web應用往往離不開資料庫,而在k8s上執行資料庫,則需要提供持久化儲存,否則資料庫的容器重啟後資料就丟失了。

首先,在 package/templates 目錄下建立 pv.yaml 檔案,然後寫入以下內容:

kind: PersistentVolume
apiVersion: v1
metadata:
  name: mysql-pv-volume
  namespace: spring-cloud
  labels:
    type: local
spec:
  storageClassName: manual
  capacity:
    storage: 2Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/opt/data/mysql"

解釋:建立持久化卷 PersistentVolume ,簡稱 PV,儲存路徑就用宿主機目錄 /opt/data/mysql

然後,在同一個目錄下建立 pvc.yaml 檔案,然後寫入以下內容:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pv-claim
  namespace: spring-cloud
spec:
  storageClassName: manual
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 2Gi

解釋:建立持久卷使用宣告 PersistentVolumeClaim ,簡稱 PVC,繫結前面建立的 PV
PVC 的容量是 2G ,必須小或等於 PV 的容量,否則無法繫結,可以根據實際情況調整容量大小。

為了防止解除安裝過程中意外刪除PV卷導致資料丟失的情況,helm不會執行刪除 PV 的操作,必須要使用者手動執行。
因此如果部署過程出現PVC與PV無法繫結而導致無法繼續的情況,請手動刪除再重新PV以及PVC的方式排除故障

最後,在同一個目錄下建立 statefulset.yaml 檔案,然後寫入以下內容:

apiVersion: v1
kind: Service
metadata:
  name: mysql                  # 同一個名稱空間的其他服務可以透過域名 “mysql” 來訪問 MySQL 服務
  namespace: spring-cloud      # 透過名稱空間來隔離不同的專案
spec:
  type: ClusterIP
  ports:
  - name: mysql
    protocol: TCP
    port: 3306
    targetPort: 3306
  selector:
    app: mysql                 # 透過定義標籤選擇器,只轉發請求給帶有 app: mysql 的Pod
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  namespace: spring-cloud
spec:
  selector:
    matchLabels:
      app: mysql
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - image: mysql:5.6
        name: mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: bocloud@2019    # 密碼屬於高度敏感機密,建議在生成環境中透過 ServiceAccount 和 ConfigMap 等方式注入
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: mysql-persistent-storage
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql-persistent-storage
        persistentVolumeClaim:
          claimName: mysql-pv-claim   # 將前面定義的pvc掛載成卷,給容器使用

解釋:建立 mysql 的 ServiceStatefulSet。由於 mysql 是個資料庫,屬於 有狀態應用 ,所以建議使用 StatefulSet 來管理。
另外由於 k8s 的機制問題,Pod 重啟後IP地址會改變,所以Pod之間的通訊不適合直接透過訪問 Pod IP 的方式進行,最佳實踐是透過建立特定 Service ,請求方的 Pod 向特定 Service 傳送請求,再由特定 Service 轉發請求給被請求方的 Pod。

部署微服務

package/templates 目錄下清空 deployment.yaml 檔案,然後寫入以下內容:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: datacenter-dep
  namespace: spring-cloud      # 同一個專案的名稱空間一定要相同
  labels:
    app: datacenter            # 自定義標籤
spec:
  replicas: 1                       # 副本數量
  selector:
    matchLabels:
      app: datacenter               # 透過統計有多少個帶有app: datacenter的Pod來確定副本的數量
  template:
    metadata:
      labels:
        app: datacenter             # 給Pod打上app: datacenter標籤,方便統計
    spec:
      containers:
      - name: school
        image: {{.Values.image.school.repository}}:{{.Values.image.school.tag}} # 注入真正的映象
        imagePullPolicy: IfNotPresent
        env:
        - name: MYSQL_ADDRESS
          value: mysql:3306
        - name: MYSQL_PASSWORD
          value: bocloud@2019           # 密碼屬於高度敏感機密,不建議在真實環境使用明文密碼,這裡僅為展示
        ports:
        - containerPort: 8084
      - name: teacher
        image: {{.Values.image.teacher.repository}}:{{.Values.image.teacher.tag}} # 注入真正的映象
        imagePullPolicy: IfNotPresent
        env:
        - name: MYSQL_ADDRESS
          value: mysql:3306
        - name: MYSQL_PASSWORD
          value: bocloud@2019           # 密碼屬於高度敏感機密,不建議在真實環境使用明文密碼,這裡僅為展示
        ports:
        - containerPort: 8082
      - name: student
        image: {{.Values.image.student.repository}}:{{.Values.image.student.tag}} # 注入真正的映象
        imagePullPolicy: IfNotPresent
        env:
        - name: MYSQL_ADDRESS
          value: mysql:3306
        - name: MYSQL_PASSWORD
          value: bocloud@2019           # 密碼屬於高度敏感機密,不建議在真實環境使用明文密碼,這裡僅為展示
        ports:
        - containerPort: 8083

解釋:根據 k8s 的規範要求,應該透過 Deployment 來部署 無狀態應用

然後,在同一個目錄下清空 service.yaml 檔案,然後寫入以下內容:

apiVersion: v1
kind: Service
metadata:
  name: datacenter
  namespace: spring-cloud
spec:
  type: ClusterIP
  selector:
    app: datacenter
  ports:
    - name: school
      protocol: TCP
      port: 8084
      targetPort: 8084
    - name: teacher
      protocol: TCP
      port: 8082
      targetPort: 8082
    - name: student
      protocol: TCP
      port: 8083
      targetPort: 8083

解釋:建立 Service 暴露到叢集內部,供叢集內部的其他服務呼叫

最後,修改 package/values.yaml 檔案,然後寫入以下內容:

image:
  school:
    repository: datacenter-school
    tag: latest
  teacher:
    repository: datacenter-teacher
    tag: latest
  student:
    repository: datacenter-student
    tag: latest

解釋:helm 推薦透過 values.yaml 檔案統一管理 chart 模板的變數。
skaffold 也是透過修改 values.yaml 注入不同的映象名稱,動態更新執行中容器映象

安裝配置kubectl和docker

安裝kubectl與k8s通訊

版本:v1.23.0

點選上述連結,下載到本地,再講二進位制加入 PATH 環境變數,詳細安裝過程不再贅述。

K8S配置 一般儲存在主節點的 ~/.kube 目錄下,將完整目錄複製到本地目錄下,開啟目錄下的 .kube/config,可以類似的內容如下:

apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: "****"  # 證照頒發機構的證照
    server: https://172.24.86.22:6443   # k8s的apiserver地址
  name: kubernetes                      # k8s叢集的名稱
contexts:
- context:
    cluster: kubernetes                        # 上下文的k8s叢集的名稱
    user: kubernetes-admin                     # 上下文的k8s憑證的使用者名稱稱
  name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes   # 設定當前上下文的所使用的k8s叢集
kind: Config
preferences: {}
users:
- name: kubernetes-admin             # k8s憑證的使用者名稱稱
  user:
    client-certificate-data: "****"  # 使用者證照
    client-key-data: "****"          # 使用者金鑰

然後設定環境變數 KUBECONFIG 指向本地的 ./kube/config 路徑,kubectl 便可以透過憑證與 k8sAPIServer 通訊。
修改環境變數後,記得執行命令更新環境變數,Windows平臺執行 refreshenv 命令。

安裝 docker 用於打包映象
如果你的本地電腦環境存在docker環境,可以跳過docker安裝配置環節
安裝Docker客戶端

點選這裡
下載到本地,再講二進位制加入 PATH 環境變數,詳細安裝過程不再贅述。

配置Docker

假設你的WSL環境中存在 Docker 環境,又或者遠端Linux伺服器上存在 Docker 環境,
可以透過修改 Docker 守護程式的配置,將 Docker 程式暴露到內網供其他裝置進行使用。

首先,編輯 /etc/systemd/system/multi-user.target.wants/docker.service 檔案,將 ExecStart 行改成以下內容:

ExecStart=/usr/bin/dockerd --containerd=/run/containerd/containerd.sock

重點是去掉 fd:// ,接著編輯 /etc/docker/daemon.json 檔案,重點是 hosts 加上 fd://tcp://0.0.0.0:10086

注意事項:升級docker後會覆蓋當前設定,導致docker無法正常執行,需要參考上述步驟重新設定 docker.service 檔案才能正常執行

埠號可以根據實際情況調整

{
  "hosts": [
    "fd://",
    "tcp://0.0.0.0:10086"
  ],
  "registry-mirrors": [
    "https://docker.mirrors.ustc.edu.cn/",
    "https://dockerhub.azk8s.cn/",
    "https://hub-mirror.c.163.com/"
  ],
  "exec-opts": ["native.cgroupdriver=systemd"],
    "log-driver": "json-file",
    "log-opts": {
      "max-size": "100m"
  },
  "storage-driver": "overlay2"
}

介面執行以下命令,重啟 docker

systemctl daemon-reload
systemctl restart docker

最後,在本地設定環境變數 DOCKER_HOST=<eth0IP>:10086 ,把 <eth0IP> 換成遠端Linux伺服器的真實IP。

由於WSL每次重啟eth0的IP會變化,需要重新設定 DOCKER_HOST 變數

在本地命令列介面,執行 docker info 命令檢查是否設定成功。

C:\WINDOWS\system32>docker info
Client:
 Context:    default
 Debug Mode: false
 Plugins:
  buildx: Docker Buildx (Docker Inc., v0.7.1)
  compose: Docker Compose (Docker Inc., v2.2.1)
  scan: Docker Scan (Docker Inc., 0.9.0)

Server:
 Containers: 0
  Running: 0
  Paused: 0
  Stopped: 0
 Images: 30
 Server Version: 20.10.12
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Native Overlay Diff: true
  userxattr: false
 Logging Driver: json-file
 Cgroup Driver: systemd
 Cgroup Version: 1
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
 Swarm: inactive
 Runtimes: io.containerd.runc.v2 io.containerd.runtime.v1.linux runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: 7b11cfaabd73bb80907dd23182b9347b4245eb5d
 runc version: v1.0.2-0-g52b36a2
 init version: de40ad0
 Security Options:
  seccomp
   Profile: default
 Kernel Version: 5.10.74.3-microsoft-standard-WSL2+
 Operating System: Ubuntu 20.04.3 LTS
 OSType: linux
 Architecture: x86_64
 CPUs: 8
 Total Memory: 7.239GiB
 Name: LAPTOP-MAGICBOOKPRO
 ID: EXNQ:FGLE:MROB:C7FG:WJXC:R7YV:HUFB:6A46:4KAW:LG2A:TM3J:SAAB
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 Registry: https://index.docker.io/v1/
 Labels:
 Experimental: false
 Insecure Registries:
  127.0.0.0/8
 Registry Mirrors:
  https://docker.mirrors.ustc.edu.cn/
  https://dockerhub.azk8s.cn/
  https://hub-mirror.c.163.com/
 Live Restore Enabled: false

填寫skaffold配置並執行

在專案根目錄下,建立 skaffold.yaml 檔案,填寫以下內容

apiVersion: skaffold/v2beta26                     # version of the configuration.
kind: Config                                     # always Config.
metadata:
  name: datacenter
build:
  local:
    push: false
  artifacts:
    - image: datacenter-school         # must match in artifactOverrides
      context: "school"
      docker:
        dockerfile: Dockerfile
    - image: datacenter-teacher        # must match in artifactOverrides
      context: "teacher"
      docker:
        dockerfile: Dockerfile
    - image: datacenter-student        # must match in artifactOverrides
      context: "student"
      docker:
        dockerfile: Dockerfile
deploy:
  helm:
    releases:
    - name: datacenter
      chartPath: package
      artifactOverrides:
        image:
          school: datacenter-school               # no tag present!
          teacher: datacenter-teacher               # no tag present!
          student: datacenter-student               # no tag present!
      imageStrategy:
        helm: {}

先執行以下命令建立 spring-cloud 名稱空間,k8s 透過名稱空間來隔離不同微服務的資源

kubectl create namespace spring-cloud

再執行 skaffold dev 命令,如果前面的步驟和配置都正確,應該可以看到以下輸出

C:\Users\huang\Documents\datacenter>skaffold dev
time="2022-02-14T00:13:29+08:00" level=warning msg="failed to detect active kubernetes cluter node platform. Specify the correct build platform in the `skaffold.yaml` file or using the `--platform` flag" subtask=-1 task=DevLoop
Listing files to watch...
 - datacenter-eureka
 - datacenter-school
 - datacenter-teacher
 - datacenter-student
Generating tags...
 - datacenter-eureka -> datacenter-eureka:13577a5
 - datacenter-school -> datacenter-school:13577a5
 - datacenter-teacher -> datacenter-teacher:13577a5
 - datacenter-student -> datacenter-student:13577a5
Checking cache...
 - datacenter-eureka: Found. Tagging
 - datacenter-school: Found. Tagging
 - datacenter-teacher: Found. Tagging
 - datacenter-student: Found. Tagging
Tags used in deployment:
 - datacenter-eureka -> datacenter-eureka:769afdbaaf2a35acada2a56cf1d1cccbc8a8ab8196396a8ff9e2803cf6a49490
 - datacenter-school -> datacenter-school:b89167e724932d41e40945a39ff04d84e419345957c4c0a022e7c4694153b609
 - datacenter-teacher -> datacenter-teacher:9d013f9295b7bd3e75b68b2d8a9df434a77cbc9514df1ae36a967b6841c4328f
 - datacenter-student -> datacenter-student:3f5267479ce35cec929485edce5f1dfc2cb1017136bbc90f2a0de5cd4f48f808
Starting deploy...

Ctrl+C 即可停止服務,如果 kubernetes 叢集中依舊存在 datacenter 相關的資源,可以透過 helm uninstall datacenter 手動清除。

[可選]使用Buildah代替Docker

對於 WSL1 或者 嫌棄在 WSL2 安裝 docker 環境太麻煩的 windows 使用者,以及不想在本地安裝 dockerMac 使用者,
可以嘗試安裝 redhat 開源的 buildah

按照官方教程自行安裝即可。
安裝結束後執行 buildah image, 若遇到以下錯誤:

kernel does not support overlay fs: 'overlay' is not supported over <unknown> at "/home/zaoying/.local/share/containers/storage/overlay": backing file system is unsupported for this graph driver
WARN[0000] failed to shutdown storage: "kernel does not support overlay fs: 'overlay' is not supported over <unknown> at \"/home/zaoying/.local/share/containers/storage/overlay\": backing file system is unsupported for this graph driver"
ERRO[0000] exit status 125

只需要安裝 fuse-overlayfs 即可:

# for debian/ubuntu
apt install fuse-overlayfs

buildah 基於 fork 模型,不需要 daemon 守護程式,因此不依賴於 systemd ,不需要root許可權即可執行。
安裝完後即可使用,不需要額外的配置。但 skaffold 尚未提供 buildah 官方支援,因此需要自定義構建指令碼。

apiVersion: skaffold/v2beta26                     # version of the configuration.
kind: Config                                     # always Config.
metadata:
 name: datacenter
build:
 local:
   push: false
 artifacts:
   - image: datacenter-school         # must match in artifactOverrides
     context: "school"
     custom:
       buildCommand: |
         buildah bud -t $IMAGE -f .
   - image: datacenter-teacher        # must match in artifactOverrides
     context: "teacher"
     custom:
       buildCommand: |
         buildah bud -t $IMAGE -f .
   - image: datacenter-student        # must match in artifactOverrides
     context: "student"
     custom:
       buildCommand: |
         buildah bud -t $IMAGE -f .
deploy:
 helm:
   releases:
   - name: datacenter
     chartPath: package
     artifactOverrides:
       image:
         school: datacenter-school               # no tag present!
         teacher: datacenter-teacher               # no tag present!
         student: datacenter-student               # no tag present!
     imageStrategy:
       helm: {}

更多詳細的自定義構建器幫助,請檢視官方文件

[可選]程式碼熱更新

程式碼熱更新在日常的開發過程非常實用,可以加快特性開發與功能驗證的效率。

skaffold 可以解析 Dockerfile ,根據 COPYADD 等指令,自動選擇監聽和同步哪些檔案。

當修改完程式碼後,手動執行 mvn clean & mvn package 命令,skaffold 監聽jar包變動,自動重新打包映象並替換。

整個過程算不上真正意思上的熱更新,主要的原因是 spring boot 透過 jar 包部署,每次只做了很小的改動都需要重新打包映象,耗費非常多的時間。

如果改為 exploded war 的方式部署,就可以實現 class 粒度的 熱更新

直接跳過 mvn package 打包環節,直接將編譯的中間產物 .class 位元組碼檔案同步到執行中的容器中,從而在不重啟容器的前提下實現程式碼熱更新。

理論上來說,skaffold 的程式碼熱更新功能同時適用於 JavaJavascript 等技術棧。但限於篇幅,本文僅限於Java。

首先要改造 pom.xml 配置,把 <package>jar</package> 改成 <package>war</package>

    <packaging>war</packaging>

然後加上 spring-boot-starter-tomcatscope 改為 provided,以及增加 start-class 屬性,填寫全路徑的啟動類

  ...
    <properties>
    ...
        <start-class>com.springcloud.eureka.SchoolApplication</start-class>
    ...
    </properties>
  ...
  <dependencys>
    ...
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
      <scope>provided</scope>
    </dependency>
    ...
  </dependencys>

開啟啟動類 SpringApplication.java ,增加 extends SpringBootServletInitializer 並過載 configure 方法

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class SchoolApplication extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
       return application.sources(SchoolApplication.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(SchoolApplication.class, args);
    }
}

接著,修改 Dockerfile, 如果你不願意在生產環境採用 war ,這裡可以另存為 Dockerfile.dev

FROM tomcat:9.0.62-jre11-temurin-focal
RUN rm -rf /usr/local/tomcat/webapps.dist
# 透過修改server.xml的方式修改埠
RUN sed -i 's/redirectPort="8443"//' /usr/local/tomcat/conf/server.xml && sed -i 's/8005/8004/' /usr/local/tomcat/conf/server.xml && sed -i 's/8080/8084/' /usr/local/tomcat/conf/server.xml
COPY target/exploded /usr/local/tomcat/webapps/ROOT
COPY target/classes /usr/local/tomcat/webapps/ROOT/WEB-INF/classes
CMD ["catalina.sh", "run"]

以上操作都是針對單個服務,因此每個服務都要重複一遍上述操作,但以下操作則是針對整體服務。

最後修改 skaffold.yaml ,增加自定義構建指令碼

apiVersion: skaffold/v2beta26                     # version of the configuration.
kind: Config                                     # always Config.
metadata:
  name: datacenter
build:
  local:
    push: false
  artifacts:
    - image: datacenter-eureka         # must match in artifactOverrides
      context: "eureka"
      custom:
        buildCommand: |
          mvn clean package && 7z x target/eureka-0.0.1-SNAPSHOT.war -otarget/exploded && docker build -t %IMAGE% -f Dockerfile.dev %BUILD_CONTEXT%
        dependencies:
          paths:
          - target/classes
          - Dockerfile.dev
          ignore:
          - target/exploded
    - image: datacenter-school         # must match in artifactOverrides
      context: "school"
      custom:
        buildCommand: |
          mvn clean package && 7z x target/school-0.0.1-SNAPSHOT.war -otarget/exploded && docker build -t %IMAGE% -f Dockerfile.dev %BUILD_CONTEXT%
        dependencies:
          paths:
          - target/classes
          - Dockerfile.dev
          ignore:
          - target/exploded
    - image: datacenter-teacher        # must match in artifactOverrides
      context: "teacher"
      custom:
        buildCommand: |
          mvn clean package && 7z x target/teacher-0.0.1-SNAPSHOT.war -otarget/exploded && docker build -t %IMAGE% -f Dockerfile.dev %BUILD_CONTEXT%
        dependencies:
          paths:
          - target/classes
          - Dockerfile.dev
          ignore:
          - target/exploded
    - image: datacenter-student        # must match in artifactOverrides
      context: "student"
      custom:
        buildCommand: |
          mvn clean package && 7z x target/student-0.0.1-SNAPSHOT.war -otarget/exploded && docker build -t %IMAGE% -f Dockerfile.dev %BUILD_CONTEXT%
        dependencies:
          paths:
          - target/classes
          - Dockerfile.dev
          ignore:
          - target/exploded
deploy:
  helm:
    releases:
    - name: datacenter
      chartPath: package
      artifactOverrides:
        image:
          eureka: datacenter-eureka               # no tag present!
          school: datacenter-school               # no tag present!
          teacher: datacenter-teacher               # no tag present!
          student: datacenter-student               # no tag present!
      imageStrategy:
        helm: {}

圖中所展示的 buildCommand 是在 Windows 平臺執行的,如果是 LinuxMacOS,請參考以下命令

# 打包eureka
mvn clean package && unzip target/eureka-0.0.1-SNAPSHOT.war -o target/exploded && docker build -t $IMAGE -f Dockerfile.dev $BUILD_CONTEXT

# 打包school
mvn clean package && unzip target/school-0.0.1-SNAPSHOT.war -o target/exploded && docker build -t $IMAGE -f Dockerfile.dev $BUILD_CONTEXT

# 打包teacher
mvn clean package && unzip target/teacher-0.0.1-SNAPSHOT.war -o target/exploded && docker build -t $IMAGE -f Dockerfile.dev $BUILD_CONTEXT

# 打包student
mvn clean package && unzip target/student-0.0.1-SNAPSHOT.war -o target/exploded && docker build -t $IMAGE -f Dockerfile.dev $BUILD_CONTEXT

僅第一次執行需要打包 war 再解壓的操作,後續可透過 mvn compile 即可以 class 為粒度實現 熱更新

總結

本次演示所使用的微服務專案是很多年前筆者為了學習 Spring Cloud 而編寫的 Demo
時隔多年 Spring Cloud 已經不再推薦 Eureka 作為服務發現與註冊中心。
同時 k8s 本身也支援將 CoreDNS 作為服務發現/註冊的元件使用。
所以讀者不必糾結 Demo 程式碼中的錯誤,因為本文的重點是 skaffold 的配置與使用。

相關文章