基础设施三件套:Terraform + Docker + Kubernetes
基础设施三件套:Terraform + Docker + Kubernetes
整理一下这三个工具各自干什么、为什么要一起用、一个真实项目里它们怎么衔接。
全局认知
现代云原生项目的基础设施栈,从下到上大致四层,每一层只解决一类问题:
┌─────────────────────────────────────────┐
│ Terraform → 创建基础设施(云资源) │ Provision 建房
├─────────────────────────────────────────┤
│ Ansible → 配置服务器(装软件、改配置) │ Configure 装修(可选)
├─────────────────────────────────────────┤
│ Docker → 打包应用(镜像 + 运行时) │ Package 装家具
├─────────────────────────────────────────┤
│ Kubernetes → 编排容器(调度、伸缩、自愈) │ Orchestrate 物业管理
└─────────────────────────────────────────┘
一句话:Terraform 建房子(集群本身),Docker 装家具(应用镜像),K8s 是物业管理员(决定家具放哪、坏了怎么修)。Ansible 在容器化项目里通常用不到,下面重点是 Terraform + Docker + K8s。
各自定位
| 工具 | 解决什么问题 | 典型产物 | 状态模型 |
|---|---|---|---|
| Terraform | "我要 10 台 EC2 + 1 个 RDS + 1 个 VPC" | .tf 文件 + state | 声明式,有 state 文件 |
| Docker | "我的应用和依赖打包成可移植单元" | image(镜像) | 不可变 artifact |
| Kubernetes | "把这堆容器跑起来,挂了自动重启,流量自动负载均衡" | Deployment/Service YAML | 声明式,etcd 存状态 |
Terraform:管云资源的生命周期
Terraform 是 Infrastructure as Code(IaC)工具,把"在云控制台点点点"用代码表达出来。它管理云资源的完整生命周期:
- 声明要什么:1 个 EKS 集群、3 台 m5.xlarge 节点、1 个 RDS、VPC、安全组
- 创建:
terraform apply调云厂商 API 把资源建出来 - 修改:改
.tf文件后apply,自动算 diff,只改变化的部分 - 销毁:
terraform destroy一键拆掉所有资源 - 状态追踪:
terraform.tfstate记录建过什么,避免重复或遗漏
最小示例:
# main.tf
provider "aws" {
region = "ap-northeast-1"
}
resource "aws_eks_cluster" "main" {
name = "game-cluster"
role_arn = aws_iam_role.eks.arn
vpc_config {
subnet_ids = aws_subnet.main[*].id
}
}
terraform init # 下载 provider 插件
terraform plan # 看一眼要做什么改动(不执行)
terraform apply # 真正去云上建资源
terraform destroy # 不用了就拆掉
两个容易踩的坑:
- state 文件不能丢。丢了 Terraform 就不知道哪些资源是它建的。生产环境一定把 state 存到远端(S3 + DynamoDB 锁)。
- 不要手工改云控制台。手工改完 Terraform 不知道,下次
apply可能把你的修改覆盖掉。
Docker:应用交付的标准格式
Docker 把应用 + 依赖 + 运行环境打成一个不可变镜像,到哪都能跑,解决"在我机器上能跑"的问题。三个核心概念:
| 概念 | 含义 |
|---|---|
| Dockerfile | 声明应用需要什么环境的文本文件 |
| Image(镜像) | 构建产物,不可变,能推到 registry(ECR/Harbor/Docker Hub) |
| Container(容器) | 镜像运行起来的实例 |
最小示例:
# Dockerfile
FROM openjdk:17
COPY game-server.jar /app/
CMD ["java", "-jar", "/app/game-server.jar"]
# 构建镜像
docker build -t my-registry/game-server:v1 .
# 本地跑一下试试
docker run -p 8080:8080 my-registry/game-server:v1
# 推到镜像仓库(之后 K8s 从这里拉)
docker push my-registry/game-server:v1
几个关键点:镜像是分层的,每条 Dockerfile 指令是一层,共享层能省存储和传输时间;镜像不可变,要改就重新 build 一个新 tag;K8s 集群里运行的就是 Docker 镜像(确切说是 OCI 镜像,Docker 镜像是其子集)。
Kubernetes:容器编排器
K8s 是开源软件,不是云服务。它的职责是管理一堆容器:
| K8s 做的事 | 具体含义 |
|---|---|
| 调度 | 把容器放到合适的节点上跑 |
| 自愈 | 容器/节点挂了自动重启或迁移 |
| 伸缩 | 根据 CPU/内存或自定义指标自动扩缩容 |
| 服务发现 | 容器之间用 DNS 名字找彼此 |
| 负载均衡 | 流量自动分发到多个副本 |
| 滚动更新 | 新版本平滑替换旧版本,挂了能回滚 |
云厂商的 EKS(AWS)/ GKE(Google)/ AKS(Azure)是托管的 K8s,帮你运维 control plane,你只管用。
最小示例:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: game-server
spec:
replicas: 3 # 跑 3 个副本
selector:
matchLabels:
app: game-server
template:
metadata:
labels:
app: game-server
spec:
containers:
- name: game-server
image: my-registry/game-server:v1 # 用 Docker 镜像
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: game-server
spec:
type: LoadBalancer
selector:
app: game-server
ports:
- port: 80
targetPort: 8080
kubectl apply -f deployment.yaml # 部署
kubectl get pods # 看容器状态
kubectl logs <pod-name> # 看日志
kubectl rollout undo deployment/game-server # 回滚
三者怎么衔接
┌──────────────┐
│ Terraform │ apply 之后产出 ──► EKS 集群的 kubeconfig
└──────────────┘
│
▼
kubectl / Helm 拿它连上 K8s
│
┌──────────────┐ ▼
│ Docker │ push 之后 ──► K8s 从 registry 拉取镜像
└──────────────┘ │
▼
容器跑起来
职责边界最容易混,列个表:
| 问题 | 谁负责 |
|---|---|
| "我要一个 K8s 集群" | Terraform |
| "集群里要跑 nginx 这个服务" | K8s(YAML / Helm) |
| "nginx 这个镜像怎么构建" | Docker(Dockerfile) |
| "K8s 集群的节点扩容到 10 台" | Terraform(改节点组)或 K8s Cluster Autoscaler |
| "nginx 副本数从 3 扩到 10" | K8s(HPA 或手动改 Deployment) |
端到端最小流程
目标:把一个 Java 游戏后端部署到 AWS。
Step 1,Terraform 建集群:
# infra/main.tf
resource "aws_eks_cluster" "main" {
name = "game-cluster"
# ... VPC、节点组、IAM 等略
}
cd infra/
terraform init
terraform apply
# 拿到集群的 kubeconfig
aws eks update-kubeconfig --name game-cluster
Step 2,Docker 打镜像:
# app/Dockerfile
FROM openjdk:17
COPY game-server.jar /app/
CMD ["java", "-jar", "/app/game-server.jar"]
cd app/
docker build -t my-registry/game-server:v1 .
docker push my-registry/game-server:v1
Step 3,K8s 部署:
cd k8s/
kubectl apply -f deployment.yaml
kubectl get pods # 验证容器跑起来了
后续迭代:改代码后重新 docker build + push 一个新 tag,改 deployment.yaml 的 image 字段再 kubectl apply;加节点改 .tf 后 terraform apply;加副本 kubectl scale deployment/game-server --replicas=10。
在 CI/CD 流水线里的角色
维护类似 Jenkins 共享库的项目时,这三件套通常这样接入:
| 流水线阶段 | 用到的工具 |
|---|---|
| 准备构建环境(Jenkins agent 池) | Terraform 建机器 / EKS 节点组 |
| 构建产物 | Docker build + push |
| 部署测试环境 | kubectl apply 或 helm upgrade |
| 部署生产 | 同上 + 蓝绿/金丝雀策略 |
| 销毁临时环境 | terraform destroy 或 kubectl delete |
学习路径
- 先学 Docker(1-2 天):本地装个 Docker Desktop,把一个 Hello World 应用打成镜像跑起来。
- 再学 K8s(1 周):用 minikube 或 kind 在本地起单节点集群,跑通 Deployment + Service + Ingress。
- 最后学 Terraform(3-5 天):拿一个免费云账号(AWS Free Tier),用 Terraform 建个 VPC + EC2 试试。
- 串起来:用 Terraform 建一个 EKS,把前面练习过的镜像部署上去。
官方教程:Docker、Kubernetes、Terraform。
几个常见疑问
Docker 和 K8s 是替代关系吗?不是。Docker 是镜像格式 + 单机运行时,K8s 是多机编排器,底层调用容器运行时(现在通常是 containerd)。单机跑 1-2 个容器用 docker compose 够了,规模上来用 K8s。
没有 K8s 能用 Docker 吗?能。小项目用 docker compose 完全够用。K8s 是为了解决多机器 + 高可用 + 自动伸缩才引入的,没那个需求别上。
Terraform 能管 K8s 资源(比如 Deployment)吗?能,Terraform 有 Kubernetes provider,但不推荐。K8s 资源变化频率远高于基础设施,混在一起会让 Terraform state 变乱。最佳实践是 Terraform 只管集群本身,集群内的 workload 用 kubectl / Helm / ArgoCD。
要不要全栈都上?看规模:
| 规模 | 推荐组合 |
|---|---|
| 团队 < 10 人、服务 < 10 个 | 只用 Docker + docker compose |
| 中等规模、单云、单区域 | Docker + 托管 K8s(手工建集群) |
| 多环境、多区域、需要 IaC 审计 | 全栈 Terraform + Docker + K8s |