Don't Repeat Yourself

Don't Repeat Yourself (DRY) is a principle of software development aimed at reducing repetition of all kinds. -- wikipedia

terraformを触り始める

画面をポチポチしながらインスタンスを立てて…といったことを考えるのが辛くなってきたので、そろそろ terraform 使いこなせるようになりたいと思い、チュートリアルをコツコツはじめてみました。

普段は CDK を使ってるんですが、最近少しだけ terraform を触るチャンスがあって、なかなかいいじゃないかと思ったのもあります。

あと基本的にインフラ(というかクラウド)が苦手なのですが、そろそろ逃げられなくなってきたのもあって、本腰入れてやらないとなと思っているというのもあります。

以下はこのチュートリアルをやってみたメモです。terraform にはチュートリアルガイドが存在し、それを一通りやるだけである程度 terraform を使いこなせるように設計されているようです。英語ですが、無料なのでオススメです。

terraform そのもののインストールコマンド等は一旦割愛します。詳しくはチュートリアルをご覧ください。私の手元の terraform のバージョンは下記です。

❯ terraform --version
Terraform v0.13.5

また1つ1つ調べていたら例のごとく長くなってしまったので、お好きなところをかいつまんでお読みください :) 私は terraform はまったくの素人なので、情報が正確でない箇所があるかもしれません。

今回のお題: Docker 上に nginx を立ち上げる

今回のお題は Docker 上に nginx を立てて localhost:8000 で Welcome to nginx! するものです。

terraform は設定内容を .tf という拡張子のファイルに書いていきます。さまざまなリソースの管理をコード形式で見られるように作られているようです。

さっそくですが、Docker 上に nginx を立ち上げる設定は下記のように書くことができます。main.tf という名前をチュートリアルに従って命名しました。

terraform {
  required_providers {
    docker = {
      source = "terraform-providers/docker"
    }
  }
}

provider "docker" {}

resource "docker_image" "nginx" {
  name         = "nginx:latest"
  keep_locally = false
}

resource "docker_container" "nginx" {
  image = docker_image.nginx.latest
  name  = "tutorial"
  ports {
    internal = 80
    external = 8000
  }
}

{} で囲まれたところをブロックと呼ぶそうです。terraform の tf ファイルの文法は比較的単純で、

<BLOCK TYPE> "<BLOCK LABEL>" "<BLOCK LABEL>" {
  # Block body
  <IDENTIFIER> = <EXPRESSION> # Argument
}

というものを繰り返していくだけのようです。非常に覚えやすく、シンプルで好きです。

設定ファイルを理解していく

まず上から読んでいきます。

terraformブロック, required_providersブロック

terraform ブロックは、その terraform の実行単位でのさまざまな設定を行えるブロックです。今回は後ほど解説する required_providers のみを記載していますが、required_version のように terraform の実行バージョンを設定したりもできます*1

version を使用する場合には、たとえば

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 2.70"
    }
  }
}

といったように書けるようです。

今回記載されている required_providers ブロックは、どの「プロバイダ(provider)」を利用するかを規定するブロックになっています。プロバイダという概念については後述します。

required_provider 内には2つの識別子が存在します。

  • source address と呼ばれる required_provider にのみ定義できる識別子。
  • local name と呼ばれる terraform のモジュール内ならどこでも定義できる識別子。
  required_providers {
    docker = {
      source = "terraform-providers/docker"
    }

上記のような記述をした場合、

  • source = "terraform-providers/docker"部分にあるのが、先ほどで言うところの source address。この場合の source address は terraform-providers/docker となる。
  • docker = { 部分にあるのが、先ほどで言うところの local name。この場合の local name は docker となる。

なお名前がコンフリクトすることもあるようです。そして、terraform はどの名前空間に紐づくものかまでを詳細には推測できないようなので、できるだけがんばって名前がダブらないようにする必要があるようです。

このあたりの詳しい仕様はこちらのページに記載されています。上述ならびに以降も同様ですが、ほぼこのドキュメントの内容を日本語にまとめ直していきます。

provider ブロック

まずプロバイダ(provider)という概念を理解します。プロバイダというのは terraform 自体とは独立したプラグインです。なので、バージョン管理も terraform からは分離しており、独自のバージョニングを保持しています。

Terraform Registry というところに数多くのプロバイダが登録されており、terraform においてはそれらを利用しながら設定ファイルを組み上げていくことになります。たとえば AWS のプロバイダや、Docker のプロバイダといったメジャーどころはだいたい抑えられているように見えます。

provider で重要なことは下記です。

  • provider という宣言によって始まる。
  • local name で識別する。
  • provider ブロック自体は tf ファイルのルートに書く必要がある。

provider ブロックの基本的な文法は下記のような形でしょう。

provider "<LOCAL NAME>" { ... }

先ほど説明した local name が provider の横に入ります。つまり、required_providers で先ほど docker という local name を設定したので、provider にも docker が入る必要があります。したがって、下記のような記述になっています。

provider "docker" { ... }

これだけではわかりにくいので少し他の例も利用しておくと、provider ブロックの中には例えばリージョンの情報といった arguments が入れられます。どうやらプロバイダによって arguments は異なっているようです。たとえば AWS のプロバイダを使用してみたい場合は、次のように記述できます

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 2.70"
    }
  }
}

