每日一Go-30、Go语言进阶-现代化部署:容器化与Docker

文末有源码下载链接

在现代云原生架构中,容器化已经成为应用部署的标准方式。Docker通过"镜像+容器"的机制,让任何人可以轻松地:

  • 打包应用及其所有依赖

  • 所有环境保持一致:你的电脑能跑,服务器也能跑

  • 启动快、占用少、易扩展

  • 配合K8s实现自动扩容、滚动更新

1、为什么微服务都在用Docker?

Docker出现之前地痛点:

  • Go代码在本机运行正常,部署到服务器后各种依赖缺失

  • 配置环境、装依赖、升级版本非常麻烦

  • 多服务部署搞得像"盘丝洞",环境乱成一锅粥

  • 回滚、扩容、迁移都很困难

Docker出现后解决了什么?

  • 将应用、依赖、运行环境一起封装成镜像

  • 镜像是不可变的,哪里都能跑

  • 启动容器只需要几百毫秒

  • 天然适配K8s、CI/CD

一句话:用Docker = 构建"可复制、可迁移、有保障"地部署流程

2、准备一套完整的web服务

bash 复制代码
package server
func StartHTTPServer(host string, port int) error {
    zap.L().Info("正在初始化服务器...")
    mode := viper.GetString("server.mode")
    switch mode {
    case "dev":
        gin.SetMode(gin.DebugMode)
    case "prod":
        gin.SetMode(gin.ReleaseMode)
    case "test":
        gin.SetMode(gin.TestMode)
    default:
        gin.SetMode(gin.DebugMode)
    }
    components.Init()         // 初始化各个组件
    migrates.DoMigrate()      // 数据库迁移
    services.InitServices()   // 初始化服务层
    r := routers.InitRouter() // 初始化路由
    s := &http.Server{
        Addr:           fmt.Sprintf(`%s:%d`, host, port),
        Handler:        r,
        ReadTimeout:    time.Duration(viper.GetInt("server.readtimeout")) * time.Second,
        WriteTimeout:   time.Duration(viper.GetInt("server.writertimeout")) * time.Second,
        MaxHeaderBytes: 1 << 20,
    }
    // 启动HTTP服务
    go func() {
        zap.L().Info("HTTP服务器启动了", zap.String("addr", s.Addr))
        if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            zap.L().Fatal("HTTP服务器启动失败", zap.Error(err))
        }
    }()
    //告诉k8s或者docker我准备好了
    services.SetReady()
    // 捕获系统信号
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, os.Interrupt)
    <-quit
    zap.L().Warn("收到关闭信号,正在优雅退出...")
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    s.SetKeepAlivesEnabled(false) // 避免新的连接进来
    if err := s.Shutdown(ctx); err != nil {
        fmt.Println("服务器被强制关闭了:", err)
    }
    zap.L().Info("服务器优雅退出了")
    return nil
}

3、编写Dockerfile(敲黑板)

Go容器镜像最佳实践---多阶段构建(multi-stage build)

bash 复制代码
# 第一阶段:构建阶段
FROM golang:1.23-alpine AS builder
#LABEL 指令用来给镜像添加一些元数据(metadata),以键值对的形式
LABEL maintainer="Codee君"
#设置容器Go语言环境变量
ENV GO111MODULE=on
ENV GOPROXY=https://goproxy.cn,direct
#为 RUN、CMD、ENTRYPOINT、COPY 和 ADD 设置工作目录,就是切换目录
WORKDIR /go/release
#COPY 拷贝文件或目录到容器中,跟ADD类似,但不具备自动下载或解压的功能
COPY . .
#RUN 构建镜像时运行的指令
#sed 命令用于文本处理,这里是用来替换 Alpine Linux 的软件源镜像为中科大的镜像
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
RUN apk update && apk add tzdata
#安装swago,并自动生成API文档
RUN go install github.com/swaggo/swag/cmd/swag@v1.8.10 
RUN swag init  --parseDependency=true
#go build 编译程序
RUN CGO_ENABLED=0 GOOS=linux go build -p 1 -ldflags="-w -s" -a -installsuffix cgo -o golang_per_day .
# 第二阶段:运行阶段
FROM alpine:latest
# 把第一阶段构建的二进制文件和依赖拷贝到新的镜像中
COPY --from=builder /go/release/golang_per_day /
COPY --from=builder /go/release/configs /configs
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
#EXPOSE 声明容器的服务端口(仅仅是声明)
EXPOSE 8080
#CMD 运行容器时执行的shell命令
CMD ["/golang_per_day_30","server","-c", "/configs/config.yaml"]

