Go 項目的簡單部署

概述

在上一篇筆記記錄了 Gin 實現簡單的註冊登錄和狀態管理。這一篇筆記來分享一下如何將上面的項目打包鏡像和部署,筆記分成三部分,分別是 Web 後端項目 Docker 鏡像的構建、使用 Docker 運行、使用 Docker Compose 和 k8s 部署容器。使用 Ingress 路由規則和 Web 前端的部署運行在下一篇筆記中記錄。

構建 Docker 鏡像

概述

構建 Docker 鏡像離不開 Dockerfile 文件。Dockerfile 是一個用來構建 Docker 鏡像的文本文件,文本內容包含構建鏡像所需的指令和說明;這些指令包括指定基礎鏡像、安裝依賴的軟件、配置環境變量、添加文件和目錄、定義容器啓動時運行的命令等。

我們根據上一個項目作爲例子,使用多階段構建的方式構建一個 Docker 鏡像,第一階段先將項目編譯爲可執行的二進制文件,第二階段運行可執行文件多階段構建可以減少鏡像的大小,每個階段只包含必要的依賴項和文件,提高構建的速度,並提高安全性。

最後可以使用 docker 指令構建鏡像,或使用make工具簡化構建 docker 鏡像的過程提高效率。make是可以用來構建和管理 Docker 鏡像的命令行工具,該工具可以組合不同的指令混合使用。在 Mac 環境下可以使用brew install make指令通過homebrew安裝make工具

思路

多階段構建的第一步是將項目輸出爲可執行文件。Go 項目輸出首先需要基於 Go 的環境,再使用go mod tidy監測並下載項目中缺失的依賴包(實際上 go mod tidy 的主要作用是清理項目中未使用的依賴,更新 go.mod 和 go.sum 文件;當執行go mod tidy時,go 工具鏈首先會掃描所有的 Go 文件,分析 import 聲明確認有效的依賴項;然後將 go.mod 中未被引用的依賴項刪除,對缺少的依賴嘗試添加,並更新 go.sum 文件)。依賴包都下載後執行go build -o main .指令將項目編譯成名爲main的可執行文件。

多階段構建的第二步要將該可執行文件運行起來。首先運行要基於一個操作系統,再將上一階段中項目的配置文件和編譯的可執行文件拷貝出來、暴露該服務的端口,最後執行該文件。

思路大致是這樣,在第一階段中使用golang:alpine作爲基礎鏡像並將結果命名爲buildergolang:apline是基於 Alpine Linux 操作系統的 Go 鏡像,Alpine LInux 是一個輕量級的 Linux 發行版,因此golang:apline鏡像通常比golang鏡像更小、更快。第二階段也使用了輕量級的alpine鏡像作爲基礎鏡像,並從第一階段的結果 (builder) 中拷貝可執行文件和配置文件。

最後使用docker build指令將項目打包成 docker 鏡像,再使用 make 工具幫助簡化構建 docker 鏡像過程。以下是完整的 Dockerfile 文件和構建過程。

步驟

1. 編寫 Dockerfile 文件

# 第一階段
FROM golang:alpine as builder
# 使用golang:alpine作爲基礎鏡像,並將該階段/結果命名爲builder
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
# 更換alpine linux的軟件源爲中科大的鏡像源
WORKDIR /app
# 設置工作目錄
COPY . .
# 拷貝所有文件到工作目錄中
ENV GOPROXY=https://goproxy.io
# 設置GOPROXY的環境變量
RUN go mod tidy
# 下載管理項目依賴
RUN go build -o main .
# 編譯當前目錄下的Go代碼,並將可執行文件命名爲main

# 第二階段
FROM alpine:3.10.0
# 使用alpine:3.10.0作爲基礎鏡像

WORKDIR /app
# 設置工作目錄 方便在容器中進行操作和管理文件
COPY --from=builder /app/main /app/main
COPY --from=builder /app/config/prod.config.yaml /app/config/config.yaml
# 從builder(第一階段)目錄中的/app/main 複製到當前目錄 /app/main
# 從builder目錄中的 /app/config/prod.config.yaml文件,複製到/app/config/目錄下,並將文件命名爲config.yaml

EXPOSE 8085
# 暴露端口8085

CMD ["/app/main","-env","prod"]
# 在容器內執行/app/main命令,並傳參 -env prod

2. 使用指令構建鏡像

