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
作爲基礎鏡像並將結果命名爲builder
,golang: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
, 數據庫Mysql
和Redis
。因爲 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 PLANE
和Node
組成。CONTROL PLANE
由kube-api-server
、Kube-scheduler
、Kube-controller-manager
、etcd
和cloud-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