为什么要多阶段构建?

  • 避免把整个Go编译环境打进最终的镜像

  • builder阶段 > 1GB

  • alpine阶段 < 100MB

  • 最终镜像只有几十MB,非常适合云原生

4、构建Docker镜像

在项目根目录执行:

bash 复制代码
$ docker build --no-cache -t imoowi/golang_per_day_30 .

5、把镜像push到仓库,这里用的docker的公共仓库,如果你需要私有仓库,请去其他云厂商购买或者自己搭建。

bash 复制代码
$ docker login -u 你的用户名

i Info → A Personal Access Token (PAT) can be used instead.
          To create a PAT, visit https://app.docker.com/settings
Password: 输入密码
Login Succeeded
$ docker push imoowi/golang_per_day_30
Using default tag: latest
The push refers to repository [docker.io/imoowi/golang_per_day_30]
68578effd277: Pushed
94ffa90d46ab: Pushed
b7012b08af98: Pushed
0f4970872d39: Pushed
418dccb7d85a: Mounted from library/alpine
latest: digest: sha256:bad95cb900e0758a4cfeb7d8e3fe284911b1d60bd5fd19ab19b69dd2044b5ecb size: 1364

6、运行容器

bash 复制代码
docker
8080
8080

浏览器访问http://localhost:8080/swagger/index.html

7、用Docker Compose管理多个服务(Go+MySQL+Redis)

7.1 配置docker-compose.yml文件

bash 复制代码
version: "3"
networks:
    #定义一个名为codee_jun的网络
    codee_jun:
      driver: bridge
services:
  mysql:
    container_name: mysql
    image: mysql:5.7
    ports:
        - 3306:3306
    environment:
        MYSQL_ROOT_PASSWORD: "123456"
    volumes:
        # 数据库挂载目录
        - ./docker-data/mysql/data:/var/lib/mysql
        # 数据库初始化脚本挂载目录
        - ./configs/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    command: [
        '--character-set-server=utf8mb4',
        '--collation-server=utf8mb4_unicode_ci',
        '--max_connections=3000'
    ]
    restart: always
    networks:
        - codee_jun
  redis:
    container_name: redis
    image: redis:latest
    command: redis-server --requirepass 123456
    ports: 
        - 6379:6379
    volumes:
        - ./docker-data/redis/db:/data
    restart: always
    networks:
        - codee_jun
  golang_per_day_30:
    container_name: golang_per_day_30
    build: .
    ports:
      - 8080:8080
    # 依赖的服务,必须先启动依赖的服务,才能启动当前服务
    depends_on:
        - mysql
        - redis
    volumes:
      - ./configs:/configs
    restart: always
    networks:
        - codee_jun

7.2 添加初始化sql

bash 复制代码
---configs/init.sql
CREATE DATABASE IF NOT EXISTS `golang_per_day` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE `golang_per_day`;

7.2 启动服务

bash 复制代码
$ docker-compose up -d 
[+] Running 4/4
 ✔ Network golang_per_day_30_codee_jun  Created                                                                                                                                   0.1s 
 ✔ Container redis                      Started                                                                                                                                   0.6s 
 ✔ Container mysql                      Started                                                                                                                                   0.6s 
 ✔ Container golang_per_day_30          Started  

7.3 关闭服务

bash 复制代码
$ docker-compose down
 ✔ Container golang_per_day_30          Removed                                                                                                                                   0.4s 
 ✔ Container mysql                      Removed                                                                                                                                   2.1s 
 ✔ Container redis                      Removed                                                                                                                                   0.5s 
 ✔ Network golang_per_day_30_codee_jun  Removed 

7.4 注意 文件configs/config.docker.yaml 里,mysql和redis的主机地址,现在分别写的是同网络下的服务的名字,而不再是localhost,一定要注意。

bash 复制代码
#configs/config.docker.yaml
mysql:
  dsn: root:123456@tcp(mysql:3306)/golang_per_day?charset=utf8&parseTime=True&loc=Local&timeout=1000ms
redis:
  addr: "redis:6379"
  password: "123456"
  db: 0
bash 复制代码
#docker-compose.yml
version: "3"
networks:
    #定义一个名为codee_jun的网络
    codee_jun:
      driver: bridge
services:
  mysql: 这个名字就是配置文件里对应的服务名
  redis:

8、部署到K8S

8.1 Deployment(核心,部署Go服务),文件k8s/deployment.yaml

