Golang项目的简单部署

2023-12-06 21:44:36
224

概述

在上一篇笔记记录了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