Skip to content

Commit 1f528b4

Browse files
Copilotlonegunmanb
andcommitted
Add Azure (miniblue) version of terraform init Killercoda lab
Agent-Logs-Url: https://github.com/lonegunmanb/terraform-tutorial/sessions/152de788-b595-4fa7-8c49-1db7abd60a96 Co-authored-by: lonegunmanb <2233414+lonegunmanb@users.noreply.github.com>
1 parent 95820a7 commit 1f528b4

13 files changed

Lines changed: 805 additions & 1 deletion

File tree

docs/terraform-cli-init.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,4 +188,10 @@ terraform init -input=false -lockfile=readonly
188188
- `-input=false`:禁用交互式输入(CI 中无法交互)
189189
- `-lockfile=readonly`:防止 CI 意外修改锁文件
190190

191-
<KillercodaEmbed src="https://killercoda.com/lonegunman-terraform-tutorial/course/terraform-tutorial/terraform-cli-init" />
191+
<KillercodaEmbed src="https://killercoda.com/lonegunman-terraform-tutorial/course/terraform-tutorial/terraform-cli-init" title="实验环境(AWS / LocalStack 版)" />
192+
193+
<KillercodaEmbed
194+
src="https://killercoda.com/lonegunman-terraform-tutorial/course/terraform-tutorial/terraform-cli-init-azure"
195+
title="实验环境(Azure / miniblue 版)"
196+
desc="点击下方按钮在新标签页中打开 Azure 版 Killercoda 实验环境,预装了 Terraform + miniblue(Azure 本地模拟器),所有 init 子命令与参数与 AWS 版完全一致,远端状态存储改用 azurerm backend(Azure Blob Container)。"
197+
/>