bash 复制代码
#k8s/deployment.yaml
bash 复制代码
apiVersion: apps/v1 #定义应用部署的API版本
kind: Deployment #定义部署应用
metadata: #元数据部分
  name: golang-per-day-30 #这里填写部署名称
  namespace: codee_jun #这里填写命名空间
spec:
  #replicas表示期望运行的Pod副本数量
  replicas: 1 #这里填写副本数量
  #选择器,定义如何选择Pod
  selector:
    matchLabels: #选择标签,匹配具有特定标签的Pod
      app: golang-per-day-30 #这里填写标签名称
  #模板,定义Pod的规格
  template:
    metadata: #Pod的元数据
      labels: #标签部分
        app: golang-per-day-30 #这里填写标签名称
    spec: #Pod的规格部分
      containers: #定义容器
        - name: golang-per-day-30 #这里填写容器名称
          image: golang_per_day_30:latest #这里填写镜像名称
          ports: #定义容器端口
            - containerPort: 8080 #容器内部端口
          volumeMounts: #定义卷挂载
            - name: golang_per_day_30-configmap
              mountPath: /configs/config.docker.yaml #容器内挂载路径
              subPath: config.yaml #子路径
          resources: #定义资源请求和限制
            requests: #资源请求
              cpu: 1000m #请求的CPU资源
              memory: 2Gi #请求的内存资源
            limits: #资源限制
              cpu: 2000m #限制的CPU资源
              memory: 4Gi #限制的内存资源
      volumes:
        - name: golang_per_day_30-configmap #卷名称
          configMap: 
            name: golang-per-day-30-configmap #引用的configmap名称

8.2 服务(Service) (给Pod提供稳定访问),文件k8s/service.yaml

bash 复制代码
#k8s/service.yaml
apiVersion: v1
kind: Service #定义服务
metadata:
  name: golang-per-day-30 #这里填写服务名称
  namespace: codee_jun #这里填写命名空间
spec: #服务规格
  # externalIPs:
  #   - 123.59.187.137
  ports:
    - protocol: TCP 
      port: 80 #服务端口
      targetPort: 80 #目标端口(容器端口)
  sessionAffinity: ClientIP #会话亲和性设置为ClientIP
  selector: 
    app: golang-per-day-30  #选择标签,匹配具有特定标签的Pod

8.3 ConfigMap(配置文件),文件k8s/configmap.yaml

bash 复制代码
apiVersion: v1 #定义API版本
kind: ConfigMap #定义资源类型
metadata:
  name: golang-per-day-30-configmap #这里填写ConfigMap名称
  namespace: codee_jun #这里填写命名空间
data:
  config.yaml: |
    server:
      mode: "prod"
      port: 8080
      host: 0.0.0.0
      readtimeout: 60
      writetimeout: 60
    log:
      level: "info"
    mysql:
      dsn: root:123456@tcp(mysql.codee_jun.svc.cluster.local:3306)/golang_per_day?charset=utf8&parseTime=True&loc=Local&timeout=1000ms
    redis:
      addr: "redis.codee_jun.svc.cluster.local:6379"
      password: "123456"
      db: 0
    jwt:
      secret: "golang_per_day_secret_key"
      # 24h表示24小时
      expire: 24h
    ratelimit:
      # 每秒放多少个令牌
      cap: 1000
      # 每秒取多少个令牌
      quantum: 1000

注意:mysql地址变成了mysql.codee_jun.svc.cluster.local,redis的地址变成了redis.codee_jun.svc.cluster.local,这是k8s集群内部服务的访问格式,你也可以用云数据库,在这里直接替换即可。

8.4 自动扩容HPA(水平自动扩容),文件k8s/hpa.yaml

bash 复制代码
apiVersion: autoscaling/v2 #定义API版本
kind: HorizontalPodAutoscaler #定义水平Pod自动扩缩容器
metadata: #元数据
  name: golang-per-day-30-hpa #这里填写HPA名称
  namespace: codee_jun #这里填写命名空间
spec: #HPA规格
  scaleTargetRef:
    apiVersion: apps/v1 #定义API版本
    kind: Deployment #定义资源类型
    name: golang-per-day-30 #这里填写Deployment名称
  minReplicas: 1 #最小副本数
  maxReplicas: 10 #最大副本数
  metrics:  #指标集合
    - type: Resource #资源指标类型
      resource: #资源指标
        name: cpu #资源名称
        target:  #资源目标值
          type: Utilization #CPU利用率目标
          averageUtilization: 80 #CPU利用率目标值
    - type: Resource
      resource:
        name: memory #内存资源名称
        target:
          type: Utilization #内存利用率目标
          averageUtilization: 80 #内存利用率目标值

