ECS(Amazon EC2 Container Service)がなんなのかやっと理解できてきた
いざ自分で一から作ってみると・・・
今まで人が作ったECS上でサービスの作成とか更新とか、タスクの更新とかやってきてなんとなく使えてたけど、 いざ自分で一から作ってみるとそれぞれの要素がどうなってるのか何も分かってないことに気づいた。 今まではECSでclusterとserviceを作っていって、clusterとかserviceに対して全体的なネットワークとかインスタンスの数とかの設定をしていくものだと思ってたけどそうじゃなかった。
ECSの役割
ECSがやってるのは主にスケジューラ(どのインスタンスにどのコンテナをいい感じに配置するか)がメインで、コンテナーインスタンスにamazon-ecs-agentをおいてecsサービスのAPIを叩かせている。
じゃあ、全体的なネットワークの設定はどうするのかっていうと、ただのAuto Scalingという機能を使っていつも通りインスタンスを立ち上げてるだけ。なんならAuto Scalingを使わずに手動でAMIからインスタンスたちあげて、それをECSにコンテナーインスタンスとして登録したってよい。
GUIのウィザードでECSを作ろうとすると至れり尽せりでいい感じに色々なものができる。で、今やろうとしてるのはterraformで一から作ろうとしてるところだから、必要なものがなんなのか、どんな機能の組み合わせでできてるのかが分からないとterraformを書くことができない。進みは遅いし苦しいけど勉強にはなる。
ecs-agentの起動方法
会社の既存プロジェクトとかterraformのexampleを見てると、launch_configurationでuser_dataで結構がっつりシェルが書いてある。でも、AWS Consoleでウィザード経由で作ると、これだけ。
#!/bin/bash echo ECS_CLUSTER=hoge >> /etc/ecs/ecs.config
一体この違いはなんなんだ・・ってはまったけどlinuxのdistributionが違ってた。見てたexampleとかはcoreOSとかubuntuとか。一方で、ウィザードだとamazon-linuxのecs-optimizedてやつ。だから色々書かずに済んでたんだ。 amazon-ecs-agentのREADMEにその辺のことが書いてあった。
まとめ
ちょっとすっきりした
terraformで作るときに参考にするもの
社内のプロジェクトのを参考にできる恵まれた環境なのでまずはそれを見る。
他にもterraform公式リポジトリにあるexampleも参考になった。 terraform/examples at master · hashicorp/terraform · GitHub
インフラエンジニアになっていきたいと思った理由
今の思い
ここでいうインフラエンジニアはハードディスクがとかケーブルがとかガチなやつじゃなくて、各種スタックを組み合わせて構築するぐらいのやつ。 プログラミング歴=ほぼ社会人歴で、iOS開発がメインで一番長かった。そのあとScalaでサーバーサイド。で、今はインフラできるようになりたいと思ってる。
iOSエンジニアとしてサーバーサイドへの引け目
iOSは動く画面を作るのが楽しかった。ただ、すぐに太ってしまうUIViewControllerやたくさんのManagerクラスなどクソコードもいっぱい生み出してきてしまった。なので、Clean Architectureとかもっといい設計はないかとかを考えるのが楽しかった。 でも、なんとなくサーバーサイドとかWebのことが分からないことに引け目をずっと感じていたが、プロジェクトの状況で幸か不幸かサーバーサイドが足りないとなり、がっつりサーバーサイドの開発に携わることもできた。 はじめはWebの基本的なこととか効率的なSQL、DB設計とかが分かってなかったりScalaの関数型的な書き方が分からず苦しかったけどそこそこできるようになった。
解決できる領域の広さ
でも、サービスを運営してると障害起きたりしてもインフラ周りだと手も足も出せないのが悔しく、あと、何かXXXという問題を解決したいっていう時に、アプリケーション側で色々苦労して解決できたとしてもインフラ側でそれを何十倍の効果で解決しちゃったりする場面があって、それも悔しかった。なので、出来るようになりたい。
あとアプリもサーバーサイドも保守しやすいとか変更しやすいとかの設計を考えるのは楽しいし、実力もまだまだなんだけど、普通ぐらいのレベルにはなったなという気がしてる。
新しいことを覚える楽しさ
インフラは0だからそれを普通にする方がワクワクする。ほんの1ヶ月前は分からなかったことが分かるようになる感覚がたまらない。 ドラクエ3で言えば転職をしてレベルが1に戻るのを繰り返してる。 最初は簡単なことも分からず進捗も悪くてかなり苦しいんだけど、だんだんできるようになっていくのが好き。マゾなのかもしれない。
terraformというかHCLでif文使いたい
基本的にはサポートされてない。 けど、こちらの方のやり方を参考にして、こんな風にすればできるっちゃできる。
If you set count to 1 on a resource, you get one copy of that resource and if you set count to 0, that resource is not created at all.
trueが1、falseが0になることを利用して、countを0または1にするっていうちょっとdirtyな感じ。
resource "aws_eip" "example" { count = "${var.create_eip}" instance = "${aws_instance.example.id}" } module "frontend" { source = "/modules/microservice" service_name = "frontend" ami = "ami-abcd1234" instance_type = "t2.medium" create_eip = true } module "backend" { source = "/modules/microservice" service_name = "backend" ami = "ami-efgh5678" instance_type = "m4.large" create_eip = false }
なるほどねー。でも、公式にのってないようなやり方はあんまりやりたくないタイプ。
追記
terraform 0.8から公式にsupportされました🎉
resource "aws_instance" "web" { subnet = "${var.env == "production" ? var.prod_subnet : var.dev_subnet}" }
CONDITION ? TRUEVAL : FALSEVAL
残業をさせてるのは誰
ふるさと納税のワンストップのやつ、自治体によって印字してくれてたり返信用封筒入れてくれてたりしてみんなこうなればいいのにと思ったんだけど、いいサービスやおもてなしを期待することにより、自分が巡り巡って残業や日本の生産性の低下を生み出してる側なのではと思った。
実際、過度なサービスに慣れすぎてしまってるなー。サービス受ける側としては日本最高だなーって思ってしまう
terraformでmoduleのsourceにinterpolationは使えない
module "hoge" { source = "${path.module}/hoge" }
とやっても、
* hoge: module source cannot contain interpolations
で、はじかれる
remote-execでEC2インスタンスの起動時の処理書いたけどuser-dataがあった
前にこれを書いたんだけど、こんなことする必要なかった。
こっちでよかったじゃないか。AWSのこと何も分かってない証拠ですね。
EC2をremote-execでprovisioningする方法
追記:そもそもuser-dataがあることを知らなかったのでこっちを使おう
elastic ipで接続されない
こんな風に素直に(自分的には)書くとElastic IPよりも先にEC2を先に作ろうとしちゃう。 で、private ipでssh接続しようとするから、そりゃあいつまでたっても接続できないし、 elastic ipも作られない。
resource "aws_eip" "dev" { instance = "${aws_instance.dev.id}" vpc = true } resource "aws_instance" "dev" { ami = "ami-b73b63a0" availability_zone = "us-east-1a" instance_type = "t2.micro" disable_api_termination = false key_name = "dev" vpc_security_group_ids = [ "${aws_security_group.default.id}" ] subnet_id = "${aws_subnet.public.id}" provisioner "remote-exec" { connection { user = "ec2-user" private_key = "${file("${path.root}/dev.pem")}" } inline = [ "sudo yum update -y" ] } }
null_resourceを使う
これの最後の方のコメントが参考になった。 ec2_instance -> eip の順番で作成して、そのあとnull_resourceでtriggerを設定して、remote-execを実行する。 なるほどーうまい。
resource "aws_eip" "dev" { instance = "${aws_instance.dev.id}" depends_on = ["aws_instance.dev"] vpc = true } resource "aws_instance" "dev" { ami = "ami-b73b63a0" availability_zone = "us-east-1a" instance_type = "t2.micro" disable_api_termination = false key_name = "dev" vpc_security_group_ids = [ "${aws_security_group.default.id}" ] subnet_id = "${aws_subnet.public.id}" } resource "null_resource" "preparation" { triggers { instance = "${aws_instance.dev.id}" } connection { host ="${aws_eip.dev.public_ip}" user = "ec2-user" timeout = "30s" private_key = "${file("${path.root}/dev.pem")}" agent = true } provisioner "remote-exec" { inline = [ "sudo yum update -y" ] } }
まとめ
非同期に何かやりたい場合は、null_resourceがつかえそう
terraformのmoduleで定義したresouseにアクセスするにはoutputしないとダメ
moduleの誤解
何もわかってなかった。
module使うと変数とかをscope化できるからいいなーって思ってたけど、resourceもscope化されるってことだった。
まぁ逆に言えば、module内にresource名は被らないように気をつける必要がないとも言えるけど。
terraform how to get another module resource
みたいな感じでググってもそんなにでてこないから多分当たり前の概念過ぎるんだろうな。
他のmoduleのresource名を取得するには
resource名から取ろうとしてもダメ
例えばこんなディレクトリ構成で、bastionの方でbase_networkで定義したvpcのidが欲しいみたいな場合。
├── base_network │ ├── route_table.tf │ ├── subnet.tf │ └── vpc.tf ├── bastion │ ├── ec2.tf │ ├── eip.tf │ └── security_group.tf ├── main.tf
vpc.tfで、resource "aws_vpc" "default"
なんてやった場合に、bastionの中でaws_vpc.default.id
ってやっても、そんなresourceありませんよみたいな感じに言われてしまう。
外部から使いたい値をoutputする
なので、どうするかっていうと、各module内でoutputを定義する。
output "vpc_id" { value = "${aws_vpc.default.id}" }
で、これでbastionの方で、"${module.base_network.vpc_id}"
ってやれば使えるんかなと思ったけど、使えない。
bastionの中で、moduleとして、base_networkをimportしないといけないんだと思う。
でも、そうすると依存関係がスパゲッティみたいになってしまうのが想像できる。
そこで、moduleを定義するときに、変数を受け取れるのでそこで受け取る。
module側はvariableで受け取る
main.tfが各moduleをimportするところなんだけど、そこで、変数の橋渡しをしてあげるイメージ。
provider "aws" {} module "base_network" { source = "./base_network" } module "bastion" { source = "./bastion" vpc_id = "${module.base_network.vpc_id}" }
で、bastion内で使うときは、"${var.vpc_id}"
でいける。
最終的にはこんな感じになる。
├── base_network │ ├── outputs.tf │ ├── route_table.tf │ ├── subnet.tf │ ├── variables.tf │ └── vpc.tf ├── bastion │ ├── ec2.tf │ ├── eip.tf │ ├── outputs.tf │ ├── security_group.tf │ └── variables.tf ├── main.tf
これは、 terraform-community-modules · GitHubで使われてる基本的なやり方だった。やっとスタートラインにたどり着いた感じだw
terraformのresourceのNAMEの命名規則について
NAMEにTYPEを入れるかどうか
結構プロジェクトによってバラバラで他の言語でよくあるガイドラインみたいなのがない気がする。
個人的にはresourceのTYPEで特定できるから入れない方がいいかなという気になっている。
resource "aws_vpc" "dev_vpc_01" {} resource "aws_vpc" "dev_vpc_02" {}
ではなくて、こっちの方がいいと思った。
resource "aws_vpc" "dev_01" {} resource "aws_vpc" "dev_02" {}
とはいえ、IDEもないし、PRレビューとかで見る方としては冗長でもいいから書いてあった方が分かりやすいのではという気にもなってきた。
NAMEの規則
- TYPEがスネークケースなので、NAMEもスネークケースがよさそう。
- あとに環境が増えてもいいように001とか01とか通し番号つけておくとよさそう。
terraform入門時のメモ
どう入門するか
今まで人が作ったterraformの設定をいじって適用とかはしてきたけど自分で一から作ったことない。仕組みを理解するには、一から自分で作ってみるのが早そうだと思ったので自分で一から作ってみることにした。 ので、素直にこれをやることにした。
terraformで知ったこと
- とにもかくにもproviderを書く。
- terraform showコマンドがあること。これでtfstateが見れる。
- planのときに出てくる
-/+
は削除して再作成って意味 - Atlasっていうterraformの設定ファイルバージョン管理 & 実行してくれるみたいなTerraformのremoteのサービスがある
depends_on
で実行順序を変えられて、terraform graphコマンドで順序が見られる- 単にresourceを削除してterraform applyすればdestroyしてくれる
- provisioningに失敗したら
tainted
なresourceになって次はまた新しいのを再生成する - variableを
{}
にしておくとplan時に対話形式で入力することができる variable "hoge" {}
で変数宣言して使うときは、${var.hoge}
、default値は宣言時のブレース内に書く- 変数は、環境変数か実行時の引数、実行時にファイル指定、
terraform.tfvars
っていう決まった名前のファイルに書いておくことで渡せる - outputでapplyのときにコンソールに出力することができるし、markしておくおことができてterraform outputコマンドであとで簡単に取り出せる
- providerがawsなら
AWS_ACCESS_KEY_ID
、AWS_SECRET_ACCESS_KEY
、AWS_DEFAULT_REGION
を読み込んでくれる
そもそもAWSについて知らなかったこと
- ARN
Amazon リソースネーム (ARN) は、AWS リソースを一意に識別します。IAM ポリシー、Amazon Relational Database Service (Amazon RDS) タグ、API 呼び出しなど、明らかに全 AWS に渡るリソースを指定する必要がある場合、ARN が必要です。
- 各リージョンにdefault VPC security Groupがある
- secret_keyとかってどうやって発行されてるの? https://console.aws.amazon.com/iam/home?region=us-east-1#/security_credential にある。
- IAMはAWS Identity and Access Management の略
slackのweb api経由のchat.postMessageでattachmentsが送れない
JSON.stringifyする必要があった。
NG
"attachments": [{ "text": "hoge" }]
OK
"attachments": JSON.stringify([{ "text": "hoge" }])