Edited at

IaCの運用が辛い原因は考え方じゃなくてツールだと言いたい


言いたいこと


  • IaCの運用がビジネスのスピードに合わなくなってきている

  • IaCが辛い原因は考え方じゃなくてツールにあると思う

  • Terraform のmoduleの再利用性がないのは DI ができないから

  • DIのできるインフラ構築ツールがでてくれば、インフラ構築の大変さが劇的に変わる(はず)


発端: IaC って辛いよね

発端となったのはこちらのスライドです。

Infrastructure as Codeに疲れたので、僕たちが本来やりたかったことを整理する

簡単に説明すると、「IaCっていい加減つらいよね。なんでもかんでもCode化すればいいってもんじゃないよね。」という話です。

辛みについてはすごく共感できましたが、ただ、ここに語られてることって、 IaCの辛さじゃなくて、terraform の辛さだと思うんです

ツールが現状の運用のニーズに合わなくなってきただけだと思うんです。


Terraform がなぜ辛いのか?

Terraform が辛い原因を一言でいうと、 module のポータビリティーの低さだと私は考えています。

ここからは具体例で説明します。


例: ELB + SecurityGroup + Route53 record からなるモジュール

ELB, ACM, Security Group などの public 層のリソースをまとめたモジュールを作りたいとします。

ELBは以下のような定義であるとします:


elb.tf

resource "aws_elb" "internal" {

name = "${var.app_name}-${var.env}-internal"
security_groups = ["${aws_security_group.internal-elb.id}"]
subnets = [
"${lookup(var.subnet_ids, "private-1a")}",
"${lookup(var.subnet_ids, "private-1c")}",
]
internal = true

listener {
instance_port = "${var.listener_port["instance"]}"
instance_protocol = "http"
lb_port = "${var.listener_port["lb"]}"
lb_protocol = "https"
ssl_certificate_id = "${data.aws_acm_certificate.wildcard.arn}"
}

health_check {
healthy_threshold = "${var.health_check["healthy_threshold"]}"
unhealthy_threshold = "${var.health_check["unhealthy_threshold"]}"
timeout = "${var.health_check["timeout"]}"
target = "${var.health_check["target"]}"
interval = "${var.health_check["interval"]}"
}

cross_zone_load_balancing = true
idle_timeout = 60
connection_draining = true
connection_draining_timeout = 300

tags {
"Name" = "${var.app_name}-${var.env}-internal"
}

lifecycle {
ignore_changes = ["access_logs", "idle_timeout"]
}
}


このとき、 ELB を internet faced にするか inner にするかくらいなら、 boolean 変数で注入すれば外からコントロールできますよね。


elb.tf

  internal        = "${var.is_internal}"


ところが、ELBのリスナーをサービスAとサービスBで独自のものを作りたい場合があります。

運用に即した例としては、 HTTP から HTTPS にリダイレクトする ELB を作りたいとします。

ところが、現状のterraform はこれを外から注入する手段はありません。


AsIs の解決策

AsIs の解決策としては、以下の2つです(module 使わないは論外として):


  1. ELB の定義をこの moudle から外に出す

  2. ELBのリスナーだけ異なる似たような module を2つ作る


解決策1: ELBの定義を module の外に出す

この方法だと、そもそもALBの定義が module の外に出てしまうのだから、この module には重要なことが書かれなくなるわけです。これは module を使わない、といっているのと似たような感じになります。


解決策2: ELBの定義だけ差し替えた2つの module を作る

以下のような2つのモジュールを作ります。


  • module1: public-elb

  • module2: public-elb-with-redirect

module2 のELBの定義は以下のようになります(module1との差分のみ表示):


public-elb-with-redirect/elb.tf

// ほかはmodule1と同じ

...
listener {
instance_port = "${var.listener_port_ssl["instance"]}"
instance_protocol = "http"
lb_port = "${var.listener_port_ssl["lb"]}"
lb_protocol = "https"
ssl_certificate_id = "${data.aws_acm_certificate.wildcard.arn}"
}
...

この方法だと、 似てるけど listner の部分だけ違う module が複数できるわけです。これでは他の部分のコードが二重管理になります。さらに別の部分も変更したいとなると、似てるmoduleがどんどん増えていきます。これは管理がとても煩雑です。


本来やりたいこと = DI (Dependency Injection)

これに対して、listener 定義の部分だけ外部から注入できれば、module は1つですみます。これによって、汎用的な用途で使える public レイヤの module ができるわけです。これはいろんなところに使い回せます。


本来やりたかったこと

// こんな言語はありませんが、あくまでイメージです。。

// public_elb module
const public_elb = new Elb({ listener: new Listener({ redirect: false }) });
// public_elb_with_redirect module
const public_elb_with_redirect = new Elb({ listener: new Listener({ redirect: true }) });

参考: Dependency Injection (依存性の注入)


terraform は module に DI を導入するか?

たぶん No だと思います。

terraform でこれをやろうとすると、根本的にアーキテクチャの変更を強いられると思うんです。だから多分terraform に DI は来ないと思う。

一方で、 pulumiaws-cdk みたいなツールは汎用プログラミング言語でインフラ定義をするので、DIが比較的導入しやすいんじゃないかと思います。だからこれらのツールはこれから有望な選択肢になるんじゃないかと思うんです。

(もちろん、ツール側がDIを導入してくれなかったら意味はないんですが。。)


DIができるツールが出てくればインフラ構築の辛さが劇的に変わる

DIができるツールが出てくれば、moduleに相当するもののポータビリティも上がるので、githubとかでいろいろな開発者がmoduleを公開していくようになると思います。

そうなると、オープンソースの資産が充実してきて、同じ要件をアリもので代用できるようになっていくと思います。つまり、インフラ定義が楽になると思うんです。


終わりに

つまるところ、だからみなさん一緒に aws-cdk や pulumi を勉強しませんか?という話ですw


参考

この一連のツイート