在當前目錄下執行指令,-t參數用於指令鏡像名稱 (basic_project) 和標籤(v1), .表示當前目錄 (Dockerfile 所在目錄),也可以通過-f指定 Dockerfile 的路徑(.對於-f指定的 Dockerfile 而言,就是當前的相對路徑,基於當前目錄查找指定的 Dockerfile)。

docker build -t basic_project:v1 .
# or
docker build -f ./Dockerfile -t basic_project:v1 .

使用 make 工具簡化流程

makefile 文件中首先要定義一個僞目標,即.PHONY:xxx作爲一組指令的標籤,下面是指令的名稱。@的作用是不將執行的指令打印到屏幕中。使用 Make 工具就幾件事,重新構建鏡像時把之前的可執行文件刪掉,將之前的鏡像刪掉,重新構建鏡像。

.PHONY: docker
docker:
    # 清理上次編譯的可執行文件main,如果main不存在則返回一個錯誤,|| true會讓程序繼續往下運行
    @rm main || true

  # docker刪除鏡像 basic_project:v1
    @docker rmi -f basic_project:v1
    
    # docker構建鏡像
    @docker build -t basic_project:v1 .

.PHONY: clean
clean:
    @rm main || true
    @echo "clean success"

使用make docker指令構建鏡像

make docker

部署運行

使用 Docker 運行

要檢查項目的配置文件 Mysql 和 redis 的配置能不能對上,然後直接跑就是了 (修改了配置文件記得重新編譯和構建鏡像)。

name: "basicproject"
mode: "dev"
port: 8085
version: "v0.0.1"

mysql:
  host: "your_ip" // 你的電腦ip地址
  port: 3306
  user: "root"
  password: "12345678"
  dbname: "testdb"
  max_open_conns: 200
  max_idle_conns: 50

redis:
  host: "your_ip"  // 你的電腦ip地址
  port: 6379

使用 Docker Compose 運行

概述

Docker Compose 是 docker 自己用於定義和管理多個 Docker 容器服務的工具,通過 docker-compose.yaml 文件配置應用程序和創建容器服務。在 Docker compose 中可以定義多個服務,這些服務可以互相通信、共享網絡和存儲資源

思路

我們需要在 Docker-compose.yaml 文件中定義 3 個服務,後端服務basic_project, 數據庫MysqlRedis。因爲 Docker-compose 共享網絡的關係,可以將項目中config.yaml文件中的數據庫 ip 地址配置爲services中定義數據庫的名稱。修改完配置文件後要記得重新打包鏡像。

步驟

修改項目 config.yaml 配置文件

name: "basicproject"
mode: "prod"
port: 8085
version: "v0.0.1"

mysql:
  host: "db" # docker-compose.yaml文件中services中定義的db
  port: 3306
  user: "root"
  password: "12345678"
  dbname: "testdb"
  max_open_conns: 200
  max_idle_conns: 50

redis: # docker-compose.yaml文件中 services中定義的redis
  host: "redis"
  port: 6379

編寫 Docker-compose.yaml 文件

version: '3.7' # Docker Compose版本號 3.7
services: # 定義服務
  db: #定義MySQL服務
    image: 'mysql:8.0' # 指定鏡像文件
    container_name: 'basic-project-mysql' # 容器名稱
    command: --default-authentication-plugin=mysql_native_password 
    #設置MySQL的默認認證插件爲mysql_native_password 
    environment:
        # 設置Mysql數據庫的root密碼和數據庫名稱
      MYSQL_ROOT_PASSWORD: 12345678 
      MYSQL_DATABASE: testdb
    # 要確保要掛載的目錄存在且有正確的讀取權限
    volumes:
      - /var/lib/mysql
    ports:
    # 3306端口映射13306端口
      - '13306:3306'
  redis: #定義redis服務
    image: 'redis:7.2.3'
    container_name: 'basic_project_redis'
    ports:
      - '16379:6379'
  backend: # 定義後端服務
    depends_on: #指該服務依賴其他服務,確保redis和db這兩個服務啓動運行正常再啓動backend服務
      - redis
      - db
    image: 'basic_project:v1' 
    container_name: 'basic-project'
    ports:
      - '8085:8085'
    restart: always # 設置重啓

啓動容器

docker-compose up -d

使用 Kubernetes 運行

概述

Kubernetes 是一個可移植、可擴展的開源容器編排平臺,具有自動化容器部署、服務發現和負載均衡、存儲編排、自動執行故障恢復、自動密鑰和配置管理等功能。

