BEMAロゴ

エンジニアの
成長を支援する
技術メディア

AWS経験者もハマる!Terraformで学ぶさくらのクラウドとcloud-initの罠

昨今、政府のガバメントクラウド採択などを背景に、国産クラウドへの注目が高まっています。私たちもその一つである「さくらのクラウド」のナレッジを身につけるべく、Terraformを使ったサーバー構築を検証してみました。

その中で、AWSやGoogle Cloudなどの主要クラウドサービスに慣れていると、「同じ感覚でいけるはず」と考えがちですが、さくらのクラウドには独自の仕様があり、思わぬ「ハマりどころ」に遭遇することがあります。特に、仮想サーバーの初期設定で使われるcloud-initの挙動には、独特のポイントがありました。

この記事では、Terraformを使ってさくらのクラウド上にシンプルなWebアプリケーション環境を構築する手順を解説します。加えて、AWSとの違いを意識しつつ、筆者が実際に詰まったcloud-initの問題と、その解決策も詳しくご紹介します。

この記事を読んでわかること

  • Terraformを使ったさくらのクラウドの基本的な環境構築手順
  • さくらのクラウドと主要クラウドの考え方の違い
  • cloud-initがうまく動作しない時の原因と、その特定が難しい理由

事前準備:Terraformとさくらのクラウド利用の必須設定

この記事のTerraformコードを実行する前に、以下の準備が完了していることを確認してください。

  • さくらのクラウド APIキーの作成:
    • コントロールパネルからAPIキー(アクセストークンとシークレット)を作成しておきます。
  • usacloudのインストールと設定:
    • さくらのクラウド公式のCLIツールである usacloud をインストールし、usacloud profile コマンドで作成したAPIキーを設定しておきます。
    • Terraformのさくらのクラウドプロバイダは、デフォルトで usacloud の設定プロファイル(~/.usacloud/default/config.json)を参照して認証を行います。

※詳細な手順は、公式ドキュメントをご参照ください。

構築する環境の構成:Webサーバー+DB+ネットワーク

今回は、以下のようなシンプルなWebアプリケーション環境を構築します。

  • サーバー: Webサーバーを1台構築します。
  • データベース: MariaDBを1台用意します。
  • ネットワーク:
    • サーバーは共有セグメント(グローバルIP)とプライベートなスイッチに接続します。
    • データベースはプライベートなスイッチにのみ接続します。
    • パケットフィルタで、特定のIPアドレスからのみSSH/HTTP/HTTPSアクセスを許可します。

    DNS: サーバーのグローバルIPアドレスを指すAレコードを登録します。

構成図

ディレクトリ構成

Terraformのコードは、以下のようなファイル構成で管理します。(プロパティの詳細はこちらOpen in new tab)

Terraform

./
├── secrets.auto.tfvars      # パスワードなどの機密情報を記述
├── database.tf              # データベースリソース
├── global.tf                # DNSリソース
├── network.tf               # ネットワークリソース
├── server.tf                # サーバーリソース
├── terraform.tf             # Terraform全体の設定
└── variables.tf             # 変数定義

Terraformコード全体像

1. Terraformとプロバイダの設定 (terraform.tf)

Terraformのバージョンや、さくらのクラウドプロバイダの情報を定義します。 また、terraform.tfstateファイルは、チームでの管理や意図しない変更を防ぐために、ローカルではなくS3互換のオブジェクトストレージに保存する設定(バックエンド設定)にしています。また、参考記事Open in new tabのようにprofileを指定してアクセスキーIDとシークレットアクセスキーを読み込む方法を利用しています。

Terraform

# terraform.tf
terraform {
  required_version = ">= 1.11" # versionは適宜変更してください

  required_providers {
    sakuracloud = {
      source  = "sacloud/sakuracloud"
      version = "2.28.1"
    }
  }

  backend "s3" {
    bucket = "tfstate-file"
    key    = "simple.tfstate"
    endpoints = {
      s3 = "https://s3.isk01.sakurastorage.jp"
    }
    region                       = "jp-north-1"
    profile                      = "terraform-s3"
    shared_credentials_files     = ["../hogehoge/.accesskey"] # 自身の環境に合わせてください
    skip_requesting_account_id   = true
    skip_credentials_validation  = true
    skip_region_validation       = true
    skip_metadata_api_check      = true
    skip_s3_checksum             = true
  }
}

provider "sakuracloud" {
  profile = "default"
}

2. ネットワークリソース (network.tf)

サーバーとデータベースを接続するためのプライベートなスイッチと、サーバーに適用するパケットフィルタを定義します。

※注意: スイッチの作成数上限について 2025年9月19日時点で、さくらのクラウドでは東京第1ゾーン、石狩第1ゾーン、石狩第2ゾーンにおいて、スイッチの作成可能数に上限が設けられています。上限は「2024年12月12日の制限開始前に作成されていたスイッチの数 + 1個」となります。 新規でアカウントを作成した場合、各ゾーンで作成できるスイッチは1つまでとなる可能性があるためご注意ください。詳細は公式のアナウンスOpen in new tabをご確認ください。

