
文末有源码下载链接
在现代云原生架构中,容器化已经成为应用部署的标准方式。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 滚动更新:
不是推倒重来,而是在保持生活继续的同时,
慢慢替换掉旧的自己,让更好的版本上线。
如果您喜欢这篇文章,请点赞、推荐+分享给更多朋友,万分感谢!