K8S 集羣一般由CONTROL PLANENode組成。CONTROL PLANEkube-api-serverKube-schedulerKube-controller-manageretcdcloud-controller-manager組成,控制平面組件會爲集羣做出全局決策,如資源的調度,檢測和相應集羣事件。控制平面組件可以在集羣中的任何節點上運行,但爲了方便通常會在同一臺計算機上啓動所有控制平面組件,並且不會在此計算機上運行用戶容器。kube-api server 是 k8s 控制平面的前端,負責處理請求工作;etcd是一致且高可用的鍵值存儲,用於 K8s 所有集羣數據的後臺數據庫。kube-scheduler負責監控新創建、未指定運行節點 (Node) 的 Pods, 並選擇節點讓 Pod 在上面運行。kube-controller-manager負責運行控制器進程。

 Node是一羣工作節點。kubelet會在集羣中的每個節點 (node) 上運行,保證容器都運行在 Pod 中。kube-proxy是集羣每個節點上運行的網絡代理。

Mac 下的 k8s 環境

1. 開啓 Docker 的 K8S 支持

2. 安裝 kubectl 工具

可以在https://kubernetes.io/docs/tasks/tools/安裝kubectl工具。我的環境是Apple Silicon的 Mac 環境,使用下面這條指令安裝。

curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/darwin/arm64/kubectl"

思路

一般來說在 k8s 啓動一個服務,需要有一個配置文件來定義服務的各種配置。這個文件包含幾個部分:

1. PersistentVolume是集羣中的一塊存儲空間,用於存儲數據庫文件,PV 可以是網絡文件系統、雲存儲、本地存儲;

2. PersistentVolumeClaim是用戶對存儲的請求。用戶可以在 PVC 中指定所需的存儲大小和訪問模式(讀寫權限),k8s 會找一個滿足這些條件的 PV 並掛在到 Pod 上。如果找不到則動態創建一個。

3. Deployment是一種高級的 Pod 管理策略,它可以確保任何時候都有指定數量的 Pod 運行。Deploymen 會創建 ReplicaSet 來保證 Pod 的數量;

4. Service 是一種抽象,它定義了訪問一組 Pod 的策略。因爲 Pod 可能會頻繁地創建和銷燬,所以它們的 IP 地址可能會經常變化。Service 通過選擇器來選擇一組 Pod,併爲它們提供一個穩定的 IP 地址和 DNS 名稱,這樣其他的 Pod 就可以通過這個 IP 地址或 DNS 名稱來訪問這組 Pod。

所以我們需要分別給 Mysql、Redis 和後端項目編寫一份 k8s 的配置文件再通過指令啓動這些服務。

步驟

1. 編寫 Mysql 的 k8s 配置文件

# Service 提供一個穩定的接口來訪問Mysql 
# 定義資源的版本和類型
apiVersion: v1
# 定義資源的類型
kind: Service
# 定義資源的名稱
metadata:
  name: basic-project-mysql
# 定義資源的規格、內容
spec:
  type: LoadBalancer # 爲了方便調試
  # 定義端口
  ports:
    # 服務的端口號
    - port: 3309
      # 名稱
      name: mysql
      # 服務的協議,通常是TCP
      protocol: TCP
      # Pod上的目標端口
      targetPort: 3306
  # selector 定義了哪些Pod可以被此Server訪問,通常是通過標籤選擇器實現的
  selector:
    app: basic-project-mysql

---
# Deployment 定義Pod的規格
# 定義資源的版本
apiVersion: apps/v1
# 定義資源的類型
kind: Deployment
# 定義資源的名稱和標籤
metadata:
  name: basic-project-mysql
# 定義資源的規格
spec:
  # 定義那些Pod可以被Deployment管理
  replicas: 1
  # 指定Pod的副本數量。如果沒有指定那麼默認是1
  selector:
    matchLabels:
      app: basic-project-mysql # 選擇器 用於找到要管理的pod
  template: # Pod的模板
    metadata:
      labels:
        app: basic-project-mysql
    spec:
      containers:
        - name: mysql-8
          env:
            - name: MYSQL_ROOT_PASSWORD # 圖方便但不安全 設置用戶名跟密碼
              value: "12345678"
            - name: MYSQL_DATABASE
              value: "testdb"
          image: mysql:8.0  #使用的Docker鏡像
          ports:
            - containerPort: 3306 # 容器端口號
              name: mysql
          volumeMounts: # 掛載volume到容器的文件系統
            - mountPath: /var/lib/mysql # 掛載路徑
              name: mysql-storage
      volumes: # 定義volume
        - name: mysql-storage
          persistentVolumeClaim: # 使用PersistentVolumeClaim作用volume的源
            claimName: basic-project-mysql-pv-claim

