LoginSignup
25
23

More than 5 years have passed since last update.

Hashicorpのbest-practicesから学ぶTerraformの使い方

Last updated at Posted at 2016-07-01

これなんだ?

Terraformをちょっと上手に使うためのテクニックというかコツです。
個人的には上手にHashicorpツールを使う能力を橋本力と呼んでます。

今回参考にしたものはこちらになります。
https://github.com/hashicorp/best-practices/

また今回動作させたコードも置いておきます。
https://github.com/a4t/terraform_001

前提

Terraformも一つの言語と思ってください。
いいコードとは、変数のスコープは短く、同じ処理を何度も書かないことです。
これはTerraformでも同様です、できるだけ綺麗なコードを書くことを意識してください。

Terraformの課題

  • そもそもTerraformのバージョンが違って動かない
    • バージョンアップによって微妙な差分が発生し、何も変更ないのに変更箇所が出るケースがある (例: 指定パラメータが true -> 1 に変更とか)
  • s3でtfstateを管理する場合、applyした後にpushをする必要がある
    • 各エンジニアが毎回実行するのであれば問題ないが、忘れるケースがあるためそもそもPushコマンドをDocker側でフォローして毎回Pushする
  • null_resourceでaws cliのコマンド使うとか
    • 人によってはローカルに入ってなかったり、バージョンが違うため動かないとか

解決策

Dockerを使います。
あんまりこの辺りは深く説明しませんが、こんな感じにすれば先ほどの3つの問題が解決します。

FROM alpine:3.3

ENV TERRAFORM_VERSION=0.6.15

RUN apk update && \
    apk add bash \
    ca-certificates \
    git \
    openssl \
    zip \
    unzip \
    wget \
    python \
    py-pip \
    py-setuptools \
    ca-certificates \
    groff \
    less && \
    pip install awscli && \
    wget -P /tmp https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip && \
    unzip /tmp/terraform_${TERRAFORM_VERSION}_linux_amd64.zip -d /usr/bin && \
    rm -rf /tmp/* && \
    rm -rf /var/tmp/*

ADD ./scripts /scripts

ENTRYPOINT ["/scripts/terraform.sh"]

CMD ["--help"]
terraform.sh
#!/bin/sh

/usr/bin/terraform remote config \
  -backend=s3 \
  -backend-config="bucket=hogehogefugafugapiyopiyomogemoge" \
  -backend-config="key=terraform.tfstate" \
  -backend-config="region=ap-northeast-1" \

/usr/bin/terraform get
/usr/bin/terraform $@
/usr/bin/terraform remote push

毎回必ずterraform getとterraform remote pushをしてくれます。
あとは $@ の部分に任意のコマンドが入ってくれます。

Terraformコマンドは docker run terraform plan とかを使うことになります。
環境変数とか面倒なところありますが、docker-compose使って解決するといいです。

コードを綺麗にする

最初にみたbest-practicesを見るとわかるのですが、terraformが階層が深くなっています。
moduleを使って各tfファイルを繋いでいます。
ディレクトリ構造はこの辺りを参考にするといいと思います。(ファイル多いので一部略してます)

best-practices
├── LICENSE
├── README.md
├── packer
└── terraform
    ├── empty.tf
    ├── modules
    │   └── aws
    │       ├── compute
    │       ├── data
    │       ├── network
    │       └── util
    │           ├── artifact
    │           │   └── artifact.tf
    │           ├── deploy
    │           │   └── deploy.tf
    │           ├── iam
    │           │   └── iam.tf
    │           └── website
    │               └── website.tf
    └── providers
        └── aws
            ├── README.md
            ├── us_east_1_prod
            │   ├── terraform.tfvars
            │   └── us_east_1_prod.tf
            └── us_east_1_staging
                ├── terraform.tfvars
                └── us_east_1_staging.tf

compute, data, network, util とそれぞれの役割をしっかりディレクトリで切られてるのでコードを追いやすいです。
1ディレクトリにごちゃごちゃ置いてしまうのがよくあるパターンだと思います。
security_group_web.tf、security_group_mysql.tf、security_group_elb.tf、ec2_web.tfとかこんな感じに最初は自分も分けてました。

解決策

moduleを使うとこの辺は綺麗に書けます。
例えば今回はdeployサーバからcapistranoを実行するために `deploy -> [port22] -> app を開放するとします。
こんな感じにするとコードの重複も避けれますし、変更箇所も減るかと思います。

  • security_group.tf
security_group.tf
module "security_group" {
  source = "./modules/security_group"

  vpc_id       = "${aws_vpc.vpc.id}"
  deploy_sg    = "${aws_security_group.deploy.id}"

  app_sg_count = "3" # module内のcountでlengthとか使うとsyntax errorが出るため
  app_sg_list  = [
    "${aws_security_group.app1.id}",
    "${aws_security_group.app2.id}",
    "${aws_security_group.app3.id}"
  ]
}
  • ./modules/security_group
variable "deploy_sg"    {}
variable "vpc_id"       {}
variable "app_sg_count" {}
variable "app_sg_list"  { default = [] }

resource "aws_security_group_rule" "attach" {
  count = "${var.app_sg_count}"

  type = "ingress"
  from_port = 22
  to_port = 22
  protocol = "tcp"
  source_security_group_id = "${var.deploy_sg}"

  security_group_id = "${element(var.app_sg_list, count.index)}"
}

最後に

とは言っても既存のコードを書き換えるのは超労力いるので無理はしないでください。
subnetとかを破壊するのは危険ですし、tfstateのjsonを目で追うのはつらいです。

  • 追記(2016-07-02 00:30) よく見たらコードの重複の部分をうまく説明できてないのでこんなmodule作るとコード綺麗に書けるよ、と捕捉
variable project_name {}
variable rolename     {}
variable vpc_id       {}

resource "aws_security_group" "sg" {
  vpc_id = "${var.vpc_id}"

  name        = "${var.project_name}_${var.role_name}"
  description = "${var.project_name} ${var.rolename}"

  tags {
    Name    = "${var.project_name}_${var.role_name}"
    Project = "${var.project_name}"
    Role    = "${var.rolename}"
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

output "sgid" { value = "${aws_security_group.sg.id}"   }
output "name" { value = "${aws_security_group.sg.name}" }
25
23
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
23