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" }])
XcodeのSchemeで値を切り替える方法を探る
背景
APIの向き先とかをTargetを増やしてswiwt other flagsで#if DEBUG
とか#if STAGING
でやってる。ので環境が増えるたびにtargetが増えていってしまうのでやめたい。
実行時に環境変数的に値を渡せないか
SchemeのPre-actionsで追加する
- Build、Run、Test、Archiveで共通して書けるのは、Pre-actionsとPost-actionsである。
- ここにはshが書ける。ここのshの中で、
export HOGE=1
を書いてプログラム内から取得できるか。#if HOGE
-> ダメNSProcessInfo.processInfo().environment["HOGE"]
-> ダメ- InfoPlistにcustom keyを追加して、valueに$(HOGE) -> ダメ
- Build SettingsにUser-Defined Settingとして追加して、valueとして、$(HOGE) -> ダメ
SchemeのEnvironment Variablesを使う
NSProcessInfo.processInfo().environment["HOGE"]
で取得できた。- しかし、Environment VariablesはRunとTestにしか存在しないので、BuildやArchive時に使えない。
- xcodebuildコマンド経由でなら環境変数を渡せる可能性はある。
xcconfigの切り替え
やっぱり、これでやるしかなさそうだ。
fastlaneでflags渡す
https://github.com/fastlane/fastlane/issues/408
fastlaneでother swift flags渡す方法もあった。社内配布はどうせfastlane使うのでありかもしれない。
Use this in swift: xcargs: "OTHER_SWIFT_FLAGS='$(inherited) -DVARNAME=1'"
Google Apps scriptでインデント整理する方法
Tabを押すと勝手になる。逆に無駄なタブは打てない。全選択してTabでいける。
Play FrameworkのRequest.remoteAddressの値が突然変わった
現象
あるサービスの仕組みで処理結果を通知してくるために、自サーバーのapiをたたきにくるときがある。この際に、サーバー側では本当にそのサービスからのリクエストかどうかをみるために、あらかじめIPアドレスを設定しておいてvalidateしてるが、あるリリースのバージョンからvalidateに失敗するようになった。 ログを見てみると、該当サービスじゃなくてELBからのIPアドレスになっていた。
原因
- play frameworkのverが勝手に上がってしまっていた。
- addSbtPluginで指定しているplayは2.4.3
- kamon-playが依存しているのがplay2.4.6だった
- play2.4.3 -> play2.4.4 でx-forwarded-forヘッダの取得まわりで変更が入っていた
- x-forwarded-forは簡単にspoofingできてしまうので、信頼できるproxyからだった場合にのみremoteAddressに
x-forwarded-for
ヘッダを利用するようになった - サーバーでは
play.http.forwarded.proxies
は設定していなかったため、remoteAddressがx-forwarded-for
の値ではなく、単にhostの値(ELB)が使われるようになった。
対応
play.http.forwarded.proxies = [ "0.0.0.0/0", "::/0" ]
を追加- Spec追加する
- ただし、playのversionが上がるのは厳しいので、play2.4.6が入らないようにしたい