# PersistentVolumeClaim 用於聲明對PersistentVolume的需求
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: basic-project-mysql-pv-claim
spec:
  storageClassName: manual
  accessModes:
    - ReadWriteOnce # 表示volume可以被一個Pod讀寫
  resources:
    requests:
      storage: 1Gi #請求1GB的存儲空間

---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: basic-project-mysql-pv-claim
spec:
  storageClassName: manual 
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/mnt/data"

2. k8s 啓動 Mysql

使用指令 kubectl apple -f xxx.yaml 啓動,顯示 READY 表示服務已經準備好了,然後測試一下是否能連接 Mysql 服務。

❯ kubectl apply -f k8s-basicproject-mysql.yaml                                                                          
service/basic-project-mysql unchanged
deployment.apps/basic-project-mysql configured
persistentvolumeclaim/basic-project-mysql-pv-claim unchanged
persistentvolume/basic-project-mysql-pv unchanged

❯ kubectl get pods                                                                                                               
NAME                                   READY   STATUS    RESTARTS   AGE
basic-project-mysql-758785bcd5-mqrvm   1/1     Running   0          9m15s

3. 編寫 redis 的 k8s 配置文件

# Service
apiVersion: v1
kind: Service
metadata:
  name: basic-project-redis
spec:
  type: LoadBalancer
  ports:
    - port: 16379
      name: redis
      protocol: TCP
      targetPort: 6379
  selector:
    app: basic-project-redis

# Deployment
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: basic-project-redis-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: basic-project-redis
  template:
    metadata:
      labels:
        app: basic-project-redis
    spec:
      containers:
        - name: redis
          image: redis:7.2.3
          ports:
            - containerPort: 6379

4. k8s 啓動 Redis

❯ kubectl apply -f k8s-basicproject-redis.yaml                                                                                                                                             
service/basic-project-redis configured
deployment.apps/basic-project-redis-deployment configured
❯ kubectl get pods                                                                                                                                                                        
NAME                                              READY   STATUS    RESTARTS   AGE
basic-project-mysql-758785bcd5-mqrvm              1/1     Running   0          34m
basic-project-redis-deployment-7db758f6b5-h88bg   1/1     Running   0          4s

5. 修改項目的配置文件

name: "basicproject"
mode: "prod"
port: 8086
version: "v0.0.1"

mysql:
  host: "your_ip" 
  port: 3309
  user: "root"
  password: "12345678"
  dbname: "testdb"
  max_open_conns: 200
  max_idle_conns: 50

redis:
  host: "your_ip"
  port: 16379

修改配置文件過後要記得重新打包 basic-project 的鏡像。

6. 編寫 basic-project 的 k8s 配置文件

apiVersion: v1
kind: Service
metadata:
  name: basic-project
spec:
  type: LoadBalancer
  selector:
    app: basic-project
  ports:
    - name: http
      port: 8086
      protocol: TCP
      targetPort: 8086

---
apiVersion: apps/v1
kind : Deployment
metadata:
  name: basic-project
spec:
  replicas: 1
  selector:
    matchLabels:
      app: basic-project
  template:
    metadata:
      labels:
        app: basic-project
    spec:
      containers:
        - name: basic-project
          image: basic_project:v1
          ports:
            - containerPort: 8086

7. k8s 啓動 basic-project

❯ kubectl apply -f k8s-basicproject-backend.yaml                                                                                                                                          
service/basic-project unchanged
deployment.apps/basic-project configured


❯ kubectl get pods                                                                           
NAME                                              READY   STATUS    RESTARTS      AGE
basic-project-679b8d4849-8hdwj                    1/1     Running   0             11m
basic-project-mysql-7995c97fd6-gggnm              1/1     Running   1 (75m ago)   143m
basic-project-redis-deployment-7db758f6b5-h88bg   1/1     Running   1 (75m ago)   170m

我們可以看到 3 個 pods 已經啓動。

測試註冊登錄功能

寫在最後

本人是新手小白,如果這篇筆記中有任何錯誤或不準確之處,真誠地希望各位讀者能夠給予批評和指正。謝謝!

練習的代碼放在這裏 --↓

https://github.com/FengZeHe/LearnGolang/tree/main/project/BasicProject
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/q91uDC83MjL-NWjeqxAbNw