8.5 Ingress (域名访问), k8s/ingress.yaml

bash 复制代码
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: golang_per_day_30-ingress
  namespace: codee_jun
  annotations:
    nginx.ingress.kubernetes.io/enable-cors: "true"
    nginx.ingress.kubernetes.io/cors-allow-origin: "https://golang_per_day_30.com"
    nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, DELETE, OPTIONS"
    nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"
    nginx.ingress.kubernetes.io/cors-allow-credentials: "true" # 如果需要凭证
    nginx.ingress.kubernetes.io/proxy-body-size: "1024m"
    nginx.ingress.kubernetes.io/affinity: "cookie"
    nginx.ingress.kubernetes.io/session-cookie-name: "golang_per_day_30-session"
    nginx.ingress.kubernetes.io/session-cookie-expires: "172800"
    nginx.ingress.kubernetes.io/session-cookie-max-age: "172800"
spec:
  rules:
    - host: golang_per_day_30.com # 域名
      http: # 协议
        paths: # 路径
          - path: / # 路径
            pathType: Prefix   # 路径类型
            backend: # 后端
              service: # 服务
                name: golang-per-day-30 # 服务名称
                port: # 服务端口
                  number: 8080 # 服务端口号

8.6 部署一条龙

进入k8s目录

bash 复制代码
kubectl apply -f .

查看部署情况

bash 复制代码
$ kubectl get pods -n orth -o wide | grep golang_per_day_30
golang_per_day_30-56c4c7f685-2tm7g                    1/1     Running     0          1m10s    172.16.10.47    cn-beijing.10.16.233.81    <none>           <none>
golang_per_day_30-56c4c7f685-8q9zn                    1/1     Running     0          1m10s    172.16.10.91    cn-beijing.10.12.229.121   <none>           <none>
golang_per_day_30-56c4c7f685-mbrnv                    1/1     Running     0          1m10s    172.16.10.16    cn-beijing.10.16.233.81    <none>           <none>  
golang_per_day_30-56c4c7f685-vf8t6                    1/1     Running     0          1m10s    172.16.10.99    cn-beijing.10.12.229.121   <none>           <none>    

查看服务

bash 复制代码
$ kubectl get svc -n codee_jun | grep golang_per_day_30
golang_per_day_30                       ClusterIP   192.168.1.109    <none>        80/TCP      5m

查看ingress地址

bash 复制代码
$ kubectl get ingress -n codee_jun | grep golang_per_day_30
golang_per_day_30-ingress   nginx   golang_per_day_30.com   a.b.c.d    8080   10m

测试访问浏览器访问地址http://golang\\_per\\_day\\_30.com,如果域名解析成功,即可访问服务

8.7 滚动更新,Deployment默认就是滚动更新,如何触发滚动更新呢?

8.7.1 更新镜像

bash 复制代码
kubectl set image deployment/golang_per_day_30 golang_per_day_30=imoowi/golang_per_day_30:v2

8.7.2 修改Deployment配置

bash 复制代码
kubectl apply -f deployment.yaml

8.7.3 手动重启(强制滚动)

bash 复制代码
kubectl rollout restart deployment golang_per_day_30

*源码地址*

1、公众号"Codee君"回复"每日一Go"获取源码

2、pan.baidu.com/s/1B6pgLWfS...

人生的成长,也像 K8s 滚动更新:

不是推倒重来,而是在保持生活继续的同时,

慢慢替换掉旧的自己,让更好的版本上线。


如果您喜欢这篇文章,请点赞、推荐+分享给更多朋友,万分感谢!

相关推荐
子玖4 小时前
go实现通过ip解析城市
后端·go
Das1_5 小时前
【Golang 数据结构】Slice 底层机制
后端·go
太凉1 天前
select 语句详解
go
想用offer打牌1 天前
一站式了解四种限流算法
java·后端·go
怕浪猫1 天前
第20章:Web服务实战——构建RESTful API
后端·go·编程语言
Coding君2 天前
每日一Go-28、Go语言进阶-深入Go运行时:内存管理与GC
go
echo本尊472182 天前
如何设计一个简单易用的定时任务模块
go
Bigger3 天前
告别版本焦虑:如何为 Hugo 项目定制专属构建环境
前端·架构·go