まーぽんって誰がつけたの?

iOS→Scala→インフラなおじさん技術メモ

EC2をremote-execでprovisioningする方法

追記:そもそもuser-dataがあることを知らなかったのでこっちを使おう

www.mpon.me

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を実行する。 なるほどーうまい。

github.com

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の設定をいじって適用とかはしてきたけど自分で一から作ったことない。仕組みを理解するには、一から自分で作ってみるのが早そうだと思ったので自分で一から作ってみることにした。 ので、素直にこれをやることにした。

www.terraform.io

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_IDAWS_SECRET_ACCESS_KEYAWS_DEFAULT_REGIONを読み込んでくれる

そもそもAWSについて知らなかったこと

  • ARN

Amazon リソースネーム (ARN) は、AWS リソースを一意に識別します。IAM ポリシー、Amazon Relational Database Service (Amazon RDS) タグ、API 呼び出しなど、明らかに全 AWS に渡るリソースを指定する必要がある場合、ARN が必要です。

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'"

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が入らないようにしたい

sh経由で実行するとeオプションつけてても途中で終了しない

Jenkinsのシェルの実行で#!/bin/sh -xeってつけて途中でエラーが起きたらその時点で、 jobをfailureにするっていうのはよくあると思うんですが、 そのなかで、sh hoge.shみたいに別のshを起動すると、 hoge.shの中でエラーがあったときに、eオプションつけててもhoge.shは一番最後まで実行されちゃう。

どういうことかというと、hoge.shはこんな感じだとして

#!/bin/sh -xe

ls x
echo "hello"
  • 直接実行 -> lsで止まる
$  ./hoge.sh
+ ls x
ls: x: No such file or directory
  • sh経由で実行 -> 最後まで実行される
$  sh hoge.sh
ls: x: No such file or directory
hello

やっぱ、sh経由だとxeオプションつけてても途中で終わってくれないみたい。

sbt testが途中で止まってしまうのを調査する

状況

f:id:masato47744:20170422005236p:plain

という状態になってしまって困った。sbtがグローバルで利用するsbtoptsもプロジェクト共通で指定してるやつがあって他の人は問題なさそうとのこと。ただ、OutOfMemoryなのでJVMのステータスみたいのが見たいと思って調べ始めた。

:dancers: JVMのパフォーマンスを見るにはjstat

jstat - Java 仮想マシン統計データ監視ツール というのがある。 使い方はこんな感じ :point_down: jpsでプロセス番号を探して、jstat -gcutil <プロセス番号> <出力の間隔>。 ざっくり、S0とかS1とかは各領域の使用率みたいなのを表してて、GCってついてるやつはガベージコレクションの回数を表してる。

$ jps
66803 sbt-launch.jar
67844 Jps
$ jstat -gcutil 66803 200
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
  0.00   6.25   4.31  18.41  84.36  91.64     43    0.051     0    0.000    0.051
  0.00   6.25   6.00  18.41  84.36  91.64     43    0.051     0    0.000    0.051

:flashlight:sbtが途中で止まってるときはどんなことが起きていたか

JVMは、New領域(さらにEden、S0、S1と分かれる)とOld領域っていうのがあるが、Eden、Old領域が100%で張り付いていて、Full GCがずーっと続いている状態だった。最後OOMで終わったときにはFullGCが357回。

  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
・
中略
・
  0.00 100.00 100.00 100.00  97.14  95.17    144   15.169   273 1134.327 1149.496
  0.00  99.99 100.00 100.00  97.14  95.17    144   15.169   274 1139.459 1154.628
  0.00  99.99 100.00 100.00  97.14  95.17    144   15.169   274 1139.459 1154.628
  0.00  99.99 100.00 100.00  97.14  95.17    144   15.169   274 1139.459 1154.628
  0.00  99.99 100.00 100.00  97.14  95.17    144   15.169   274 1139.459 1154.628
  0.00  99.99 100.00 100.00  97.14  95.17    144   15.169   274 1139.459 1154.628
  0.00 100.00 100.00 100.00  97.14  95.17    144   15.169   275 1140.528 1155.697
  0.00 100.00 100.00 100.00  97.14  95.17    144   15.169   275 1140.528 1155.697
  0.00 100.00 100.00 100.00  97.14  95.17    144   15.169   275 1140.528 1155.697
  0.00 100.00 100.00 100.00  97.14  95.17    144   15.169   275 1140.528 1155.697
  0.00 100.00 100.00 100.00  97.14  95.17    144   15.169   275 1140.528 1155.697
  0.00 100.00 100.00 100.00  97.14  95.17    144   15.169   275 1140.528 1155.697
  0.00 100.00 100.00 100.00  97.14  95.17    144   15.169   275 1140.528 1155.697