パケットフィルタでは、特定のIPアドレスからのみSSH (22)、HTTP (80)、HTTPS (443) の通信を許可し、その他はすべて拒否する設定にしています。

Terraform

# network.tf
resource "sakuracloud_switch" "switch_simple" {
  name        = "switch_simple"
  description = "シンプル構成のスイッチ"
  tags        = ["sakura", "Simple"]
}

resource "sakuracloud_packet_filter" "filter_simple" {
  name = "filter_simple"
}

resource "sakuracloud_packet_filter_rules" "filter_rule_simple" {
  packet_filter_id = sakuracloud_packet_filter.filter_simple.id

  // SSH許可 (IPはサンプルです)
  expression {
    protocol         = "tcp"
    allow            = true
    source_network   = "<IPアドレス>"
    destination_port = "22"
  }
  // HTTP許可
  expression {
    protocol         = "tcp"
    allow            = true
    source_network   = "<IPアドレス>"
    destination_port = "80"
  }
  // HTTPS許可
  expression {
    protocol         = "tcp"
    allow            = true
    source_network   = "<IPアドレス>"
    destination_port = "443"
  }
  // その他は拒否
  expression {
    protocol = "ip"
    allow    = false
  }
}

3. サーバーリソース (server.tf)

Webサーバーを構築します。OSにはUbuntu Serverを利用し、cloud-initを使ってサーバー起動時に初期設定(ホスト名設定、パッケージインストール、ファイル作成など)を実行します。

ここが今回の最重要ポイントです。詳細は後述します。

Terraform

# server.tf
data "sakuracloud_archive" "ubuntu" {
  filter {
    tags = [
      "cloud-init",      # <- このタグが重要!
      "distro-ubuntu",
    ]
  }
}

resource "sakuracloud_disk" "disk_simple" {
  name              = "disk_simple"
  description       = "シンプル構成のブロックストレージ"
  tags              = ["sakura", "Simple"]
  size              = 40
  plan              = "ssd"
  connector         = "virtio"
  source_archive_id = data.sakuracloud_archive.ubuntu.id
}

resource "sakuracloud_server" "server_simple" {
  name        = "server_simple"
  description = "シンプル構成のサーバー"
  tags        = ["sakura", "Simple"]
  disks       = [sakuracloud_disk.disk_simple.id]
  core        = 2
  memory      = 4

  network_interface {
    upstream         = "shared"
    packet_filter_id = sakuracloud_packet_filter.filter_simple.id
  }

  network_interface {
    upstream        = sakuracloud_switch.switch_simple.id
    user_ip_address = "128.0.0.1"
  }

  user_data = <<-EOF
    #cloud-config
    hostname: example.com
    manage_etc_hosts: true
    password: ${var.server_password}
    chpasswd: 
      expire: false
    ssh_pwauth: true
    lock_passwd: false
    write_files:
      - path: /var/www/html/index.html
        permissions: '0644'
        owner: www-data:www-data
        content: |
          Hello, World!
    package_update: true
    package_upgrade: true
    packages:
      - apache2
    runcmd:
      - systemctl enable apache2
      - systemctl start apache2
  EOF
}

4. データベースリソース (database.tf)

MariaDBのデータベースアプライアンスを作成します。プライベートなスイッチに接続し、サーバー(128.0.0.1)からのアクセスのみを許可しています。

Terraform

# database.tf
resource "sakuracloud_database" "simple_db" {
  name          = "db_simple"
  description   = "シンプル構成のデータベース"
  tags          = ["sakura", "Simple"]
  database_type = "mariadb"
  plan          = "30g"
  username      = "dbuser"
  password      = var.database_password

  network_interface {
    switch_id     = sakuracloud_switch.switch_simple.id
    ip_address    = "128.0.11.10"
    netmask       = 24
    gateway       = "128.0.11.1"
    port          = 3306
    source_ranges = ["128.0.0.1/32"]
  }
}

5. DNSリソース (global.tf)

作成したサーバーのグローバルIPアドレスを、指定したドメインのAレコードとして登録します。
Terraform

# global.tf
resource "sakuracloud_dns" "main" {
  zone        = "example.com"
  description = "シンプル構成のDNS"
  tags        = ["sakura", "Simple"]

  depends_on = [
    sakuracloud_server.server_simple
  ]
}

resource "sakuracloud_dns_record" "web" {
  dns_id = sakuracloud_dns.main.id
  name   = "@"
  type   = "A"
  value  = sakuracloud_server.server_simple.ip_address
  ttl    = 300
}

6. 変数定義 (variables.tf と secrets.auto.tfvars)

パスワードなどの直接コードに記述したくない情報を変数として定義します。

Terraform

# variables.tf
variable "server_password" {
  type      = string
  sensitive = true
}

variable "database_password" {
  type      = string
  sensitive = true
}

実際の値は .gitignore に追加した secrets.auto.tfvars ファイルに記述します。

