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

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:

ScalaでHTTPクライアントのライブラリは何を使えばいいのかと思ってAkkaを調べる

scalaのhttpクライアント

今までPlayしかやったことないからWSしか知らなくて、Playじゃないやつでhttpクライアント的なライブラリって何を使えばいいんだろうってのがスタート。Comparing Scala's HTTP client libraries - implicitdefとか見てdispatchってのがいいのかなーと思って同僚に質問してみたらakka-httpがよさそうって話になったので、何者なのかよく分からないので理解をしたいと思って調べた。

そもそもAkkaとは?

What is AkkaのGoogle翻訳

What is Akka? — Akka Documentationの冒頭を適当に日本語訳。

我々は正しい、分散、同時、フォールトトレラントでスケーラブルなアプリケーションを作成することはあまりにもハードであると信じています。我々は間違ったツールと抽象化の間違ったレベルを使用しているので、それはですほとんどの時間。アッカは、それを変更するためにここにあります。アクターモデルを用いて、我々は、抽象化のレベルを高め、より多くの詳細については、反応性マニフェストを、スケーラブルな弾力性と応答性の-参照アプリケーションを構築するためのより良いプラットフォームを提供します。フォールトトレランスのために我々は通信業界は、自己治癒すると停止することはありませんシステムアプリケーションを構築するために大きな成功を収めて使用されている "それがクラッシュしましょう」モデルを採用しています。アクターは、透明分布と真にスケーラブルでフォールトトレラントなアプリケーションのための基礎のための抽象化を提供します。

うーん、なんとなく分かったような分からないような・・・

Akka とはググる

www.slideshare.net

はぁー、、これはとっても分かりやすい。そもそもアクターモデルという原理があってそれを実装してるのがAkkaということかな。 今日はここまで

Xcode上でImplicit declaration of function 'notify_cancel' is invalid in C99 でハマった

Pureeをcocoapodsで導入しようとしてたら

Pureeが依存してるYapDatabaseというやつがCocoaLumberjackに依存してて、CocoaLumberjackのところでタイトルのエラーが出た。

Implicit declaration of function 'notify_cancel' is invalid in C99

f:id:masato47744:20170422004819p:plain

notify_cancelメソッドをクリックすると、osのコードっぽいところ/usr/include/notify.hにいきつく。

f:id:masato47744:20170422004848p:plain

Implicit declaration of function '' is invalid in C99というやつでググるとコンパイラのフラグでどうのこうのとか、osのコードっぽいところまできたので、xcode7.3.1とかxcode8とかswiftのバージョンとかを疑ってしまって、CocoaLumberjackをビルド対象から外せないかとか、other_c_flagsとかで無視できるってissueとかにつられて、盛大にはまってしまった。

敵はここにいた・・

そもそもCocoaLumberjack単体でビルドできるのかとか試してたけどなんの問題もなくてどうしようかなーと考えてて、ふと、エラーが出る冒頭で、#include <notify.h>としてるんだけど、そこをクリックすると、なんとFoxのSDKのNotify.hに飛んだ:joy:

f:id:masato47744:20170422004913p:plain

解決策

Podfileに追記

+  # HEADER_SEARCH_PATHSからfoxSdkを削除する
+  def remove_fox_sdk_from_header_search_path(pod)
+    workDir = Dir.pwd
+    xcconfigFilename = "#{workDir}/Pods/Target\ Support\ Files/#{pod}/#{pod}.xcconfig"
+    xcconfig = File.read(xcconfigFilename)
+    newXcconfig = xcconfig.gsub('"${PODS_ROOT}/Headers/Public/foxSdk"', '')
+    File.open(xcconfigFilename, "w") { |file| file << newXcconfig }
+  end
+
+  ['CocoaLumberjack', 'YapDatabase'].each do |proj|
+    remove_fox_sdk_from_header_search_path proj
+  end

cf: http://dev.classmethod.jp/smartphone/iphone/cocoapods-inherited/

役割を明確化すると働きやすいなと感じた

今まで

入社して開発メンバーとして仕事をしてきて、いつのまにかチームリーダー的な役割になった。 スケジュール間に合うかベロシティを計測したり、やることがなくならないようにタスクを検討したり、新卒メンバーの受け入れをやったり、ミーティングの調整・ファシリテーションしたり、外部との窓口やユーザーの問い合わせに対応したり、1日中プルリクレビューしたり、その他のこぼれ落ちていく雑用を拾ったりしてた。

その結果、いろいろ抱え込みすぎて辛くなった。

役割の明確化

このままだと辛いと言ったらすんなり部署を変えてもらった。 チーム運営みたいなところから離れて今は開発のための開発をする部署に異動になった。

今まで嫌々拾っていた雑用やこぼれ球も開発を支援するという役割になったことで、ポジティブに向き合えるようになった。 雑用も一つのタスクとして記録に残していこうとか、どうやったらこの雑用を自動化できるかとか、部全体として他のチームでも同じようなタスクを巻き取れないかとかそういうことを考えながら取り組めているので楽しい。あと、今までやりたいと思っていたインフラまわりの開発が多いので学ぶことが多くて楽しい。

自分は、リーダーとしてみんなを引っ張っていくというより、メンバーが働きやすくなるようにサポートするという方が性にあってる気がするなーと思った。

今後

インフラもアプリもサーバーもフロントもチーム運営もできるスーパー便利屋になっていきたい。