- 文档/教程
【免费下载链接】90DaysOfDevOps
This repository started out as a learning in public project for myself and has now become a structured learning map for many in the community. We have 3 years under our belt covering all things DevOps, including Principles, Processes, Tooling and Use Cases surrounding this vast topic.
本文是 90DaysOfDevOps 开源学习路线图中「IaC(基础设施即代码)」阶段的实战篇:在 Day 59 用 Terraform + VirtualBox 创建虚拟机之后,本篇将把同样的声明式思路迁移到本地 Docker 环境——先用kreuzwerker/docker社区 Provider 部署一个对外暴露的 NGINX 容器,再进一步把 docker-compose 风格的 WordPress + MySQL 架构完整改写为 Terraform 配置,并梳理 Provisioners 与 Modules 两大组织机制。读完本文你将掌握:用 Terraform 声明 Docker 镜像、容器、网络与卷资源,通过terraform init/apply/destroy管理容器生命周期,以及何时用local-exec、remote-exec等 Provisioner 兜底非声明式逻辑、如何用 Modules 拆分可复用基础设施组件。
前置回顾:从 VirtualBox 虚拟机到 Docker 容器
Day 60 是 IaC 系列的第 3 站。在 Day 59 中,我们已经使用terra-farm/virtualbox社区 Provider 在本地 VirtualBox 中声明式地创建了 2 台 Ubuntu 虚拟机(对应源码 virtualbox.tf),并体验了terraform init、terraform plan、terraform apply与terraform destroy的完整生命周期,还引入了变量与输出的概念。
本节的核心思路不变:写一份描述「期望状态」的.tf配置,交给 Terraform 去执行差异比对(drift)与资源编排。区别仅在于这次的目标不是虚拟机,而是本地 Docker 守护进程——Terraform 通过 Docker Provider 与dockersocket 通信,把镜像拉取、容器创建、端口映射、网络与卷挂载全部纳入 Terraform 状态(terraform.tfstate)管理。
定位说明:在真实生产环境中,Terraform 管理 Docker 并不常见(Kubernetes 才是更合适的容器编排层),但作为学习 IaC 抽象模型、体验 Provider 与资源声明的过渡实验,它是成本最低、反馈最快的演练环境。
第一个实验:用 Terraform 部署 NGINX 容器并暴露到 8000 端口
1. 编写配置:镜像资源 + 容器资源
在项目里新建一个目录(例如Docker/),创建docker.tf,内容如下。仓库中已有完整可用的示例文件 docker.tf,建议直接对照阅读:
terraform { required_providers { docker = { source = "kreuzwerker/docker" version = "2.16.0" } } } provider "docker" {} resource "docker_image" "nginx" { name = "nginx:latest" keep_locally = false } resource "docker_container" "nginx" { image = docker_image.nginx.latest name = "tutorial" ports { internal = 80 external = 8000 } }逐段解读这份配置:
terraform块:声明 Provider 来源与版本。这里用的是社区维护的kreuzwerker/dockerProvider(非 HashiCorp 官方),固定版本2.16.0可保证行为可复现,避免上游更新带来的隐性变更。provider "docker" {}:无参实例化 Provider。默认情况下它直接使用本机的 Docker 守护进程(与dockerCLI 同一 socket)。docker_image.nginx:声明期望存在的镜像。name = "nginx:latest"指定镜像标签;keep_locally = false表示当资源被销毁时不保留本地镜像副本(若设为true则terraform destroy不会删除已拉取的镜像)。docker_container.nginx:声明期望运行的容器。image = docker_image.nginx.latest引用了上方镜像资源的latest属性,形成资源间依赖(Terraform 会先创建镜像再创建容器);name = "tutorial"指定容器名;ports块把容器内部 80 端口映射到宿主机的 8000 端口。
2. 初始化:terraform init 拉取 Provider
首次执行前必须先初始化,下载kreuzwerker/dockerProvider 插件并生成.terraform目录与锁定文件:
terraform init执行成功后的终端输出如图(来自 Day60_IAC1.png):Terraform 初始化后端、安装 docker Provider 插件并提示初始化完成。
3. 应用与验证:terraform apply + docker ps
执行应用命令,Terraform 会先拉取nginx:latest镜像、再创建容器:
terraform apply从源码与运行截屏(Day60_IAC2.png)可以看到,apply完成后立即执行docker ps,即出现名为tutorial的 nginx 容器处于运行状态。
此时打开浏览器访问http://localhost:8000/,即可看到 NGINX 默认欢迎页(Day60_IAC3.png),说明容器内部 80 端口已通过宿主 8000 端口对外提供服务:
4. 清理
不需要时执行terraform destroy,Terraform 会按依赖逆序移除容器与镜像(keep_locally = false时镜像一并删除)。
小结:这个最小实验证明了「镜像 + 容器」这两个资源类型,以及
terraform state对容器生命周期的接管能力——这正是 docker-compose 与 Terraform 的交叉地带。
进阶实验:把 docker-compose 的 WordPress + MySQL 改写为 Terraform
为了让演示更有说服力,接下来把容器章节中用 docker-compose 编排的 WordPress + MySQL 架构,完整改写为 Terraform 配置。仓库中的对应文件为 docker-wordpress.tf。
完整配置
terraform { required_providers { docker = { source = "kreuzwerker/docker" version = "2.16.0" } } } provider "docker" {} variable wordpress_port { default = "8080" } resource "docker_volume" "db_data" { name = "db_data" } resource "docker_network" "wordpress_net" { name = "wordpress_net" } resource "docker_container" "db" { name = "db" image = "mysql:5.7" restart = "always" network_mode = "wordpress_net" env = [ "MYSQL_ROOT_PASSWORD=wordpress", "MYSQL_PASSWORD=wordpress", "MYSQL_USER=wordpress", "MYSQL_DATABASE=wordpress" ] mounts { type = "volume" target = "/var/lib/mysql" source = "db_data" } } resource "docker_container" "wordpress" { name = "wordpress" image = "wordpress:latest" restart = "always" network_mode = "wordpress_net" env = [ "WORDPRESS_DB_HOST=db:3306", "WORDPRESS_DB_USER=wordpress", "WORDPRESS_DB_NAME=wordpress", "WORDPRESS_DB_PASSWORD=wordpress" ] ports { internal = "80" external = "${var.wordpress_port}" } }这份配置引入了比上一示例更丰富的资源类型,逐个说明:
variable wordpress_port:声明输入变量,default = "8080"提供默认值。WordPress 容器把 80 端口映射到external = "${var.wordpress_port}",意味着可以通过变量(或-var、TF_VAR_*、terraform.tfvars等方式,见 Day 59 的变量章节)灵活调整对外端口。docker_volume "db_data":声明命名卷,对应 compose 中的db_datavolume。它是 MySQL 数据持久化的载体。docker_network "wordpress_net":声明自定义网络,对应 compose 中的网络定义,用于让db与wordpress两个容器通过容器名互相解析。docker_container "db"(MySQL):镜像mysql:5.7;restart = "always"让守护进程在容器退出后自动重启;network_mode = "wordpress_net"加入自定义网络;env数组等价于 compose 的environment,注入MYSQL_ROOT_PASSWORD、MYSQL_PASSWORD、MYSQL_USER、MYSQL_DATABASE;mounts块把db_data卷挂载到容器内/var/lib/mysql(MySQL 数据目录),实现数据持久化。docker_container "wordpress":镜像wordpress:latest;env中WORDPRESS_DB_HOST=db:3306让 WordPress 通过容器名db访问 MySQL 服务(依赖wordpress_net网络的 DNS 解析);其余变量对应数据库账号、库名与密码,与db容器的env保持一致。
执行流程与验证
把配置放入新目录后,依次执行:
terraform init # 拉取 docker Provider terraform apply # 创建卷、网络、两个容器 docker ps # 查看运行中的容器apply完成后执行docker ps,可以看到mysql:5.7镜像的db容器与wordpress:latest镜像的wordpress容器均处于运行状态,如图 Day60_IAC5.png:
随后在浏览器打开http://localhost:8080/,即可进入 WordPress 安装向导。整个初始化流程与之前用 docker-compose 走通的过程完全一致:完成安装后,文章、页面等数据都写入 MySQL 的db_data卷中,数据随容器重建而保留。
与 docker-compose 的对照
| 能力 | docker-compose | Terraform + Docker Provider |
|---|---|---|
| 镜像 | image: | docker_image资源 |
| 容器 | services: | docker_container资源 |
| 网络 | networks: | docker_network资源 |
| 卷 | volumes: | docker_volume资源 |
| 环境变量 | environment: | env = [...] |
| 端口映射 | ports: | ports { internal / external } |
| 重启策略 | restart: | restart = "always" |
| 状态管理 | 无状态文件 | terraform.tfstate记录全部资源 |
两者的声明式思想一致,但 Terraform 会把资源生命周期纳入统一的 state 管理,并能与其他云资源(VPC、VM、K8s 集群)编排在同一个配置中。
生产环境提示:原文档明确指出,容器直接运行方式的验证价值大于生产价值。若真的要对外提供网站服务,应优先考虑 Kubernetes 编排(下一篇将演示 kubernetes.tf 用 Terraform 声明 Namespace、Deployment 与 Service),容器方案只适合测试与本地演练。
Provisioners:声明式之外的命令执行兜底
为什么需要 Provisioners
Terraform 的核心哲学是声明式:配置只描述「最终状态」,Terraform 负责计算执行计划。但现实总有些动作无法被任何资源类型表达(例如初始化应用配置、执行系统级脚本、注册外部服务)。此时引入Provisioners——它们在资源创建(或销毁)后执行命令,是「把命令式逻辑塞进声明式配置」的兜底通道。
原文档也给出明确的取舍建议:只有当你没有其他替代方案时才使用,因为它会把无法追踪的副作用带进配置,降低可预测性。
local-exec 与 remote-exec
原文档给出的local-exec示例——在本地机器上执行命令(可用于打印信息、调用本地脚本、触发后续工具链):
resource "docker_container" "db" { # ... provisioner "local-exec" { command = "echo The server's IP address is ${self.private_ip}" } }其中${self.private_ip}引用当前资源自身的属性,local-exec会在本地 shell 中执行这条 echo 命令。
remote-exec则是在远程资源创建完成后,通过 SSH/WinRM 等通道在目标机器上执行脚本。它适合处理操作系统特定的初始化,或作为包装器把 Ansible、Chef、Puppet 等配置管理工具串进资源创建流程。
Provisioner 类型总览
原文档列举的常用 Provisioner 及使用场景:
- file:把文件或目录复制到远程资源(常配合
remote-exec使用)。 - local-exec:在 Terraform 运行的本地机器上执行命令,无远程访问要求。
- remote-exec:在远程资源上执行脚本,需先打通 SSH/WinRM 连接。
- vendor 类集成:通过封装调用外部配置管理工具完成更复杂的配置下发:
- Ansible
- Chef
- Puppet
Modules:把基础设施拆成可复用组件
什么是 Modules
Modules 是多个资源共同组成的容器:一个模块就是同一目录下的一组.tf文件的集合(通常包含main.tf、variables.tf、outputs.tf)。Terraform 默认把当前工作目录视为根模块,而你可以在根模块中通过module块引用其他模块(本地目录或第三方注册中心)。
为什么要用 Modules
原文档给出了两个核心动机:
- 职责分离:当同一个项目需要同时构建 VMs、VPC、Security Groups 和 Kubernetes 集群时,把不同类型的资源拆进各自模块,可以更清晰地定义资源边界与组织方式,避免单个目录堆积大量互不相关的资源声明。
- 复用与共享:模块可以跨项目复用,也可以发布到公开注册中心供社区使用——"不用重新发明轮子"。团队把通用组件(如网络、安全组、集群)封装成模块后,新项目只需声明模块引用并传参即可。
结合仓库结构可以直观理解模块化思想:IaC 目录下 Docker/、Docker-Wordpress/、Virtualbox/、Kubernetes/ 各自独立成目录,每个目录就是一个自包含的模块——每个模块只负责一类基础设施,可单独init/apply,也可被上层模块引用。这正是「把基础设施分解为组件,组件即模块」的落地形态。
本篇小结与下一步
- 用
kreuzwerker/dockerProvider 声明镜像与容器资源,terraform init/apply/destroy即可完成 NGINX 的部署、验证与清理; - 通过 volume、network、env、mounts、restart 等参数,可将 docker-compose 的 WordPress + MySQL 架构 1:1 改写为 Terraform 配置并纳入 state 管理;
- Provisioners(file / local-exec / remote-exec,以及 Ansible、Chef、Puppet 等 vendor 集成)是声明式模型的兜底手段,仅在无替代方案时使用;
- Modules 是资源分组的复用单元,帮助拆分复杂基础设施并支持跨项目共享。
下一篇将进入「Terraform + Kubernetes」:使用 kubernetes.tf 示例,声明 Namespace、Deployment(含 replicas、selector、容器端口)与 NodePort Service,把同样的声明式能力延伸到集群编排层。
- 文档/教程
【免费下载链接】90DaysOfDevOps
This repository started out as a learning in public project for myself and has now become a structured learning map for many in the community. We have 3 years under our belt covering all things DevOps, including Principles, Processes, Tooling and Use Cases surrounding this vast topic.
相关推荐
90DaysOfDevOps 第 60 天:用 Terraform 管理 Docker 容器、Provisioners 与 Modules
90DaysOfDevOps 第 60 天:用 Terraform 管理 Docker 容器、Provisioners 与 Modules 本篇指南以 90Da
文档/教程90DaysOfDevOps 第 60 天:用 Terraform 管理 Docker 容器、Provisioners 与 Modules 实战指南
90DaysOfDevOps 第 60 天:用 Terraform 管理 Docker 容器、Provisioners 与 Modules 实战指南 本篇技术指
文档/教程90DaysOfDevOps:使用 Terraform 管理 Docker 容器、Provisioners 与 Modules(Day 60)
90DaysOfDevOps:使用 Terraform 管理 Docker 容器、Provisioners 与 Modules(Day 60) 本文是 90Da
文档/教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考