# secrets.auto.tfvars
server_password   = "your_secure_password"
database_password = "your_secure_db_password"

さくらのクラウドIaCのハマりどころ:cloud-initが効かない問題

AWSのEC2などで user_data を使ったことがある方は、同じ感覚でサーバーの初期設定ができると思うでしょう。しかし、実際に上記のコードで terraform apply を実行したところ、以下の問題に直面しました。

write_filesなどが実行されない:
apache2 がインストールされておらず、/var/www/html/index.html も作成されていない。

サーバーリソースは正常に作成されているように見えるため、原因特定に時間がかかりました。

原因は「cloud-init対応アーカイブ」の選択漏れ

結論から言うと、原因は server.tf のデータソース sakuracloud_archive の定義にありました。
当初、以下のようにdistro-ubuntu タグのみを指定していました。

Terraform

# server.tf (問題のあったコード)
data "sakuracloud_archive" "ubuntu" {
  filter {
    tags = ["distro-ubuntu"] # ← Ubuntuであることしか指定していなかった
  }
}

上記のようにOSの名前だけでアーカイブを指定すると、cloud-initに対応していないアーカイブが選択されてしまうことがありました。 さくらのクラウドでは、同じOSでも cloud-init を利用できるアーカイブとできないアーカイブが用意されています。(公式サイトOpen in new tab)

user_data は cloud-init の機能を使って実行されるため、cloud-init に対応していないOSイメージ(アーカイブ)を選択してしまうと、user_data に記述した内容はすべて無視されてしまうのです。

なぜこの問題にハマったのか?

解決策はシンプルですが、ここにたどり着くまでには少し時間がかかりました。その理由は「tagsに指定できる値の種類がドキュメントから見つけにくかった」ためです。

Terraformの公式ドキュメントOpen in new tabさくらのクラウドのマニュアルOpen in new tabを確認しても、filterのtagsにどのような値を指定できるのか、という一覧情報が見当たりませんでした。(2025年9月時点)

最終的に、参考にしていた他の技術記事Open in new tabのコードでtagsに"cloud-init"が指定されているのを発見し、解決の糸口となりました。しかし、その記事内でもtagsに関する詳しい言及はなく、試行錯誤の末に「このタグがないとuser_dataが動かない」という仕様に気づくことができました。

このように、ドキュメントから仕様を読み解くのが難しいケースがあることも、さくらのクラウドを扱う上での一つのポイントかもしれません。

解決策: tagsでcloud-initを明示的に指定する

この問題を解決するには、データソースの filter で cloud-init に対応していることを示すタグを明示的に指定する必要があります。

Terraform

# server.tf (修正後のコード)
data "sakuracloud_archive" "ubuntu" {
  filter {
    tags = [
      "cloud-init",      # これを追加!
      "distro-ubuntu",
    ]
  }
}

tags に "cloud-init" を含めることで、Terraformは cloud-init に対応したUbuntuのアーカイブを自動的に選択してくれます。これにより、user_data に記述した初期化スクリプトが正しく実行されるようになりました。

filter は、数ある公開アーカイブの中から、条件に合致するものを絞り込むための機能です。名前 (names) だけでなく、タグ (tags) やOSの種類 (os_type) など、複数の条件で絞り込むことができます。さくらのクラウドで user_data を活用する際は、必ず "cloud-init" タグを指定することを忘れないようにしましょう。

まとめ:AWSとの違いから学ぶさくらのクラウド活用のコツ

今回は、Terraformを使ってさくらのクラウド上にWebアプリケーション環境を構築する手順と、cloud-initにまつわるハマりどころを解説しました。

AWSのような主要クラウドサービスがAPIを前提に抽象化されたサービスを提供するのに対し、さくらのクラウドは物理サーバーやオンプレミス環境の考え方に近い部分があります。例えばネットワーク構成において、主要クラウドがVPC(仮想ネットワーク)の中にサブネットを作り、そこに仮想マシンを配置するという多層的な仮想化を前提としているのに対し、さくらのクラウドは物理的な共有セグメント(インターネット)やスイッチにサーバーを直接接続するという、より物理インフラに近い考え方をします。

このような特性を理解せず、「AWSのサブネットはさくらのクラウドのスイッチ」といった1対1の単純な置き換えで考えると、今回のような仕様の違いでつまずきやすくなります。

さくらのクラウドでIaCを実践する際は、リソースの裏側にある物理的なインフラを少し意識し、「オンプレミス環境をAPIで操作している」というメンタルモデルで臨むと、よりスムーズに理解が進むかもしれません。

この記事が役に立ったと思ったら、
ぜひ「いいね」とシェアをお願いします!

リンクをコピーXでシェアするfacebookでシェアする

この記事を書いた人

山岸 玄斉
山岸 玄斉
2024年に株式会社メンバーズに中途入社。デブオプスリードカンパニーにて開発支援を行う傍ら、クラウドエンジニアとしてクラウド技術を学習し、生成AIを用いたPoCにも従事。
詳しく見る
ページトップへ戻る