・
中略
・
  0.00  99.99 100.00 100.00  97.14  95.17    144   15.169   357 1529.127 1544.296
  0.00  99.99 100.00 100.00  97.14  95.17    144   15.169   357 1529.127 1544.296
  0.00  99.99 100.00 100.00  97.14  95.17    144   15.169   357 1529.127 1544.296
  0.00  99.99 100.00 100.00  97.14  95.17    144   15.169   357 1529.127 1544.296
  0.00  99.99 100.00 100.00  97.14  95.17    144   15.169   357 1529.127 1544.296
  0.00   0.00   0.25  12.98  91.66  89.80    144   15.169   357 1530.162 1545.331
  0.00   0.00   0.25  12.98  91.66  89.80    144   15.169   357 1530.162 1545.331
↑ oomで終了

プロジェクトで推奨していたsbtoptsのNewSizeがよくなかった?

今まで特に問題なかったsbtoptsの設定

-J-Xmx4096m
-J-Xms2048m
-J-Xss1M
-J-XX:MetaspaceSize=512m
-J-XX:MaxMetaspaceSize=1024m
-J-XX:+UseConcMarkSweepGC
-J-XX:+CMSClassUnloadingEnabled
-J-XX:NewSize=1024m
-J-XX:MaxNewSize=3072m
-J-XX:NewRatio=8
-J-Xss2M
-Duser.timezone=GMT

Old領域が100%で詰まっているっていうことで-J-XX:NewSize=1024m-J-XX:MaxNewSize=3072mが怪しい。そもそも、NewRatioを指定しているので、矛盾している?ような状態になっていた。 NewRatio=8ということは、New領域 : Old領域 = 1 : 8ということらしい。 しかし、MaxNewSizeを3072mとしていたので、多分、つまり始めてしまったころには、New領域 : Old領域 = 3072 : (4096 - 3072) = 3 : 1ぐらいに逆転状態になってしまってしまったのではと推測。 FullGCしてもOld領域が減らないのはなんでなのかはよく分かってないけども。

NewRatio=8だけでやってみた

f:id:masato47744:20170422005346p:plain

FullGCも10回で、sbt testも最後までいくようになった。もうちょいパラメータチューニングしてみてもいいというか、ヒープサイズ自体をもっとでかくしてもいいかもとは思うけど、とりあえずいけるようになったからよかった。

まとめ

jstatというコマンドを生まれて初めて打ちました:tada:

scala.PartialFunction condOpt を使おう

同僚に教えて貰った。

scala.PartialFunction condOpt を使おうに書いてあるサンプルを見るとわかる。

ポイントは

  • match式を使っていて
  • そのmatch式の戻り値の型がOption
  • 先頭からmatchさせていったとき、一番最後以外のmatchした場合については、明示的にSomeに包んで返している
  • なので最後は case _ => None というのがくる

つまりモチベーションとしては

  • 毎回自ら明示的にSomeに包むの面倒
  • case _ => Noneという定形コードうざい
PartialFunction.condOpt(code){
  case SimpleLocale(language) => Lang(language, "")
  case CountryLocale(language, country) => Lang(language, country)
}

// これの返り値は`Option[Lang]`になる!

とにかく、match書いてわざわざSomeで包んで、ないときはNoneっていうコードを書いていたらcondOptを思い出してあげてほしい:pray: