我曾花费无数时间在开发和生产环境中手动管理 Docker 容器。配置不一致、状态漂移,以及本地环境是否与生产一致的种种不确定,几乎困扰着每一次部署。直到我发现了 Terraform 的 Docker 提供商,一切才发生了改变。
在本教程中,我将带您将 Terraform 与 Docker 集成,创建可复现、可版本化的基础设施。您将学会如何创建容器、管理网络和卷、实施资源限制,并在团队中维护一致的状态。
无论您是在管理构建服务器还是编排微服务,请打开终端,让我们一起改变管理 Docker 基础设施的方式。
如果您是 Docker 新手,建议先从我们的 Introduction to Docker 课程开始。
Terraform 是一种基础设施即代码工具,通过声明式流程管理资源的生命周期。您无需运行单独的 Docker 命令,而是在配置文件中定义期望的基础设施状态。随后 Terraform 按照 plan-apply-destroy 的生命周期执行:计算所需更改、展示预览,并在获得批准后才应用。
Terraform Docker provider 是 HCL(HashiCorp 配置语言)与 Docker API 之间的翻译层。当您在配置中编写 resource “docker_container" 时,提供商会将该声明转换为相应的 Docker API 调用。这种抽象意味着可以用与云基础设施相同的工作流来管理 Docker 资源。
下面是 Terraform 与 Docker Compose 的不同之处。
Compose 通过基于项目的 YAML 文件来管理容器,但它不会维护一个跟踪实际存在资源的状态文件。
相比之下,Terraform 会将其创建的每个资源记录在 terraform.tfstate 中,使其能够检测配置与实际之间的漂移。如果有人手动修改了容器,Terraform 会发现并将其恢复到定义的状态。
这种方法在多种场景中大放异彩:
对于可复现的构建服务器,您可以用一条命令销毁并重建整个环境,从而确保一致性。
在测试环境中,您可以为每次测试运行启动隔离的基础设施。
对于不可变部署,您会完全替换容器而不是就地修改,从而降低部署风险。
先决条件与安装
在深入 Terraform 配置之前,让我们先确认所需工具已正确安装和配置。
满足系统要求
首先,确认您的机器上 Docker Engine 正在运行。
Terraform 提供商需要访问 Docker 守护进程,因此您需要确认您的用户拥有访问 Linux 上 /var/run/docker.sock(Unix 套接字)或 Windows 上命名管道的权限。
接下来,从 HashiCorp 官方网站安装 Terraform CLI。Installing Terraform 会因操作系统而异:
Linux: 下载合适的软件包或使用发行版的包管理器。
macOS: 使用 Homebrew。先通过 brew tap hashicorp/tap 安装 HashiCorp tap,再使用 brew install hashicorp/tap/terraform 安装 Terraform。
Windows: 从 hashicorp.com 下载二进制文件并添加到 PATH,或使用 Chocolatey:choco install terraform。
安装完成后,运行 terraform version 验证安装。
在验证了 Docker 和 Terraform 之后,下一步是组织项目文件以便维护。
配置项目结构
清晰有序的结构可以让 Terraform 项目受益匪浅。通常我会为 Terraform 项目创建一个专用目录并初始化版本控制:
mkdir terraform-docker-project
cd terraform-docker-project
git init技术支持
对于基础设施即代码,版本控制至关重要。对 Docker 基础设施的每一次更改都应被跟踪、审查并可回滚。我通常会将 Terraform 的工作目录和状态文件添 加到 .gitignore 文件中,以将其排除在版本控制之外:
.terraform/
*.tfstate
*.tfstate.backup
.terraform.lock.hcl技术支持
最后,我会创建一个空白的 main.tf 文件来定义基础设施。此文件将包含提供商配置和资源定义。
在项目结构就绪后,是时候配置 Docker 提供商并开始定义资源了。
初始化 Docker 提供商
首先,在 main.tf 中添加提供商配置:
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "~> 3.0"
}
}
}
provider "docker" {
host = "unix:///var/run/docker.sock"
}技术支持
required_providers 块指定 Terraform 应从何处下载提供商插件。使用版本约束(~> 3.0)可允许小版本更新,同时避免破坏性变更。对于远程 Docker 主机,您可以将 host 参数更改为类似 tcp://192.168.1.100:2376 的值。
现在在项目根目录运行 terraform init。这会下载 Docker 提供商插件并存储在 .terraform/providers/ 中。该命令会输出安装的确切版本,同时 Terraform 会创建一个锁定文件,确保团队成员间的提供商版本一致。
管理容器镜像
在完成提供商初始化后,下一步是定义容器镜像。在创建容器之前,需要先管理它们所使用的镜像,因此需要定义一个镜像资源:
resource "docker_image" "nginx" {
name = "nginx:1.29"
keep_locally = true
}技术支持
将该资源直接添加在提供商块之后。keep_locally 参数很重要,它可以防止我在运行 terraform destroy 时移除镜像。当多个容器共享同一镜像,或我希望在部署之间保留缓存镜像时,这非常有用。
在生产部署中,请使用 SHA256 摘要固定镜像以确保不可变性:
resource "docker_image" "nginx" {
name = "nginx@sha256:b60f25eb5acdd79172de1a289891d9cfe4fc3669daddeeb68619240cbe839d9f"
}技术支持
该摘要可从容器镜像仓库获取。 对于 nginx 这类来自 Docker Hub 的镜像,您可以在 Docker 官方网站 的 Tags 部分找到。使用摘要可保证每次部署的镜像二进制完全一致,即使有人用相同的标签推送了新镜像也不受影响。
配置 Docker 容器
定义镜像后,下一步是创建引用这些镜像的容器:
resource "docker_container" "web" {
name = "nginx-web"
image = docker_image.nginx.image_id
ports {
internal = 80
external = 8080
}
env = [
"NGINX_HOST=example.com",
"NGINX_PORT=80"
]
}技术支持
通过 docker_image.nginx.image_id 来引用镜像。这会创建一个隐式依赖关系:在镜像存在之前,Terraform 不会创建容器。ports 块将容器的 80 端口映射到主机的 8080 端口,相当于 docker run -p 8080:80。
环境变量以字符串形式放在 env 列表中。如果任何参数发生变化,Terraform 会重新创建该容器,从而确保运行中的基础设施始终与配置一致。
此时,运行 terraform plan 查看 Terraform 将创建的内容,然后运行 terraform apply 来配置基础设施。Terraform 会创建镜像和容器。您可以在 Docker Desktop 中查看资源,或在终端运行 docker ps 进行验证。
现在您已经运行了一个基础容器并理解了核心工作流,让我们通过生产可用的特性扩展基础设施。接下来的部分将在此基础上添加网络、持久化存储和资源约束,这些都是实际部署中不可或缺的。
管理网络
为了在保持隔离的同时实现容器间通信,创建专用网络至关重要。下面演示如何创建隔离网络来控制容器连通性。将以下资源添加到 main.tf 文件:
resource "docker_network" "app_network" {
name = "application_net"
driver = "bridge"
}技术支持
现在添加一个使用该网络的第二个容器。在 web 容器之后添加:
resource "docker_container" "api" {
name = "api-server"
image = docker_image.nginx.image_id
networks_advanced {
name = docker_network.app_network.name
}
}技术支持
这将创建一个专用的 bridge 网络,并将 api 容器连接到该网络。同一网络上的容器可通过容器名作为主机名进行通信,非常适合微服务架构。
通过运行 docker network inspect application_net 并确认仅有预期的容器连接来验证网络隔离。请确保先运行 terraform plan 和 terraform apply 创建资源后再执行该命令。
在为容器间通信配置了网络之后,下一个关键方面是数据持久化。
配置持久化卷
对于需要在容器重启后仍保留的数据,添加卷资源。在容器定义之前插入以下内容:
resource "docker_volume" "db_data" {
name = "postgres_data"
}技术支持
现在添加一个使用该卷的数据库容器:
resource "docker_container" "database" {
name = "postgres"
image = "postgres:15"
env = [
"POSTGRES_PASSWORD=mysecretpassword"
]
volumes {
volume_name = docker_volume.db_data.name
container_path = "/var/lib/postgresql/data"
}
}技术支持
这类受管卷由 Docker 创建和控制,存储在 Docker 的数据目录中。若需从主机共享配置文件, 请使用 bind mounts ,通过指定 host_path 而非 volume_name:
volumes {
host_path = "/opt/app/config"
container_path = "/etc/app/config"
read_only = true
}技术支持
在确保数据在容器重启后仍然保留之后,同样重要的是防止任何单个容器独占系统资源。
设置资源限制
除了存储之外,控制每个容器可消耗的 CPU 和内存对系统稳定性至关重要。为防止容器过度消耗资源,请应用资源限制。
虽然 Docker 允许就地更新部分资源限制,但 Terraform 的声明式模型通常需要替换容器以确保状态与配置一致。为防止在替换过程中出现停机,我们使用 create_before_destroy 生命周期规则。
重要提示: 必须移除固定的 name 参数,以避免在替换过程中发生命名冲突。可以选择使用 name_prefix 参数。
resource "docker_container" "api" {
name_prefix = "api-server-"
image = docker_image.nginx.image_id
memory = 512
cpu_shares = 1024
networks_advanced {
name = docker_network.app_network.name
}
lifecycle {
create_before_destroy = true
}
}技术支持
memory 参数以 MB 为单位限制内存。cpu_shares 的工作方式不同,它是在发生 CPU 争用时使用的相对权重。拥有 1024 份额的容器将获得 512 份额容器两倍的 CPU 时间,但仅在主机负载较高时生效。
对于硬 CPU 限制,您还可以使用 cpu_set 将容器固定到特定 CPU 核心:
cpu_set = "0,1"
}技术支持
通过运行 docker inspect background-worker --format='{{.HostConfig.Memory}} {{.HostConfig.CpuShares}} 验证这些限制,并检查 Memory 与 NanoCpus 的值是否与配置一致。
同样如上所述,您需要先运行 terraform plan 和 terraform apply 创建资源。
此时,main.tf 文件包含了提供商配置、一个镜像、一个网络、一个卷,以及三个具有不同配置的容器。
随着基础设施的增长,硬编码值会变得不灵活。本节将展示如何使用变量、模块和正确的机密处理,使配置具备可复用性。
使用输入变量
让配置更灵活的第一步,是将硬编码值抽取为变量,从而无需修改主代码即可轻松变更。在同一目录下创建一个名为 variables.tf 的新文件:
variable "web_port" {
description = "External port for web container"
type = number
default = 8080
validation {
condition = var.web_port > 1024 && var.web_port < 65535
error_message = "Port must be between 1024 and 65535."
}
}技术支持
现在在 main.tf 中更新 web 容器以使用该变量:
resource "docker_container" "web" {
name = "nginx-web"
image = docker_image.nginx.image_id
ports {
internal = 80
external = var.web_port
}
env = [
"NGINX_HOST=example.com",
"NGINX_PORT=80"
]
}技术支持
验证块可防止使用特权端口。若要覆盖默认值,请创建 terraform.tfvars 文件:
web_port = 9000技术支持
运行 terraform apply 时,Terraform 会自动从该文件读取值。或者,您也可以通过命令行传参:terraform apply -var="web_port=9000"。
创建可复用模块
变量解决了硬编码值的问题,但如果您需要多次部署包含所有资源的同一套模式,该怎么办?
这正是模块的强项。可以将模块看作蓝图,它将分散的部分打包为一个整体,使您能够像“盖章”一样一次性部署整套复杂配置。
首先创建模块目录结构:
mkdir -p modules/web-app技术支持
现在创建文件 modules/web-app/variables.tf 并添加以下内容:
variable "environment" {
description = "Environment name"
type = string
}
variable "port" {
description = "External port"
type = number
}技术支持
接着创建主文件 modules/web-app/main.tf,内容如下:
resource "docker_container" "app" {
name = "app-${var.environment}"
image = var.image_id
ports {
internal = 80
external = var.port
}
}技术支持
最后创建输出文件 modules/web-app/outputs.tf,主要用于在运行 terraform apply 后在终端显示所需的值:
output "container_ip" {
value = docker_container.app.network_data[0].ip_address
description = "IP address of the application container"
}技术支持
添加模块定义后,运行 terraform init 安装模块,然后运行 terraform apply,即可从同一模板创建两个容器。您应能在 Docker Desktop 中看到全部资源。
在通过模块建立了可复用的基础设施模式之后,配置管理还有一个至关重要的方面:安全地处理密码和 API 密钥等敏感信息。
处理敏感数据
数据库密码等敏感值不应被硬编码。您可以在 variables.tf 文件中使用变量并设置 sensitive = true:
variable "db_password" {
description = "Database root password"
type = string
sensitive = true
}技术支持
现在在 main.tf 中更新数据库容器,引用该数据库密码:
resource "docker_container" "database" {
name = "postgres"
image = "postgres:15"
env = [
"POSTGRES_PASSWORD=${var.db_password}"
]
volumes {
volume_name = docker_volume.db_data.name
container_path = "/var/lib/postgresql/data"
}
}技术支持
不要将密码写入 terraform.tfvars 并硬编码,改为将其设置为环境变量:
export TF_VAR_db_password = "your-secure-password"
terraform apply技术支持
sensitive = true 标志可防止 Terraform 在控制台输出中显示该值。
不过,这些值会以明文存储在 terraform.tfstate 中,因此为确保该文件安全,我们可以使用带加密的远程后端进行保护。下面来看看如何实现。
本节涵盖生产部署所必需的状态机制与自动化工作流。但在实施自动化之前,务必理解 Terraform 如何通过状态文件跟踪您的基础设施。
理解状态机制
状态是 Terraform 管理基础设施能力的基石。terraform.tfstate 文件将配置映射到实际的 Docker 容器 ID 与资源属性。运行 terraform apply 时,Terraform 会将该状态与配置进行对比,计算出所需的最小变更集。
使用 terraform show 可检查当前状态,它会显示每个受管资源。对于团队而言,本地状态文件会带来问题、冲突与潜在的数据丢失。解决方案是使用远程后端。
对于不使用云基建、专注于 Docker 的工作流, Terraform Cloud 的免费层非常理想。在 main.tf 的 terraform 部分中添加一个后端块:
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "~> 3.0"
}
}
backend "remote" {
organization = "your-org-name"
workspaces {
name = "docker-infrastructure"
}
}
}技术支持
在 Terraform 网站 创建一个免 费账户。然后运行 terraform login 使用提供的令牌进行认证,再运行 terraform init 迁移状态。这将在远程工作区提供状态锁定与团队协作,而无需云基础设施。
当然,也可以在 AWS 或 GCP 等云提供商上创建远 程后端。
自动化工作流
在配置并远程存储状态管理后,基础设施已为团队协作做好准备。最后一块拼图是通过 CI/CD 流水线实现部署流程自动化,确保变更一致且可审查。
首先,创建 .github/workflows/terraform.yml:
name : Terraform Docker Deploy
on :
pull_request :
branches : [ main]
push :
branches : [ main]
jobs :
terraform :
runs-on : ubuntu- latest
steps :
- uses : actions/checkout@v6
- name : Setup Terraform
uses : hashicorp/setup- terraform@v3
- name : Terraform Init
run : terraform init
- name : Terraform Plan
if : github.event_name == 'pull_request'
run : terraform plan
- name : Terraform Apply
if : github.event_name == 'push' && github.ref == 'refs/heads/main'
run : terraform apply - auto- approve技术支持
该工作流会在每个拉取请求上运行 terraform plan,向审阅者展示将发生哪些基础设施变更。当更改合并到 main 分支时,会自动执行 terraform apply,确保每次基础设施变更都经过与代码变更相同的审查流程。
即使配置谨慎,问题仍可能出现。了解常见问题以及何时考虑替代方案,有助于做出更优的架构决策。
“Permission denied”
Linux 上最常见的错误是在连接 Docker 套接字时出现 "permission denied"。这是由于用户缺乏访问 /var/run/docker.sock 的权限。通过将用户加入 docker 组来修复:
sudo usermod -aG docker $USER 技术支持
然后注销并重新登录。
WSL2 连接问题
在 Windows 的 WSL2 环境中,连接问题常见于套接字路径不同。请确保启用 Docker Desktop 的“Expose daemon on tcp://localhost:2375 without TLS”设置,然后在 main.tf 中更新提供商:
provider "docker" {
host = "tcp://localhost:2375"
}技术支持
私有仓库认证失败
对于私有 Docker 镜像仓库,请在提供商块中添加认证,将凭据定义为变量,并通过环境变量设置,以避免因硬编码导致的问题。
hclprovider "docker" {
host = "unix:///var/run/docker.sock"
registry_auth {
address = "registry.mycompany.com"
username = var.registry_username
password = var.registry_password
}
}技术支持
除了排查问题,同样重要的是了解在何种情况下,其他工具可能比 Terraform 更适合您的特定用例。
Kubernetes 提供了更为成熟的编排功能:
对于需要高可用性的复杂多节点部署,Kubernetes 是 更好的选择。Terraform 可用于配置 Kubernetes 基础设施,而 Helm 或 Kubernetes 清单则负责应用部署。
Ansible 在容器内部的配置管理方面表现出色。Terraform 负责配置基础设施,而 Ansible 管理软件配置和软件包安装。这两种工具常常配合使用:Terraform 创建 Docker 容器,Ansible 配置其中运行的应用程序。
对于简单的本 地开发环境,Docker Compose 仍是最务实的选择。Terraform 则擅长跨环境的可复现部署,以及当需要与其他基础设施组件集成时的场景。
为帮助您做出选择,我将最重 要的差异总结在下表中:
工具
核心优势
最佳使用场景
与 Terraform 的对比
Kubernetes
复杂的编排能力(自愈、高级调度、负载均衡)。
需要高可用性的复杂多节点部署。
互补:Terraform 负责配置基础设施(集群),Kubernetes 负责应用部署。
Ansible
配置管理、软件包安装与软件设置。
管理容器内部的软件与配置。
互补:Terraform 创建 Docker 容器,Ansible 配置其中运行的应用。
Docker Compose
简单、务实的基于项目的管理。
简单的本地开发环境。
替代:Compose 更易用于本地开发,但 Terraform 更适合跨环境的可复现部署。
如果您正在寻找可替代 Docker 的工具,请查 看我们的 Docker 替代方案精选 指南。
结语
将 Docker 纳入 Terraform 的生命周期管理,可将容器部署从手动流程转变为可复现、可版本控制的基础设施。在本教程中,我演示了如何循序渐进地构建完整的基础设施:从提供商与单个容器入手,再逐步加入网络、存储、变量、模块与自动化。
关键区别在于,Terraform 根据声明式配置来处理资源的配置、创建、更新与销毁。要实现自动伸缩与自愈等完整编排功能,需要使用 Kubernetes 或 Docker Swarm 等工具。Terraform 可用于配置这些编排器,但并不能替代它们。
下一步,建议查阅 Terraform Registry 上的 Docker 提供商官方文档 ,了解诸如 Swarm 服务、机密管理与插件配置等高级资源选项。
如果您希望亲手实践容器技术,建议报名我们的互动技能路径 使用 Docker 与 Kubernetes 的容器化与虚拟化 。
我曾花费无数时间在开发和生产环境中手动管理 Docker 容器。配置不一致、状态漂移,以及本地环境是否与生产一致的种种不确定,几乎困扰着每一次部署。直到我发现了 Terraform 的 Docker 提供商,一切才发生了改变。
在本教程中,我将带您将 Terraform 与 Docker 集成,创建可复现、可版本化的基础设施。您将学会如何创建容器、管理网络和卷、实施资源限制,并在团队中维护一致的状态。
无论您是在管理构建服务器还是编排微服务,请打开终端,让我们一起改变管理 Docker 基础设施的方式。
如果您是 Docker 新手,建议先从我们的 Introduction to Docker 课程开始。
什么是 Terraform 与 Docker 的集成?
Terraform 是一种基础设施即代码工具,通过声明式流程管理资源的生命周期。您无需运行单独的 Docker 命令,而是在配置文件中定义期望的基础设施状态。随后 Terraform 按照 plan-apply-destroy 的生命周期执行:计算所需更改、展示预览,并在获得批准后才应用。
Terraform Docker provider 是 HCL(HashiCorp 配置语言)与 Docker API 之间的翻译层。当您在配置中编写
resource “docker_container"时,提供商会将该声明转换为相应的 Docker API 调用。这种抽象意味着可以用与云基础设施相同的工作流来管理 Docker 资源。下面是 Terraform 与 Docker Compose 的不同之处。
Compose 通过基于项目的 YAML 文件来管理容器,但它不会维护一个跟踪实际存在资源的状态文件。
相比之下,Terraform 会将其创建的每个资源记录在
terraform.tfstate中,使其能够检测配置与实际之间的漂移。如果有人手动修改了容器,Terraform 会发现并将其恢复到定义的状态。这种方法在多种场景中大放异彩:
先决条件与安装
在深入 Terraform 配置之前,让我们先确认所需工具已正确安装和配置。
满足系统要求
首先,确认您的机器上 Docker Engine 正在运行。
Linux: 使用
docker ps检查macOS 或 Windows: 确保 Docker Desktop 已安装并正在运行
Terraform 提供商需要访问 Docker 守护进程,因此您需要确认您的用户拥有访问 Linux 上
/var/run/docker.sock(Unix 套接字)或 Windows 上命名管道的权限。接下来,从 HashiCorp 官方网站安装 Terraform CLI。Installing Terraform 会因操作系统而异:
Linux: 下载合适的软件包或使用发行版的包管理器。
macOS: 使用 Homebrew。先通过
brew tap hashicorp/tap安装 HashiCorp tap,再使用brew install hashicorp/tap/terraform安装 Terraform。Windows: 从 hashicorp.com 下载二进制文件并添加到 PATH,或使用 Chocolatey:
choco install terraform。安装完成后,运行
terraform version验证安装。在验证了 Docker 和 Terraform 之后,下一步是组织项目文件以便维护。
配置项目结构
清晰有序的结构可以让 Terraform 项目受益匪浅。通常我会为 Terraform 项目创建一个专用目录并初始化版本控制:
对于基础设施即代码,版本控制至关重要。对 Docker 基础设施的每一次更改都应被跟踪、审查并可回滚。我通常会将 Terraform 的工作目录和状态文件添加到
.gitignore文件中,以将其排除在版本控制之外:最后,我会创建一个空白的
main.tf文件来定义基础设施。此文件将包含提供商配置和资源定义。Terraform Docker 提供商与核心资源
在项目结构就绪后,是时候配置 Docker 提供商并开始定义资源了。
初始化 Docker 提供商
首先,在
main.tf中添加提供商配置:required_providers块指定 Terraform 应从何处下载提供商插件。使用版本约束(~> 3.0)可允许小版本更新,同时避免破坏性变更。对于远程 Docker 主机,您可以将host参数更改为类似tcp://192.168.1.100:2376的值。现在在项目根目录运行
terraform init。这会下载 Docker 提供商插件并存储在.terraform/providers/中。该命令会输出安装的确切版本,同时 Terraform 会创建一个锁定文件,确保团队成员间的提供商版本一致。管理容器镜像
在完成提供商初始化后,下一步是定义容器镜像。在创建容器之前,需要先管理它们所使用的镜像,因此需要定义一个镜像资源:
将该资源直接添加在提供商块之后。
keep_locally参数很重要,它可以防止我在运行terraform destroy时移除镜像。当多个容器共享同一镜像,或我希望在部署之间保留缓存镜像时,这非常有用。在生产部署中,请使用 SHA256 摘要固定镜像以确保不可变性:
该摘要可从容器镜像仓库获取。 对于 nginx 这类来自 Docker Hub 的镜像,您可以在 Docker 官方网站的 Tags 部分找到。使用摘要可保证每次部署的镜像二进制完全一致,即使有人用相同的标签推送了新镜像也不受影响。
配置 Docker 容器
定义镜像后,下一步是创建引用这些镜像的容器:
通过
docker_image.nginx.image_id来引用镜像。这会创建一个隐式依赖关系:在镜像存在之前,Terraform 不会创建容器。ports块将容器的 80 端口映射到主机的 8080 端口,相当于docker run -p 8080:80。环境变量以字符串形式放在
env列表中。如果任何参数发生变化,Terraform 会重新创建该容器,从而确保运行中的基础设施始终与配置一致。此时,运行
terraform plan查看 Terraform 将创建的内容,然后运行terraform apply来配置基础设施。Terraform 会创建镜像和容器。您可以在 Docker Desktop 中查看资源,或在终端运行docker ps进行验证。在 Terraform 中配置网络、存储与资源限制
现在您已经运行了一个基础容器并理解了核心工作流,让我们通过生产可用的特性扩展基础设施。接下来的部分将在此基础上添加网络、持久化存储和资源约束,这些都是实际部署中不可或缺的。
管理网络
为了在保持隔离的同时实现容器间通信,创建专用网络至关重要。下面演示如何创建隔离网络来控制容器连通性。将以下资源添加到
main.tf文件:现在添加一个使用该网络的第二个容器。在
web容器之后添加:这将创建一个专用的 bridge 网络,并将
api容器连接到该网络。同一网络上的容器可通过容器名作为主机名进行通信,非常适合微服务架构。通过运行
docker network inspect application_net并确认仅有预期的容器连接来验证网络隔离。请确保先运行terraform plan和terraform apply创建资源后再执行该命令。在为容器间通信配置了网络之后,下一个关键方面是数据持久化。
配置持久化卷
对于需要在容器重启后仍保留的数据,添加卷资源。在容器定义之前插入以下内容:
现在添加一个使用该卷的数据库容器:
这类受管卷由 Docker 创建和控制,存储在 Docker 的数据目录中。若需从主机共享配置文件,请使用 bind mounts,通过指定
host_path而非volume_name:在确保数据在容器重启后仍然保留之后,同样重要的是防止任何单个容器独占系统资源。
设置资源限制
除了存储之外,控制每个容器可消耗的 CPU 和内存对系统稳定性至关重要。为防止容器过度消耗资源,请应用资源限制。
虽然 Docker 允许就地更新部分资源限制,但 Terraform 的声明式模型通常需要替换容器以确保状态与配置一致。为防止在替换过程中出现停机,我们使用
create_before_destroy生命周期规则。重要提示: 必须移除固定的
name参数,以避免在替换过程中发生命名冲突。可以选择使用name_prefix参数。memory参数以 MB 为单位限制内存。cpu_shares的工作方式不同,它是在发生 CPU 争用时使用的相对权重。拥有 1024 份额的容器将获得 512 份额容器两倍的 CPU 时间,但仅在主机负载较高时生效。对于硬 CPU 限制,您还可以使用
cpu_set将容器固定到特定 CPU 核心:通过运行
docker inspect background-worker --format='{{.HostConfig.Memory}} {{.HostConfig.CpuShares}}验证这些限制,并检查 Memory 与 NanoCpus 的值是否与配置一致。同样如上所述,您需要先运行
terraform plan和terraform apply创建资源。此时,
main.tf文件包含了提供商配置、一个镜像、一个网络、一个卷,以及三个具有不同配置的容器。Terraform 中的变量、模块与机密
随着基础设施的增长,硬编码值会变得不灵活。本节将展示如何使用变量、模块和正确的机密处理,使配置具备可复用性。
使用输入变量
让配置更灵活的第一步,是将硬编码值抽取为变量,从而无需修改主代码即可轻松变更。在同一目录下创建一个名为
variables.tf的新文件:现在在
main.tf中更新 web 容器以使用该变量:验证块可防止使用特权端口。若要覆盖默认值,请创建
terraform.tfvars文件:运行
terraform apply时,Terraform 会自动从该文件读取值。或者,您也可以通过命令行传参:terraform apply -var="web_port=9000"。创建可复用模块
变量解决了硬编码值的问题,但如果您需要多次部署包含所有资源的同一套模式,该怎么办?
这正是模块的强项。可以将模块看作蓝图,它将分散的部分打包为一个整体,使您能够像“盖章”一样一次性部署整套复杂配置。
首先创建模块目录结构:
现在创建文件
modules/web-app/variables.tf并添加以下内容:接着创建主文件
modules/web-app/main.tf,内容如下:最后创建输出文件
modules/web-app/outputs.tf,主要用于在运行terraform apply后在终端显示所需的值:添加模块定义后,运行
terraform init安装模块,然后运行terraform apply,即可从同一模板创建两个容器。您应能在 Docker Desktop 中看到全部资源。处理敏感数据
数据库密码等敏感值不应被硬编码。您可以在
variables.tf文件中使用变量并设置sensitive = true:现在在
main.tf中更新数据库容器,引用该数据库密码:不要将密码写入
terraform.tfvars并硬编码,改为将其设置为环境变量:sensitive = true标志可防止 Terraform 在控制台输出中显示该值。不过,这些值会以明文存储在
terraform.tfstate中,因此为确保该文件安全,我们可以使用带加密的远程后端进行保护。下面来看看如何实现。Terraform 状态管理与 CI/CD 集成
本节涵盖生产部署所必需的状态机制与自动化工作流。但在实施自动化之前,务必理解 Terraform 如何通过状态文件跟踪您的基础设施。
理解状态机制
状态是 Terraform 管理基础设施能力的基石。
terraform.tfstate文件将配置映射到实际的 Docker 容器 ID 与资源属性。运行terraform apply时,Terraform 会将该状态与配置进行对比,计算出所需的最小变更集。使用
terraform show可检查当前状态,它会显示每个受管资源。对于团队而言,本地状态文件会带来问题、冲突与潜在的数据丢失。解决方案是使用远程后端。对于不使用云基建、专注于 Docker 的工作流,Terraform Cloud 的免费层非常理想。在
main.tf的terraform部分中添加一个后端块:在 Terraform 网站创建一个免费账户。然后运行
terraform login使用提供的令牌进行认证,再运行terraform init迁移状态。这将在远程工作区提供状态锁定与团队协作,而无需云基础设施。当然,也可以在 AWS 或 GCP 等云提供商上创建远程后端。
自动化工作流
在配置并远程存储状态管理后,基础设施已为团队协作做好准备。最后一块拼图是通过 CI/CD 流水线实现部署流程自动化,确保变更一致且可审查。
首先,创建
.github/workflows/terraform.yml:该工作流会在每个拉取请求上运行
terraform plan,向审阅者展示将发生哪些基础设施变更。当更改合并到 main 分支时,会自动执行terraform apply,确保每次基础设施变更都经过与代码变更相同的审查流程。排查 Terraform Docker 问题
即使配置谨慎,问题仍可能出现。了解常见问题以及何时考虑替代方案,有助于做出更优的架构决策。
“Permission denied”
Linux 上最常见的错误是在连接 Docker 套接字时出现 "permission denied"。这是由于用户缺乏访问
/var/run/docker.sock的权限。通过将用户加入docker组来修复:然后注销并重新登录。
WSL2 连接问题
在 Windows 的 WSL2 环境中,连接问题常见于套接字路径不同。请确保启用 Docker Desktop 的“Expose daemon on tcp://localhost:2375 without TLS”设置,然后在
main.tf中更新提供商:私有仓库认证失败
对于私有 Docker 镜像仓库,请在提供商块中添加认证,将凭据定义为变量,并通过环境变量设置,以避免因硬编码导致的问题。
Terraform 的替代方案
除了排查问题,同样重要的是了解在何种情况下,其他工具可能比 Terraform 更适合您的特定用例。
Terraform vs Kubernetes
Kubernetes 提供了更为成熟的编排功能:
对于需要高可用性的复杂多节点部署,Kubernetes 是更好的选择。Terraform 可用于配置 Kubernetes 基础设施,而 Helm 或 Kubernetes 清单则负责应用部署。
Terraform vs Ansible
Ansible 在容器内部的配置管理方面表现出色。Terraform 负责配置基础设施,而 Ansible 管理软件配置和软件包安装。这两种工具常常配合使用:Terraform 创建 Docker 容器,Ansible 配置其中运行的应用程序。
Terraform vs Docker Compose
对于简单的本地开发环境,Docker Compose 仍是最务实的选择。Terraform 则擅长跨环境的可复现部署,以及当需要与其他基础设施组件集成时的场景。
为帮助您做出选择,我将最重要的差异总结在下表中:
工具
核心优势
最佳使用场景
与 Terraform 的对比
Kubernetes
复杂的编排能力(自愈、高级调度、负载均衡)。
需要高可用性的复杂多节点部署。
互补:Terraform 负责配置基础设施(集群),Kubernetes 负责应用部署。
Ansible
配置管理、软件包安装与软件设置。
管理容器内部的软件与配置。
互补:Terraform 创建 Docker 容器,Ansible 配置其中运行的应用。
Docker Compose
简单、务实的基于项目的管理。
简单的本地开发环境。
替代:Compose 更易用于本地开发,但 Terraform 更适合跨环境的可复现部署。
如果您正在寻找可替代 Docker 的工具,请查看我们的 Docker 替代方案精选 指南。
结语
将 Docker 纳入 Terraform 的生命周期管理,可将容器部署从手动流程转变为可复现、可版本控制的基础设施。在本教程中,我演示了如何循序渐进地构建完整的基础设施:从提供商与单个容器入手,再逐步加入网络、存储、变量、模块与自动化。
关键区别在于,Terraform 根据声明式配置来处理资源的配置、创建、更新与销毁。要实现自动伸缩与自愈等完整编排功能,需要使用 Kubernetes 或 Docker Swarm 等工具。Terraform 可用于配置这些编排器,但并不能替代它们。
下一步,建议查阅 Terraform Registry 上的 Docker 提供商官方文档,了解诸如 Swarm 服务、机密管理与插件配置等高级资源选项。
如果您希望亲手实践容器技术,建议报名我们的互动技能路径 使用 Docker 与 Kubernetes 的容器化与虚拟化。