前言:
最近有机会与朋友聊到IaC(Infra as code)说到是否有比较好的切入点进行学习。
客观地说,看到XaX或XasX结构的的名词,让人立刻会与最前沿的云技术联系起来,但实际上其背后的思想仍然来自于传统系统的痛点与经验总结。IaC需要达到的目的就好像我们过去做过的这类事情:
电脑城鼎盛时期: 我们用统一的ghost分区或者硬盘镜像去确保交付给顾客的电脑上操作系统与软件配置的严格统一性。可以帮助节省出货与售后成本。
企业虚拟化时期: 我们利用VM为各种应用场景制作统一规范的VM模版,为横向与纵向扩展提供了一键部署特定VM的方式,极大提高部署效率与高度一致性。节省硬件与人力成本。
容器技术时期: 我们利用自定义的docker image或者在k8s中,为企业内开发运维人员部署高度一致的pod与container,作为测试与UAT环境,同时借助dockerfile,迭代起来比VM更轻量级也更灵活。
云上时期: 我们利用各家产品的管理组件,例如Azure中利用ARM(Azure Resource Manager)模版进行infra资源的部署,目的是为了避免环境不一致性与bug的不可复现性等等。
所有这些事情,其最后的作用都指向一个点:消除基础架构中的环境偏移问题。
而这一点,就是IaC出现的初衷。它的优势包括但不限于:
1)通过更轻松地跟踪部署内容、时间和方式来促进审核。(换句话说,它提高了可跟踪性。)
2)在不同发布之间提供一致的环境。
3)提高开发、测试和生产环境之间的一致性。
4)自动化纵向扩展和横向扩展过程。
5)允许对配置进行版本控制。
6)提供代码评审和单元测试功能,帮助管理基础结构更改。
7)使用不可变服务进程,即如果需要对环境进行更改,则会部署新服务并移除旧服务,而不会更新服务。
8)允许蓝/绿部署。此发布方法可以将故障时间降至最低,其中存在两个相同的环境,一个是实时环境,另一个则不是。更新将应用于非实时服务器。验证并完成测试后,它将与不同的实时服务器进行交换。届时,该服务器将变成新的实时环境,而先前的实时环境不再是实时的。此方法也称为A/B部署。
9)将基础结构视为一种灵活的资源,可按需进行预配、取消预配和重新预配。
那么,攻城狮应该如何向女友解释IaC呢?
从IaC的过程来举例:一个卖家想卖房,却不熟悉中间诸多法规与环节(备案,抵押,解押,面签,过户 等等等),也不敢自己全程操作。为了避免学习成本同时保证安全性,他会找到中介,因为他们会全权代理中间的所有步骤,而卖家只需要告诉其需求:价格与时限。 IaC就是那个中介。
从IaC的结果来举例:A觉得B比自己优秀,通常A会默默观察B在服装,装扮,举止上的特点,并记在小本子上,认真模仿。当然现实中A有大概率还是成为不了B,但是IaC却可以,可以经由相同的过程得到完全一致的结果。
什么?你还是觉得难以理解? 那可能是因为你还没有一个攻城狮的男朋友 :P
IaC初体验之Terraform for Docker篇
好了,来看看入坑的初体验之 Docker篇。
Q1: 为什么是Docker?
A1:因为它免费呀,云账号很难找可以免费把玩的(试用期已用完)。过去借助VM,想学啥,就自己从零(指的是从裸机装OS开始)搭建的日子,受益匪浅,但是不适合云环境的学习了。
Q2: 为什么是Terraform?
A2: 开源,用的人多,因为它不局限于某一种云产品。
1.Overall Prerequisites
bond@BondMacProM1Pro terraform % terraform -version
Terraform v1.5.7
on darwin_arm64
bond@BondMacProM1Pro terraform % docker -v
Docker version 24.0.6, build ed223bc
bond@BondMacProM1Pro terraform % tree
.
├── bin
│ └── terraform
├── learn-terraform-docker-container
│ ├── main.tf
│ ├── outputs.tf
│ ├── terraform.tfstate
│ ├── terraform.tfstate.backup
│ └── variables.tf
└── terraform_note.txt
2.Initialize the directory for Demo project
bond@BondMacProM1Pro learn-terraform-docker-container % cat main.tf
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "~> 3.0.1"
}
}
}
provider "docker" {
host="unix:///Users/bond/.docker/run/docker.sock"
}
resource "docker_image" "nginx" {
name = "nginx:latest"
keep_locally = false
}
resource "docker_container" "nginx" {
image = docker_image.nginx.image_id
# name = "tutorial"
name = var.container_name
ports {
internal = 80
external = 8080
}
}
bond@BondMacProM1Pro learn-terraform-docker-container % cat variables.tf
variable "container_name" {
description = "Value of the name for the Docker container"
type = string
default = "ExampleNginxContainer"
}
bond@BondMacProM1Pro learn-terraform-docker-container % cat outputs.tf
output "container_id" {
description = "ID of the Docker container"
value = docker_container.nginx.id
}
output "image_id" {
description = "ID of the Docker image"
value = docker_image.nginx.id
}
bond@BondMacProM1Pro learn-terraform-docker-container % terraform init
Initializing the backend...
Initializing provider plugins...
- Finding kreuzwerker/docker versions matching "~> 3.0.1"...
- Installing kreuzwerker/docker v3.0.2...
- Installed kreuzwerker/docker v3.0.2 (self-signed, key ID BD080C4571C6104C)
Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://www.terraform.io/docs/cli/plugins/signing.html
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
3.Format and validate the configuration
bond@BondMacProM1Pro learn-terraform-docker-container % terraform fmt
main.tf
bond@BondMacProM1Pro learn-terraform-docker-container % terraform validate
Success! The configuration is valid.
bond@BondMacProM1Pro learn-terraform-docker-container % docker context ls
NAME TYPE DESCRIPTION DOCKER ENDPOINT KUBERNETES ENDPOINT ORCHESTRATOR
default moby Current DOCKER_HOST based configuration unix:///var/run/docker.sock
desktop-linux * moby Docker Desktop unix:///Users/bond/.docker/run/docker.sock
provider "docker" {
host="unix:///Users/bond/.docker/run/docker.sock"
}
4.Create infrastructure
bond@BondMacProM1Pro learn-terraform-docker-container % terraform apply
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# docker_container.nginx will be created
+ resource "docker_container" "nginx" {
+ attach = false
+ bridge = (known after apply)
+ command = (known after apply)
+ container_logs = (known after apply)
+ container_read_refresh_timeout_milliseconds = 15000
+ entrypoint = (known after apply)
+ env = (known after apply)
+ exit_code = (known after apply)
+ hostname = (known after apply)
+ id = (known after apply)
+ image = (known after apply)
+ init = (known after apply)
+ ipc_mode = (known after apply)
+ log_driver = (known after apply)
+ logs = false
+ must_run = true
+ name = "tutorial"
+ network_data = (known after apply)
+ read_only = false
+ remove_volumes = true
+ restart = "no"
+ rm = false
+ runtime = (known after apply)
+ security_opts = (known after apply)
+ shm_size = (known after apply)
+ start = true
+ stdin_open = false
+ stop_signal = (known after apply)
+ stop_timeout = (known after apply)
+ tty = false
+ wait = false
+ wait_timeout = 60
+ ports {
+ external = 8000
+ internal = 80
+ ip = "0.0.0.0"
+ protocol = "tcp"
}
}
# docker_image.nginx will be created
+ resource "docker_image" "nginx" {
+ id = (known after apply)
+ image_id = (known after apply)
+ keep_locally = false
+ name = "nginx:latest"
+ repo_digest = (known after apply)
}
Plan: 2 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
docker_image.nginx: Creating...
docker_image.nginx: Still creating... [10s elapsed]
docker_image.nginx: Creation complete after 11s [id=sha256:2a4fbb36e96607b16e5af2e24dc6a1025a4795520c98c6b9ead9c4113617cb73nginx:latest]
docker_container.nginx: Creating...
docker_container.nginx: Creation complete after 1s [id=d5af79499d396a0f6f1e28b70ae53b62f73f1f28fdefb6483afed14b578f0422]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
bond@BondMacProM1Pro learn-terraform-docker-container % docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 2a4fbb36e966 6 days ago 192MB
bond@BondMacProM1Pro learn-terraform-docker-container % docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d5af79499d39 2a4fbb36e966 "/docker-entrypoint.…" 9 minutes ago Up 9 minutes 0.0.0.0:8000->80/tcp tutorial
bond@BondMacProM1Pro learn-terraform-docker-container % ls -lrt /Users/bond/Library/Containers/com.docker.docker/Data/vms/0/data
total 3902616
-rw-r--r--@ 1 bond staff 80000057344 9 27 16:17 Docker.raw
5.Verify
visit to verify: http://localhost:8000/
6.Inspect state
bond@BondMacProM1Pro learn-terraform-docker-container % terraform show
# docker_container.nginx:
resource "docker_container" "nginx" {
attach = false
command = [
"nginx",
"-g",
"daemon off;",
]
container_read_refresh_timeout_milliseconds = 15000
cpu_shares = 0
entrypoint = [
"/docker-entrypoint.sh",
]
env = []
hostname = "d5af79499d39"
id = "d5af79499d396a0f6f1e28b70ae53b62f73f1f28fdefb6483afed14b578f0422"
image = "sha256:2a4fbb36e96607b16e5af2e24dc6a1025a4795520c98c6b9ead9c4113617cb73"
init = false
ipc_mode = "private"
log_driver = "json-file"
logs = false
max_retry_count = 0
memory = 0
memory_swap = 0
must_run = true
name = "tutorial"
network_data = [
{
gateway = "172.17.0.1"
global_ipv6_address = ""
global_ipv6_prefix_length = 0
ip_address = "172.17.0.2"
ip_prefix_length = 16
ipv6_gateway = ""
mac_address = "02:42:ac:11:00:02"
network_name = "bridge"
},
]
network_mode = "default"
privileged = false
publish_all_ports = false
read_only = false
remove_volumes = true
restart = "no"
rm = false
runtime = "runc"
security_opts = []
shm_size = 64
start = true
stdin_open = false
stop_signal = "SIGQUIT"
stop_timeout = 0
tty = false
wait = false
wait_timeout = 60
ports {
external = 8000
internal = 80
ip = "0.0.0.0"
protocol = "tcp"
}
}
# docker_image.nginx:
resource "docker_image" "nginx" {
id = "sha256:2a4fbb36e96607b16e5af2e24dc6a1025a4795520c98c6b9ead9c4113617cb73nginx:latest"
image_id = "sha256:2a4fbb36e96607b16e5af2e24dc6a1025a4795520c98c6b9ead9c4113617cb73"
keep_locally = false
name = "nginx:latest"
repo_digest = "nginx@sha256:32da30332506740a2f7c34d5dc70467b7f14ec67d912703568daff790ab3f755"
}
bond@BondMacProM1Pro learn-terraform-docker-container % terraform state list
docker_container.nginx
docker_image.nginx
7.Update configuration
Change the docker_container.nginx resource under the provider block in main.tf by replacing the ports.external value of 8000 with 8080
7.1 Check change plan
bond@BondMacProM1Pro learn-terraform-docker-container % terraform plan
docker_image.nginx: Refreshing state... [id=sha256:2a4fbb36e96607b16e5af2e24dc6a1025a4795520c98c6b9ead9c4113617cb73nginx:latest]
docker_container.nginx: Refreshing state... [id=d5af79499d396a0f6f1e28b70ae53b62f73f1f28fdefb6483afed14b578f0422]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement
Terraform will perform the following actions:
# docker_container.nginx must be replaced
-/+ resource "docker_container" "nginx" {
+ bridge = (known after apply)
~ command = [
- "nginx",
- "-g",
...............................
...............................
- tmpfs = {} -> null
# (14 unchanged attributes hidden)
~ ports {
~ external = 8000 -> 8080 # forces replacement
# (3 unchanged attributes hidden)
}
}
Plan: 1 to add, 0 to change, 1 to destroy.
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
7.2 Implement change
bond@BondMacProM1Pro learn-terraform-docker-container % terraform apply
docker_image.nginx: Refreshing state... [id=sha256:2a4fbb36e96607b16e5af2e24dc6a1025a4795520c98c6b9ead9c4113617cb73nginx:latest]
docker_container.nginx: Refreshing state... [id=d5af79499d396a0f6f1e28b70ae53b62f73f1f28fdefb6483afed14b578f0422]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement
Terraform will perform the following actions:
# docker_container.nginx must be replaced
-/+ resource "docker_container" "nginx" {
+ bridge = (known after apply)
~ command = [
- "nginx",
- "-g",
...............................
...............................
~ stop_timeout = 0 -> (known after apply)
- storage_opts = {} -> null
- sysctls = {} -> null
- tmpfs = {} -> null
# (14 unchanged attributes hidden)
~ ports {
~ external = 8000 -> 8080 # forces replacement
# (3 unchanged attributes hidden)
}
}
Plan: 1 to add, 0 to change, 1 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
docker_container.nginx: Destroying... [id=d5af79499d396a0f6f1e28b70ae53b62f73f1f28fdefb6483afed14b578f0422]
docker_container.nginx: Destruction complete after 0s
docker_container.nginx: Creating...
docker_container.nginx: Creation complete after 0s [id=9c7f7fb0fb52ad18652b6d12eb32fe008aca27e5b88509c12c863892bb65064a]
Apply complete! Resources: 1 added, 0 changed, 1 destroyed.
bond@BondMacProM1Pro learn-terraform-docker-container % docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9c7f7fb0fb52 2a4fbb36e966 "/docker-entrypoint.…" 4 minutes ago Up 4 minutes 0.0.0.0:8080->80/tcp tutorial
8.Destroy infrastructure
bond@BondMacProM1Pro learn-terraform-docker-container % terraform destroy
docker_image.nginx: Refreshing state... [id=sha256:2a4fbb36e96607b16e5af2e24dc6a1025a4795520c98c6b9ead9c4113617cb73nginx:latest]
docker_container.nginx: Refreshing state... [id=9c7f7fb0fb52ad18652b6d12eb32fe008aca27e5b88509c12c863892bb65064a]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
# docker_container.nginx will be destroyed
- resource "docker_container" "nginx" {
- attach = false -> null
- command = [
- "nginx",
...............................
...............................
# docker_image.nginx will be destroyed
- resource "docker_image" "nginx" {
- id = "sha256:2a4fbb36e96607b16e5af2e24dc6a1025a4795520c98c6b9ead9c4113617cb73nginx:latest" -> null
- image_id = "sha256:2a4fbb36e96607b16e5af2e24dc6a1025a4795520c98c6b9ead9c4113617cb73" -> null
- keep_locally = false -> null
- name = "nginx:latest" -> null
- repo_digest = "nginx@sha256:32da30332506740a2f7c34d5dc70467b7f14ec67d912703568daff790ab3f755" -> null
}
Plan: 0 to add, 0 to change, 2 to destroy.
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
docker_container.nginx: Destroying... [id=9c7f7fb0fb52ad18652b6d12eb32fe008aca27e5b88509c12c863892bb65064a]
docker_container.nginx: Destruction complete after 0s
docker_image.nginx: Destroying... [id=sha256:2a4fbb36e96607b16e5af2e24dc6a1025a4795520c98c6b9ead9c4113617cb73nginx:latest]
docker_image.nginx: Destruction complete after 0s
Destroy complete! Resources: 2 destroyed.
bond@BondMacProM1Pro learn-terraform-docker-container % docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9.Define input variables
Terraform configurations can include variables to make your configuration more dynamic and flexible.
Note:Terraform loads all files in the current directory ending in .tf, so you can name your configuration files however you choose.
Create a new file called variables.tf with a block defining a new container_name variable.
In main.tf, update the docker_container resource block to use the new variable. The container_name variable block will default to its default value ("ExampleNginxContainer") unless you declare a different value.
resource "docker_container" "nginx" {
image = docker_image.nginx.image_id
# name = "tutorial"
name = var.container_name
ports {
internal = 80
external = 8080
}
}
bond@BondMacProM1Pro learn-terraform-docker-container % terraform apply
docker_image.nginx: Refreshing state... [id=sha256:2a4fbb36e96607b16e5af2e24dc6a1025a4795520c98c6b9ead9c4113617cb73nginx:latest]
docker_container.nginx: Refreshing state... [id=baea93480dc7e21d938c3fdf8e7f4a7124c76b981ab28f44daf44584fa327258]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement
Terraform will perform the following actions:
# docker_container.nginx must be replaced
-/+ resource "docker_container" "nginx" {
...............................
...............................
Plan: 1 to add, 0 to change, 1 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
docker_container.nginx: Destroying... [id=baea93480dc7e21d938c3fdf8e7f4a7124c76b981ab28f44daf44584fa327258]
docker_container.nginx: Destruction complete after 0s
docker_container.nginx: Creating...
docker_container.nginx: Creation complete after 0s [id=694e4020413e7d3837ef69159d7ba9e2ac2f6aa824bfbf45e6b6c04150e81543]
Apply complete! Resources: 1 added, 0 changed, 1 destroyed.
Now apply the configuration again, this time overriding the default container name by passing in a variable using the -var flag. Terraform will update the container's name attribute with the new name.
bond@BondMacProM1Pro learn-terraform-docker-container % terraform apply -var "container_name=YetAnotherName"
bond@BondMacProM1Pro learn-terraform-docker-container % terraform show
.........
name = "YetAnotherName"
.........
bond@BondMacProM1Pro learn-terraform-docker-container % docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d8dc60162e03 2a4fbb36e966 "/docker-entrypoint.…" About a minute ago Up About a minute 0.0.0.0:8080->80/tcp YetAnotherName
bond@BondMacProM1Pro learn-terraform-docker-container % terraform apply
.........
Plan: 1 to add, 0 to change, 1 to destroy.
Changes to Outputs:
+ container_id = (known after apply)
+ image_id = "sha256:2a4fbb36e96607b16e5af2e24dc6a1025a4795520c98c6b9ead9c4113617cb73nginx:latest"
.........
10.The output comes from terraform show
bond@BondMacProM1Pro learn-terraform-docker-container % terraform output
container_id = "995c0dc8afdfd32e0cbd389444e45910f59027d155efd29fe5851988e5e5e841"
image_id = "sha256:2a4fbb36e96607b16e5af2e24dc6a1025a4795520c98c6b9ead9c4113617cb73nginx:latest"
#### End ####
后记:
1.可以发现IaC实际上是一个概念与方法论,具体实现需要依靠响应的产品,例如Terraform或者ARM等等。
2.针对不同的后端云产品,Docker,Azure,AWS,AliYun,GCP 等,主流的IaC工具均进行了适配,为每一类资源定义了可调整的配置接口。
3.有没有一种感觉,IaC确实降低了直接操作后端产品的门槛,例如:使用者并不需要知道如何run一个docker image并且按照需求暴露端口给guest。
4. 总的来说,还是一种高效(偷懒)的运维工具,客户们最喜欢了。
5. 如果你即使没有攻城狮的男朋友,也大致明白了什么是IaC,那就太棒了!
See you!
2023年9月28日
祝大家明天中秋快乐!国庆快乐!