第15章:自動化への道 - Infrastructure as Code
15.1 はじめに:手作業からの解放
深夜3時。新しいサーバーを追加する必要が生じました。従来なら:
- 管理コンソールにログイン
- サーバーの設定を一つずつ入力
- ネットワーク設定を手動で構成
- ソフトウェアをインストール
- 設定ファイルを編集
- サービスを起動
眠い目をこすりながらの作業。一つでも間違えれば、サービスに影響が出るかもしれない。
しかし今は違います。
terraform apply
たった一つのコマンドで、複雑なインフラストラクチャが正確に、再現可能な形で構築される。これがInfrastructure as Code(IaC)の世界です。
15.2 手作業の限界と人的ミスの排除
手作業の問題点
実例:ある企業の悲劇
2019年某日、大手企業のインフラエンジニアAさんの作業記録:
09:00 - 本番サーバー#1の設定変更開始
09:30 - 本番サーバー#2の設定変更開始
09:45 - 本番サーバー#3の設定変更開始
→ あれ?#2の設定、一つ抜けてない?
10:00 - サービス障害発生
→ #2だけ設定が異なり、ロードバランサーから外れる
10:30 - 原因特定、手動で修正
11:00 - サービス復旧
損失:1時間のダウンタイム、信頼の失墜
人的ミスの分類と対策
1. 設定ミス
# 手作業での典型的なミス
server1: memory=8GB, cpu=4
server2: memory=8GB, cpu=4
server3: memory=8G, cpu=4 # 単位の誤り!
# IaCでの防止
variable "server_config" {
type = object({
memory = string
cpu = number
})
default = {
memory = "8GB"
cpu = 4
}
validation {
condition = can(regex("^[0-9]+[GM]B$", var.server_config.memory))
error_message = "Memory must be in format like '8GB' or '512MB'."
}
}
2. 手順の抜け漏れ
# 手作業の手順書(人間はミスをする)
1. サーバーを作成
2. セキュリティグループを設定
3. ソフトウェアをインストール
4. 設定ファイルを編集
5. 監視設定を追加 ← よく忘れられる
# IaCでは依存関係が明確
resource "aws_instance" "web" {
# ...
}
resource "aws_cloudwatch_metric_alarm" "cpu" {
# EC2インスタンスに自動的に紐付けられる
dimensions = {
InstanceId = aws_instance.web.id
}
}
3. 環境間の差異
# 環境ごとの設定を変数で管理
variable "environment" {
type = string
}
locals {
instance_type = {
dev = "t3.micro"
stg = "t3.small"
prod = "t3.large"
}
}
resource "aws_instance" "app" {
instance_type = local.instance_type[var.environment]
# 環境に応じて自動的に適切なサイズが選択される
}
15.3 宣言的設定と冪等性
宣言的 vs 命令的
命令的アプローチ(従来のスクリプト)
# 命令的:「どうやって」作るかを記述
#!/bin/bash
# サーバーが既に存在するかチェックしない
# 2回実行すると2台作られる!
aws ec2 run-instances --instance-type t3.micro --count 1
# ファイアウォールルールを追加
# 既に存在していてもエラーチェックなし
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
宣言的アプローチ(IaC)
# 宣言的:「何が」欲しいかを記述
resource "aws_instance" "web" {
instance_type = "t3.micro"
count = 1
# タグで識別
tags = {
Name = "WebServer"
}
}
# 何度実行しても結果は同じ(冪等性)
# - 存在しなければ作成
# - 存在すれば何もしない
# - 設定が違えば修正
冪等性の重要性
冪等性とは
f(x) = f(f(x)) = f(f(f(x))) = ...
何度実行しても同じ結果になる性質
実例で理解する冪等性
# 冪等でない操作
counter += 1 # 実行するたびに値が変わる
# 冪等な操作
counter = 10 # 何度実行しても counter は 10
# インフラでの冪等性
desired_state = {
'servers': 3,
'load_balancer': 1,
'database': 'mysql'
}
current_state = get_current_infrastructure()
if current_state != desired_state:
apply_changes(desired_state)
# 既に望む状態なら何もしない
状態管理の仕組み
Terraformの状態管理
# terraform.tfstate - 現在の状態を記録
{
"version": 4,
"terraform_version": "1.0.0",
"resources": [
{
"mode": "managed",
"type": "aws_instance",
"name": "web",
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
"instances": [
{
"attributes": {
"id": "i-1234567890abcdef0",
"instance_type": "t3.micro",
"public_ip": "203.0.113.10",
"tags": {
"Name": "WebServer"
}
}
}
]
}
]
}
状態の同期
# 実際のインフラと状態ファイルの差分を確認
terraform plan
# 差分があれば表示される
# ~ resource "aws_instance" "web" {
# ~ instance_type = "t3.micro" -> "t3.small"
# }
# 差分を適用
terraform apply
15.4 再現可能で監査可能なインフラ
バージョン管理との統合
Gitによるインフラコードの管理
# ディレクトリ構造
infrastructure/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── terraform.tfvars
│ ├── staging/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── terraform.tfvars
│ └── production/
│ ├── main.tf
│ ├── variables.tf
│ └── terraform.tfvars
├── modules/
│ ├── vpc/
│ ├── compute/
│ └── database/
└── .gitignore
# バージョン管理
git add .
git commit -m "Add auto-scaling configuration for production"
git push origin main
コードレビューによる品質保証
# .github/workflows/terraform-pr.yml
name: Terraform Pull Request Check
on:
pull_request:
paths:
- 'infrastructure/**'
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Terraform
uses: hashicorp/setup-terraform@v1
- name: Terraform Format Check
run: terraform fmt -check -recursive
- name: Terraform Init
run: terraform init
- name: Terraform Validate
run: terraform validate
- name: Terraform Plan
run: terraform plan -out=tfplan
- name: Post Plan to PR
uses: actions/github-script@v6
with:
script: |
const plan = require('fs').readFileSync('tfplan.txt', 'utf8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## Terraform Plan\n\`\`\`\n${plan}\n\`\`\``
});
監査証跡(Audit Trail)
変更履歴の完全な記録
# すべての変更がGitに記録される
git log --oneline infrastructure/
# 出力例:
# a1b2c3d Add ALB for high availability
# d4e5f6g Increase instance size for production
# g7h8i9j Add CloudWatch alarms
# j1k2l3m Initial infrastructure setup
# 特定の変更の詳細を確認
git show a1b2c3d
# 誰が、いつ、何を、なぜ変更したかが明確
コンプライアンス対応
# ポリシーの強制
resource "aws_s3_bucket" "data" {
bucket = "company-sensitive-data"
# 暗号化を強制
server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
# バージョニングを有効化
versioning {
enabled = true
}
# ロギングを有効化
logging {
target_bucket = aws_s3_bucket.logs.id
target_prefix = "s3-access-logs/"
}
# パブリックアクセスをブロック
public_access_block {
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# タグによる管理
tags = {
Environment = var.environment
Compliance = "GDPR"
Owner = "DataTeam"
CreatedBy = "Terraform"
}
}
ドリフト検出と修正
手動変更の検出
# 実際のインフラとコードの差異を検出
terraform plan -refresh-only
# ドリフトが検出された場合の出力例:
# Note: Objects have changed outside of Terraform
#
# aws_security_group.web has been changed:
# ~ ingress = [
# + {
# + from_port = 8080
# + to_port = 8080
# + protocol = "tcp"
# + cidr_blocks = ["0.0.0.0/0"]
# },
# ]
# ドリフトの修正(コードに合わせる)
terraform apply -refresh-only
15.5 演習:手作業から自動化への段階的移行
演習1:既存インフラのコード化
# import_existing_infra.sh
cat > import_existing_infra.sh << 'EOF'
#!/bin/bash
echo "=== Importing Existing Infrastructure to Terraform ==="
# 1. 既存リソースの調査
echo "Discovering existing resources..."
# EC2インスタンスの一覧取得
instances=$(aws ec2 describe-instances \
--query 'Reservations[*].Instances[*].[InstanceId,Tags[?Key==`Name`].Value|[0]]' \
--output text)
# 2. Terraformコードの生成
cat > main.tf << 'TF'
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}
TF
# 3. 各インスタンスのインポート
while IFS=$'\t' read -r instance_id name; do
echo "Importing instance: $instance_id ($name)"
# Terraformリソース定義を追加
cat >> main.tf << TF
resource "aws_instance" "$name" {
# Imported resource
}
TF
# 実際のインポート
terraform import "aws_instance.$name" "$instance_id"
done <<< "$instances"
# 4. 設定の詳細を取得して更新
echo "Updating resource configurations..."
terraform plan
EOF
chmod +x import_existing_infra.sh
演習2:モジュール化による再利用性向上
# modules/web-server/main.tf
variable "instance_count" {
description = "Number of instances"
type = number
default = 1
}
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t3.micro"
}
variable "environment" {
description = "Environment name"
type = string
}
# Launch Template
resource "aws_launch_template" "web" {
name_prefix = "${var.environment}-web-"
image_id = data.aws_ami.amazon_linux_2.id
instance_type = var.instance_type
vpc_security_group_ids = [aws_security_group.web.id]
user_data = base64encode(<<-EOF
#!/bin/bash
yum update -y
yum install -y nginx
systemctl start nginx
systemctl enable nginx
# カスタムページ
cat > /usr/share/nginx/html/index.html <<-HTML
<h1>Welcome to ${var.environment} environment</h1>
<p>Server: $(hostname)</p>
<p>Instance Type: ${var.instance_type}</p>
HTML
EOF
)
tag_specifications {
resource_type = "instance"
tags = {
Name = "${var.environment}-web-server"
Environment = var.environment
ManagedBy = "Terraform"
}
}
}
# Auto Scaling Group
resource "aws_autoscaling_group" "web" {
name = "${var.environment}-web-asg"
vpc_zone_identifier = var.subnet_ids
target_group_arns = [aws_lb_target_group.web.arn]
health_check_type = "ELB"
health_check_grace_period = 300
min_size = var.instance_count
max_size = var.instance_count * 3
desired_capacity = var.instance_count
launch_template {
id = aws_launch_template.web.id
version = "$Latest"
}
tag {
key = "Name"
value = "${var.environment}-web-asg"
propagate_at_launch = false
}
}
# Application Load Balancer
resource "aws_lb" "web" {
name = "${var.environment}-web-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = var.public_subnet_ids
enable_deletion_protection = var.environment == "production"
tags = {
Name = "${var.environment}-web-alb"
Environment = var.environment
}
}
# Output
output "load_balancer_dns" {
description = "DNS name of the load balancer"
value = aws_lb.web.dns_name
}
output "autoscaling_group_name" {
description = "Name of the Auto Scaling group"
value = aws_autoscaling_group.web.name
}
演習3:CI/CDパイプラインの構築
# .gitlab-ci.yml - GitLab CI/CD設定
stages:
- validate
- plan
- apply
- destroy
variables:
TF_ROOT: ${CI_PROJECT_DIR}/infrastructure
TF_STATE_NAME: ${CI_PROJECT_NAME}-${CI_ENVIRONMENT_NAME}
before_script:
- cd ${TF_ROOT}
- terraform init
-backend-config="bucket=terraform-state-bucket"
-backend-config="key=${TF_STATE_NAME}/terraform.tfstate"
-backend-config="region=ap-northeast-1"
validate:
stage: validate
script:
- terraform fmt -check -recursive
- terraform validate
- tflint
- checkov -d . --framework terraform
rules:
- if: $CI_MERGE_REQUEST_ID
plan:development:
stage: plan
script:
- terraform workspace select development || terraform workspace new development
- terraform plan -var-file=environments/development.tfvars -out=plan.tfplan
- terraform show -no-color plan.tfplan > plan.txt
artifacts:
paths:
- ${TF_ROOT}/plan.tfplan
- ${TF_ROOT}/plan.txt
expire_in: 7 days
environment:
name: development
rules:
- if: $CI_COMMIT_BRANCH == "develop"
apply:development:
stage: apply
script:
- terraform workspace select development
- terraform apply -auto-approve plan.tfplan
dependencies:
- plan:development
environment:
name: development
on_stop: destroy:development
rules:
- if: $CI_COMMIT_BRANCH == "develop"
when: manual
plan:production:
stage: plan
script:
- terraform workspace select production || terraform workspace new production
- terraform plan -var-file=environments/production.tfvars -out=plan.tfplan
- terraform show -no-color plan.tfplan > plan.txt
artifacts:
paths:
- ${TF_ROOT}/plan.tfplan
- ${TF_ROOT}/plan.txt
expire_in: 7 days
environment:
name: production
rules:
- if: $CI_COMMIT_BRANCH == "main"
apply:production:
stage: apply
script:
- terraform workspace select production
- terraform apply -auto-approve plan.tfplan
# 適用後の検証
- ./scripts/validate_deployment.sh
dependencies:
- plan:production
environment:
name: production
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
only:
- main
destroy:development:
stage: destroy
script:
- terraform workspace select development
- terraform destroy -auto-approve -var-file=environments/development.tfvars
environment:
name: development
action: stop
rules:
- if: $CI_COMMIT_BRANCH == "develop"
when: manual
演習4:ポリシーアズコード
# policy.sentinel - Sentinelポリシー
import "tfplan/v2" as tfplan
import "tfconfig/v2" as tfconfig
# ポリシー1: インスタンスタイプの制限
instance_type_allowed = rule {
all tfplan.resource_changes as _, rc {
rc.type is not "aws_instance" or
rc.change.after.instance_type in [
"t3.micro",
"t3.small",
"t3.medium",
"m5.large",
"m5.xlarge"
]
}
}
# ポリシー2: 必須タグの確認
mandatory_tags = rule {
all tfplan.resource_changes as _, rc {
rc.mode is not "managed" or
rc.change.after.tags is null or
all [
"Environment",
"Owner",
"CostCenter",
"Project"
] as required_tag {
required_tag in keys(rc.change.after.tags)
}
}
}
# ポリシー3: 暗号化の強制
encryption_required = rule {
all tfplan.resource_changes as _, rc {
rc.type is not "aws_s3_bucket" or
rc.change.after.server_side_encryption_configuration is not null
}
}
# ポリシー4: パブリックアクセスの禁止
no_public_access = rule {
all tfplan.resource_changes as _, rc {
rc.type is not "aws_s3_bucket" or
(rc.change.after.acl is "private" and
rc.change.after.public_access_block is not null and
rc.change.after.public_access_block[0].block_public_acls is true)
}
}
# メインルール
main = rule {
instance_type_allowed and
mandatory_tags and
encryption_required and
no_public_access
}
演習5:災害復旧の自動化
# disaster_recovery.tf - 災害復旧の自動化
module "primary_region" {
source = "./modules/regional-infrastructure"
region = "ap-northeast-1"
environment = var.environment
vpc_cidr = "10.0.0.0/16"
enable_backup = true
backup_retention_days = 30
}
module "dr_region" {
source = "./modules/regional-infrastructure"
region = "ap-southeast-1"
environment = "${var.environment}-dr"
vpc_cidr = "10.1.0.0/16"
# DRリージョンは最小構成
instance_count = var.dr_mode ? var.instance_count : 0
instance_type = var.dr_mode ? var.instance_type : "t3.micro"
}
# クロスリージョンレプリケーション
resource "aws_s3_bucket_replication_configuration" "dr" {
role = aws_iam_role.replication.arn
bucket = module.primary_region.s3_bucket_id
rule {
id = "dr-replication"
status = "Enabled"
destination {
bucket = module.dr_region.s3_bucket_arn
storage_class = "STANDARD_IA"
}
}
}
# RDSの自動バックアップ
resource "aws_db_instance" "primary" {
identifier = "${var.environment}-database"
# スナップショットからの復元をサポート
snapshot_identifier = var.restore_from_snapshot
# 自動バックアップ
backup_retention_period = 7
backup_window = "03:00-04:00"
# 最終スナップショットの作成
skip_final_snapshot = false
final_snapshot_identifier = "${var.environment}-database-final-${formatdate("YYYY-MM-DD-hhmm", timestamp())}"
}
# DR切り替えスクリプト
resource "null_resource" "dr_failover_script" {
provisioner "local-exec" {
command = <<-EOT
cat > failover_to_dr.sh << 'EOF'
#!/bin/bash
echo "Starting failover to DR region..."
# 1. Route 53の切り替え
aws route53 change-resource-record-sets \
--hosted-zone-id ${aws_route53_zone.main.id} \
--change-batch '{
"Changes": [{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "${var.domain_name}",
"Type": "A",
"AliasTarget": {
"HostedZoneId": "${module.dr_region.alb_zone_id}",
"DNSName": "${module.dr_region.alb_dns_name}",
"EvaluateTargetHealth": true
}
}
}]
}'
# 2. DRインスタンスの起動
terraform apply -var="dr_mode=true" -auto-approve
echo "Failover completed!"
EOF
chmod +x failover_to_dr.sh
EOT
}
}
# 監視とアラート
resource "aws_cloudwatch_metric_alarm" "dr_health" {
alarm_name = "${var.environment}-dr-health"
comparison_operator = "LessThanThreshold"
evaluation_periods = "2"
metric_name = "HealthyHostCount"
namespace = "AWS/ApplicationELB"
period = "60"
statistic = "Average"
threshold = "1"
alarm_description = "This metric monitors DR region health"
dimensions = {
TargetGroup = module.dr_region.target_group_arn_suffix
LoadBalancer = module.dr_region.alb_arn_suffix
}
alarm_actions = [aws_sns_topic.alerts.arn]
}
15.6 IaCのベストプラクティス
コード構造とモジュール設計
# 推奨されるディレクトリ構造
.
├── README.md
├── .gitignore
├── .pre-commit-config.yaml
├── environments/
│ ├── dev/
│ │ ├── backend.tf
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ ├── terraform.tfvars
│ │ └── variables.tf
│ ├── staging/
│ └── production/
├── modules/
│ ├── networking/
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ ├── variables.tf
│ │ └── README.md
│ ├── compute/
│ ├── database/
│ └── monitoring/
├── policies/
│ ├── security.sentinel
│ └── cost.sentinel
└── scripts/
├── setup.sh
└── validate.sh
セキュリティのベストプラクティス
# secrets.tf - シークレット管理
# 1. 環境変数から取得
variable "db_password" {
description = "Database password"
type = string
sensitive = true
}
# 2. AWS Secrets Managerの使用
data "aws_secretsmanager_secret_version" "db_password" {
secret_id = "rds/production/password"
}
# 3. 暗号化されたtfvarsファイル
# terraform.tfvars.encrypted(git-cryptで暗号化)
db_password = "encrypted_value_here"
# 4. リソースでの使用
resource "aws_db_instance" "main" {
password = var.db_password # または data.aws_secretsmanager_secret_version.db_password.secret_string
}
# 5. 出力時の保護
output "db_endpoint" {
value = aws_db_instance.main.endpoint
description = "Database endpoint"
}
output "db_password" {
value = var.db_password
description = "Database password"
sensitive = true # terraformの出力で表示されない
}
テストの自動化
// terraform_test.go - Terratest による自動テスト
package test
import (
"testing"
"time"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/gruntwork-io/terratest/modules/aws"
"github.com/stretchr/testify/assert"
)
func TestWebServerModule(t *testing.T) {
t.Parallel()
// Terraform オプションの設定
terraformOptions := &terraform.Options{
TerraformDir: "../modules/web-server",
Vars: map[string]interface{}{
"environment": "test",
"instance_count": 2,
"instance_type": "t3.micro",
},
}
// テスト終了時にリソースを削除
defer terraform.Destroy(t, terraformOptions)
// Terraform init と apply
terraform.InitAndApply(t, terraformOptions)
// ALBのDNS名を取得
albDNS := terraform.Output(t, terraformOptions, "load_balancer_dns")
// ALBが正しく動作しているか確認
url := fmt.Sprintf("http://%s", albDNS)
expectedStatus := 200
maxRetries := 30
timeBetweenRetries := 10 * time.Second
http_helper.HttpGetWithRetryWithCustomValidation(
t,
url,
nil,
maxRetries,
timeBetweenRetries,
func(status int, body string) bool {
return status == expectedStatus
},
)
// Auto Scaling Groupの確認
asgName := terraform.Output(t, terraformOptions, "autoscaling_group_name")
asg := aws.GetAutoScalingGroup(t, asgName, "ap-northeast-1")
assert.Equal(t, int64(2), *asg.DesiredCapacity)
assert.Equal(t, int64(2), *asg.MinSize)
assert.Equal(t, int64(6), *asg.MaxSize)
}
コスト最適化
# cost_optimization.tf
# 1. 適切なインスタンスタイプの選択
locals {
# 環境に応じたインスタンスタイプ
instance_types = {
dev = "t3.micro" # $0.0104/hour
stg = "t3.small" # $0.0208/hour
prod = "m5.large" # $0.096/hour
}
}
# 2. オートスケーリングによる最適化
resource "aws_autoscaling_schedule" "scale_down_nights" {
scheduled_action_name = "scale-down-nights"
autoscaling_group_name = aws_autoscaling_group.web.name
# 夜間は最小構成に
min_size = 1
max_size = 3
desired_capacity = 1
# 毎日19:00に実行(JST)
recurrence = "0 10 * * *" # UTC
}
resource "aws_autoscaling_schedule" "scale_up_mornings" {
scheduled_action_name = "scale-up-mornings"
autoscaling_group_name = aws_autoscaling_group.web.name
# 朝は通常構成に
min_size = 2
max_size = 10
desired_capacity = 3
# 毎日8:00に実行(JST)
recurrence = "0 23 * * *" # UTC
}
# 3. スポットインスタンスの活用
resource "aws_autoscaling_group" "spot" {
mixed_instances_policy {
launch_template {
launch_template_specification {
launch_template_id = aws_launch_template.web.id
}
override {
instance_type = "t3.medium"
}
override {
instance_type = "t3a.medium" # AMD版
}
}
instances_distribution {
on_demand_percentage_above_base_capacity = 0
spot_allocation_strategy = "lowest-price"
spot_instance_pools = 2
}
}
}
# 4. コストアラート
resource "aws_budgets_budget" "monthly" {
name = "${var.environment}-monthly-budget"
budget_type = "COST"
limit_amount = var.monthly_budget_limit
limit_unit = "USD"
time_unit = "MONTHLY"
notification {
comparison_operator = "GREATER_THAN"
threshold = 80
threshold_type = "PERCENTAGE"
notification_type = "FORECASTED"
subscriber_email_addresses = [var.finance_email]
}
}
15.7 まとめ:コードがインフラになる時代
Infrastructure as Codeがもたらした革命
本書の最終章で学んだIaCは、インフラ管理に以下の革命をもたらしました:
- 再現性:同じコードから同じインフラが生成される
- バージョン管理:インフラの変更履歴が追跡可能
- 協働作業:コードレビューによる品質向上
- 自動化:人的ミスの排除と効率化
- スケール:1台でも1000台でも同じ労力
手作業から自動化への旅
1990年代:物理サーバーの手動設定
↓
2000年代:設定管理ツール(Puppet、Chef)
↓
2010年代:クラウドとIaC(Terraform、CloudFormation)
↓
2020年代:GitOps、Policy as Code
↓
未来:AI駆動の自律的インフラ?
インフラエンジニアの新しい役割
もはやインフラエンジニアは、サーバーラックの前で作業する人ではありません。
現代のインフラエンジニアは:
- アーキテクト:システム全体を設計する
- プログラマー:インフラをコードで表現する
- SRE:信頼性とパフォーマンスを追求する
- セキュリティエンジニア:脅威から守る
- コスト最適化エンジニア:効率的な運用を実現する
本書を終えて
15章にわたる長い旅、お疲れさまでした。
Linuxの基礎から始まり、コンテナ、クラウド、そしてInfrastructure as Codeまで、現代のインフラ技術の全体像を学んできました。
しかし、これは終わりではなく始まりです。技術は日々進化し、新しいツールやプラクティスが生まれています。
本書で学んだ「なぜ」の視点を持ち続けることで、新しい技術にも対応できるはずです。
最後に
#!/bin/bash
echo "Congratulations! You are now an Infrastructure Engineer."
echo "May your servers be stable and your deployments be smooth."
echo "Happy coding!"
インフラエンジニアとしての素晴らしいキャリアを歩まれることを願っています。