terraform-tutorial/structure.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
{ "path": "terraform-cli-basics" },
2222
{ "path": "terraform-cli-basics-azure" },
2323
{ "path": "terraform-cli-init" },
24+
{ "path": "terraform-cli-init-azure" },
2425
{ "path": "terraform-cli-plan" },
2526
{ "path": "terraform-cli-apply" },
2627
{ "path": "terraform-cli-apply-azure" },
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
services:
2+
miniblue:
3+
# 使用 fork 后的镜像(兼容 azurerm v4),版本通过 SHA tag 锁定
4+
image: ghcr.io/lonegunmanb/miniblue:sha-8cc1c25
5+
ports:
6+
- "4566:4566"
7+
- "4567:4567"
8+
environment:
9+
- MINIBLUE_STORAGE_ENDPOINT=http://localhost:4566
10+
- MINIBLUE_DISABLE_SHAREDKEY_AUTH=1
11+
deploy:
12+
resources:
13+
limits:
14+
memory: 512M
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
terraform {
2+
required_version = ">= 1.0"
3+
required_providers {
4+
azurerm = {
5+
source = "hashicorp/azurerm"
6+
version = "~> 4.0"
7+
}
8+
}
9+
}
10+
11+
provider "azurerm" {
12+
features {}
13+
14+
# miniblue HTTPS metadata endpoint
15+
metadata_host = "localhost:4567"
16+
resource_provider_registrations = "none"
17+
18+
# miniblue 接受任意凭据
19+
subscription_id = "00000000-0000-0000-0000-000000000000"
20+
tenant_id = "00000000-0000-0000-0000-000000000001"
21+
client_id = "miniblue"
22+
client_secret = "miniblue"
23+
}
24+
25+
variable "environment" {
26+
type = string
27+
default = "dev"
28+
description = "部署环境(dev / staging / prod)"
29+
}
30+
31+
resource "azurerm_resource_group" "demo" {
32+
name = "init-demo-${var.environment}-rg"
33+
location = "East US"
34+
tags = {
35+
Environment = var.environment
36+
ManagedBy = "Terraform"
37+
}
38+
}
39+
40+
output "resource_group" {
41+
value = azurerm_resource_group.demo.name
42+
description = "演示用 Resource Group 名称"
43+
}
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
#!/bin/bash
2+
# ─────────────────────────────────────────────────────────
3+
# setup-common.sh — shared setup functions for Killercoda scenarios
4+
#
5+
# This file is the SINGLE SOURCE OF TRUTH for common setup logic.
6+
# It is copied into each scenario's assets/ directory by:
7+
# npm run sync-setup (or automatically via prebuild)
8+
#
9+
# Usage in background.sh:
10+
# source /root/setup-common.sh
11+
# install_terraform
12+
# install_awscli # optional — AWS CLI v2 + awslocal wrapper
13+
# install_tflint # optional — only in scenarios that need it
14+
# start_localstack
15+
# install_theia_plugin
16+
# finish_setup
17+
# ─────────────────────────────────────────────────────────
18+
19+
TERRAFORM_VERSION="${TERRAFORM_VERSION:-1.14.8}"
20+
TFLINT_VERSION="${TFLINT_VERSION:-v0.61.0}"
21+
TERRAGRUNT_VERSION="${TERRAGRUNT_VERSION:-0.77.5}"
22+
# Note: the miniblue container image tag is pinned in scripts/miniblue-image.mjs
23+
# and propagated into every docker-compose.yml by `npm run sync-miniblue`.
24+
# There is no shell-side miniblue version variable — start_miniblue just runs
25+
# `docker compose up -d` against the pinned compose file.
26+
27+
install_terraform() {
28+
if ! command -v unzip > /dev/null 2>&1; then
29+
apt-get update -qq && apt-get install -y -qq unzip > /dev/null 2>&1
30+
fi
31+
32+
# Install Docker Compose v2 plugin (binary download — apt package not available on Killercoda)
33+
if ! docker compose version > /dev/null 2>&1; then
34+
mkdir -p /usr/local/lib/docker/cli-plugins
35+
curl --connect-timeout 10 --max-time 120 -fsSL \
36+
"https://github.com/docker/compose/releases/latest/download/docker-compose-linux-x86_64" \
37+
-o /usr/local/lib/docker/cli-plugins/docker-compose
38+
chmod +x /usr/local/lib/docker/cli-plugins/docker-compose
39+
fi
40+
41+
curl --connect-timeout 10 --max-time 120 -fsSL \
42+
"https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip" \
43+
-o /tmp/terraform.zip \
44+
&& unzip -o -q /tmp/terraform.zip -d /usr/local/bin/ \
45+
&& chmod +x /usr/local/bin/terraform \
46+
&& rm -f /tmp/terraform.zip
47+
48+
terraform version || echo "WARNING: terraform install failed"
49+
}
50+
51+
install_tflint() {
52+
curl --connect-timeout 10 --max-time 120 -fsSL \
53+
"https://github.com/terraform-linters/tflint/releases/download/${TFLINT_VERSION}/tflint_linux_amd64.zip" \
54+
-o /tmp/tflint.zip \
55+
&& unzip -o -q /tmp/tflint.zip -d /usr/local/bin/ \
56+
&& chmod +x /usr/local/bin/tflint \
57+
&& rm -f /tmp/tflint.zip
58+
59+
tflint --version || echo "WARNING: tflint install failed"
60+
}
61+
62+
install_terragrunt() {
63+
curl --connect-timeout 10 --max-time 120 -fsSL \
64+
"https://github.com/gruntwork-io/terragrunt/releases/download/v${TERRAGRUNT_VERSION}/terragrunt_linux_amd64" \
65+
-o /usr/local/bin/terragrunt \
66+
&& chmod +x /usr/local/bin/terragrunt
67+
68+
terragrunt --version || echo "WARNING: terragrunt install failed"
69+
}
70+
71+
install_awscli() {
72+
# Install AWS CLI v2 (official binary)
73+
curl --connect-timeout 10 --max-time 120 -fsSL \
74+
"https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" \
75+
-o /tmp/awscliv2.zip \
76+
&& unzip -o -q /tmp/awscliv2.zip -d /tmp/ \
77+
&& /tmp/aws/install --update > /dev/null 2>&1 \
78+
&& rm -rf /tmp/awscliv2.zip /tmp/aws
79+
80+
aws --version || echo "WARNING: awscli install failed"
81+
82+
# Disable AWS CLI pager globally so output prints directly
83+
mkdir -p /root/.aws
84+
cat > /root/.aws/config <<'AWSCFG'
85+
[default]
86+
region = us-east-1
87+
output = json
88+
cli_pager =
89+
AWSCFG
90+
91+
# Install awscli-local (provides the 'awslocal' command)
92+
pip3 install --break-system-packages awscli-local > /dev/null 2>&1 \
93+
|| {
94+
# Fallback: create a shell wrapper if pip fails
95+
cat > /usr/local/bin/awslocal <<'WRAPPER'
96+
#!/bin/bash
97+
export AWS_PAGER=""
98+
exec aws --endpoint-url=http://localhost:4566 --region us-east-1 "$@"
99+
WRAPPER
100+
chmod +x /usr/local/bin/awslocal
101+
}
102+
103+
awslocal --version || echo "WARNING: awslocal install failed"
104+
}
105+
106+
start_localstack() {
107+
cd /root/workspace
108+
docker compose up -d
109+
110+
echo "Waiting for LocalStack to be ready..."
111+
for i in $(seq 1 60); do
112+
if curl -sf http://localhost:4566/_localstack/health > /dev/null 2>&1; then
113+
echo "LocalStack is ready."
114+
return 0
115+
fi
116+
sleep 2
117+
done
118+
echo "WARNING: LocalStack did not become healthy within 120 seconds"
119+
docker compose logs
120+
}
121+
122+
start_miniblue() {
123+
cd /root/workspace
124+
mkdir -p /root/.miniblue
125+
docker compose up -d
126+
127+
echo "Waiting for miniblue to be ready..."
128+
for i in $(seq 1 60); do
129+
if curl -sf http://localhost:4566/health > /dev/null 2>&1; then
130+
echo "miniblue is ready."
131+
# Trigger an HTTPS request so miniblue generates its self-signed cert.
132+
curl -sk https://localhost:4567/health > /dev/null 2>&1 || true
133+
134+
# Locate cert inside the container and copy to host.
135+
# The miniblue image is distroless (no shell), so we can't `docker exec`
136+
# to probe — `docker cp` itself is sufficient and silently fails on
137+
# missing paths. Try known paths in order.
138+
local cid
139+
cid=$(docker compose ps -q miniblue 2>/dev/null)
140+
for j in $(seq 1 30); do
141+
if [ -n "$cid" ]; then
142+
for p in /home/nonroot/.miniblue/cert.pem /root/.miniblue/cert.pem /app/.miniblue/cert.pem; do
143+
if docker cp "$cid:$p" /root/.miniblue/cert.pem 2>/dev/null; then
144+
break 2
145+
fi
146+
done
147+
fi
148+
sleep 1
149+
done
150+
151+
if [ ! -f /root/.miniblue/cert.pem ]; then
152+
echo "WARNING: failed to copy miniblue cert.pem out of container"
153+
else
154+
chmod 644 /root/.miniblue/cert.pem
155+
fi
156+
157+
# Make SSL_CERT_FILE available for all interactive shells
158+
cat > /etc/profile.d/miniblue.sh <<'PROF'
159+
export SSL_CERT_FILE=/root/.miniblue/cert.pem
160+
PROF
161+
chmod +x /etc/profile.d/miniblue.sh
162+
# Killercoda terminals are non-login interactive shells — they only source
163+
# ~/.bashrc, not /etc/profile.d/*. Append the export there too (idempotent).
164+
if ! grep -q 'SSL_CERT_FILE=/root/.miniblue/cert.pem' /root/.bashrc 2>/dev/null; then
165+
echo 'export SSL_CERT_FILE=/root/.miniblue/cert.pem' >> /root/.bashrc
166+
fi
167+
export SSL_CERT_FILE=/root/.miniblue/cert.pem
168+
return 0
169+
fi
170+
sleep 2
171+
done
172+
echo "WARNING: miniblue did not become healthy within 120 seconds"
173+
docker compose logs
174+
}
175+
176+
finish_setup() {
177+
touch /tmp/.setup-done
178+
}
179+
180+
install_azlocal() {
181+
# azlocal is a standalone Go CLI bundled inside the miniblue image at /azlocal
182+
# (analogous to awslocal for LocalStack). It uses HTTP on port 4566 and needs
183+
# no certificate or az CLI. Requires miniblue to be running.
184+
local cid
185+
cid=$(docker compose ps -q miniblue 2>/dev/null)
186+
if [ -z "$cid" ]; then
187+
cid=$(docker ps -q --filter "name=miniblue" | head -1)
188+
fi
189+
if [ -n "$cid" ]; then
190+
docker cp "$cid:/azlocal" /usr/local/bin/azlocal 2>/dev/null \
191+
&& chmod +x /usr/local/bin/azlocal
192+
fi
193+
azlocal --help > /dev/null 2>&1 || echo "WARNING: azlocal install failed"
194+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# 恭喜你完成了 terraform init 实战练习(Azure 版)!
2+
3+
## 你已掌握的知识
4+
5+
| 知识点 | 要点 |
6+
|--------|------|
7+
| terraform init 的作用 | 初始化工作目录:backend、模块、provider |
8+
| .terraform/ 目录 | 缓存已下载的 provider 插件和模块代码 |
9+
| .terraform.lock.hcl | 锁定 provider 版本和校验和,应提交到代码仓库 |
10+
| 幂等性 | terraform init 可安全重复执行,不会破坏资源或状态 |
11+
| -upgrade | 重新检查并升级 provider 到最新兼容版本 |
12+
| -lockfile=readonly | CI/CD 中禁止修改锁文件,确保版本一致性 |
13+
| -migrate-state | 切换 backend 时跳过交互式询问并自动迁移状态(等同于自动回答 yes) |
14+
| -force-copy | 同 -migrate-state,跳过迁移确认;两者效果相同 |
15+
| -reconfigure | 重置 backend 配置,跳过状态迁移 |
16+
17+
## 关于 azurerm Backend
18+
19+
- 状态以 blob 形式存储在 Azure Storage Account → Container 中(本课程使用 miniblue 模拟)
20+
- 状态锁通过 Azure Blob 的 lease(租约)机制实现,无需额外的"锁表"
21+
- backend 块中的 resource_group_name / storage_account_name / container_name / key 四件套定位状态 blob
22+
- 在真实 Azure 项目中,凭据应通过 ARM_* 环境变量或 -backend-config 文件注入,不应硬编码到代码中
23+
24+
## 下一步
25+
26+
继续阅读 Terraform CLI 章节,学习其他子命令的用法。也可以前往 [Terraform Backend 配置(Azure 版)](https://killercoda.com/lonegunman-terraform-tutorial/course/terraform-tutorial/terraform-backend-azure) 深入学习 azurerm backend 的部分配置、状态锁与 force-unlock 等高级特性。
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"title": "terraform init(Azure / miniblue 版):初始化工作目录与依赖管理",
3+
"description": "通过 miniblue 模拟的 Azure 环境(Storage Account + Blob Container),掌握 terraform init 的三大核心能力:初始化工作目录、管理 Provider 锁文件、切换 Backend 迁移状态",
4+
"details": {
5+
"intro": {
6+
"text": "init/init.md",
7+
"background": "init/background.sh",
8+
"foreground": "init/foreground.sh"
9+
},
10+
"steps": [
11+
{
12+
"title": "初次初始化工作目录:.terraform/ 与 .terraform.lock.hcl",
13+
"text": "step1/text.md"
14+
},
15+
{
16+
"title": "Provider 锁文件管理:-upgrade 与 -lockfile=readonly",
17+
"text": "step2/text.md"
18+
},
19+
{
20+
"title": "切换 Backend 与状态迁移:-migrate-state 与 -reconfigure",
21+
"text": "step3/text.md"
22+
}
23+
],
24+
"finish": {
25+
"text": "finish/finish.md"
26+
},
27+
"assets": {
28+
"host01": [
29+
{"file": "setup-common.sh", "target": "/root", "chmod": "+x"},
30+
{"file": "main.tf", "target": "/root/workspace"},
31+
{"file": "docker-compose.yml", "target": "/root/workspace"}
32+
]
33+
}
34+
},
35+
"backend": {
36+
"imageid": "ubuntu"
37+
},
38+
"interface": {
39+
"layout": "editor-terminal"
40+
}
41+
}

0 commit comments

Comments
 (0)