provider "aws" {
  profile = "default"
  region  = "us-west-2"
}

provider ブロックの中に独自の arguments が登場していることがわかります。

provider に関する詳細な仕様は、このページにあります。

resource ブロック

最後に登場してきたのは resource というブロックです。

リソース(resource)というのは、terraform が生成・管理する最小限の要素のことをいいます。たとえば先ほどの例ですと、docker image と docker container というリソースを terraform が生成し、管理することになります。

resource で重要なことは下記です。

  • リソースというのはプロバイダが提供する、インフラプラットフォームを管理するためのもの。
  • resource type というものが存在する。
  • local name が resource type ごとにさらに付与されている。

resource は次のような文法をもっています(と、推察しました)。

resource "<RESOURCE TYPE>" "<LOCAL NAME>" { ... }

今回は2つの設定を記述しました。

resource "docker_image" "nginx" {
  name         = "nginx:latest"
  keep_locally = false
}

resource "docker_container" "nginx" {
  image = docker_image.nginx.latest
  name  = "tutorial"
  ports {
    internal = 80
    external = 8000
  }
}

上から順に紐解いていくと、

  • resource type が docker_image で、local name が nginx のリソースを作りたい。
  • resource type が docker_container で、local name が nginx のリソースを作りたい。

ということがわかります。

provider と resource の関係性をコードで示すと次のようになるかもしれません。

さて、この resource type 以下の情報は一体どこに書いてあるのか、というのが気になり始めると思います。これは Terraform Registry の各プロバイダのページに(それなりに)詳しく書いてあります。なので、開発していく際にはこれらを見ながらやっていく感じになるのでしょう(まだ初心者なのでわからないですが)。

実行

上記まで理解できたところで、main.tf がおそらく完成しているはずです。あとは、

terraform init

して、

terraform apply

して設定内容を反映し、localhost:8000 にアクセスすると、無事に nginx が立ち上がっているはずです!

terraform destroy 

で、apply にて生成したものを削除することができます。

terraform init

何をしているか気になったので、さっそく叩いてみました。

terraform init --help
Usage: terraform init [options] [DIR]

  Initialize a new or existing Terraform working directory by creating
  initial files, loading any remote state, downloading modules, etc.

  This is the first command that should be run for any new or existing
  Terraform configuration per machine. This sets up all the local data
  necessary to run Terraform that is typically not committed to version
  control.

  This command is always safe to run multiple times. Though subsequent runs
  may give errors, this command will never delete your configuration or
  state. Even so, if you have important information, please back it up prior
  to running this command, just in case.

  If no arguments are given, the configuration in this working directory
  is initialized.
  • 初期ファイルを設定したり、リモートにある状態をロードしたり、モジュールをダウンロードしたりするようです。
  • terraform を実行するのに必要なローカルデータを読み込むコマンドのようです。
  • 冪等のようです。

terraform apply

同様に何をしているか気になったので叩いてみました。

terraform apply --help
Usage: terraform apply [options] [DIR-OR-PLAN]

  Builds or changes infrastructure according to Terraform configuration
  files in DIR.

  By default, apply scans the current directory for the configuration
  and applies the changes appropriately. However, a path to another
  configuration or an execution plan can be provided. Execution plans can be
  used to only execute a pre-determined set of actions.
  • 指定ディレクトリに存在する terraform の設定ファイルを読み込んでビルドするか変更を更新するかを行ってくれるようです。
  • 脳死でも適切に変更を検知して状況を適用してくれますが、別の設定ファイルや実行計画を与えたりもできるようです(これは使ったことがないので、正しい説明を私がしているかわかりません)。

感想

  • たとえば AWS CDK は非常にいいツールではあるのだが、汎用プログラミング言語で書くとなると terraform のようにシンプルな宣言的な記法にはなかなかしづらい。
  • どうしてもクラスや関数と言った、本来的に設定ファイルの記述とは関係ない部分の影響を受ける。
  • もちろん、汎用プログラミング言語で書けるというのは、たとえばアプリケーションエンジニアにとっては、普段使っている言語でそのままインフラの設定まで書けるというメリットもある。これは大きい。
  • 一方でその点 terraform は宣言的な記法でインフラ構成を記述していける。
  • 宣言的な記法は、抽象化が難しいなど一長一短ありそうだが、terraform はそのあたりは上手に解決しているはず(と信じたい)。これからの勉強次第。

*1:terraform は version 0.12 と 0.13 でも結構違うなど、まあまあ破壊的変更が入っていて大変だと聞いたことがあります。