Google Cloud Platformの環境をterraformで作る
そもそもGoogle Cloud Platformとは
Googleが出してるAWS的なやつ。GCPと略す。だいたいの比較表。
なんでAWS使わないの?
コンテナでぽんぽんアプリケーションをdeployしたいんだけどそれをAWSでやろうとすると、ECSというサービスを使うことになる。でも、このECSというのがdeployまわりの取り扱いがめんどくさくてちょっといけてない。 で、同じようなコンテナのスケジューリングとかやってくれるソフトウェアでkubernetesというのがあるんだけど、それをAWS上にたてようとするとちょいめんどい。GCPにはGKE(Google Container Engine)というkubernetesのマネージドサービスが最初から用意されてるのでこれを使いたいから。
GCPにおけるprojectという概念
GCPは一つのアカウントに対して、projectという概念を持ってる。たくさんprojectは作ることができる。すごくざっくりだけど、AWSでいうVPCぐらいの単位かも。
GCPにおける地域とゾーン
- AWSでいうregionはGCPだと地域で、アジアだと日本(asia-northeast1)と台湾(asia-east1)がある
- AWSでいうazはGCPだとゾーンと言って、日本には1a〜1cまである。
GCPのprovider
ここからterraformの話。とにもかくにもproviderを定義する。 GOOGLE CLOUD PROVIDER | terraform
provider "google" { credentials = "${file("../account.json")}" project = "elated-bebop-161001" region = "asia-northeast1" }
- account.jsonというのは、GCPのコンソールから認証情報(秘密鍵が書いてある)ファイルをjson形式で落としてきたもの。
- projectは上で言及してるもので全GCP上で一意になるもの。Project名ではなくIDが振られるのでそれを書く。
- regionは地域。ここでは日本を指定する。
terraformの実行🚀
terraformコマンドで直接実行してもいいけど、いろんなprojectを掛け持ちしてるとローカルのversionを揃えるのが大変なので、docker imagesで提供されているものを使う。 参考にしたサイト 実行するときにいろんなことをしたり、長いコマンドうつの面倒なのでRakefileを作ってそれで実行する。
Rakefileの中身とはまったポイント
- 🙅 terraformのdocを見ると
GOOGLE_CREDENTIALS
に認証情報のjsonの中身をつめて渡せばそれを使ってくれるとかいてあったが、これをdockerのコマンドに渡すようにするとstdoutに表示されてしまう - 😓 出力されないように環境変数にjsonの中身をいれようと試行錯誤したが、jsonの末尾の改行を取り除かないといけないのと、private keyが記述されてるのでそこに文字列として
\n
が入っててそれが環境変数として渡すと評価されてしまって改行扱いになってしまう
docker実行時に、GOOGLE_APPLICATION_CREDENTIALS
で認証情報のファイルの場所を渡してあげるようにする💯
@terraform_version = "0.8.8" @docker = %Q(docker run --rm -v ${PWD}:/work -w /work/terraform -e GOOGLE_APPLICATION_CREDENTIALS=/work/account.json hashicorp/terraform:#{@terraform_version}) namespace :tf do task :init do sh %Q(#{@docker} remote config \ -backend=gcs \ -backend-config="bucket=my-first-project-terraform-state-20170309" \ -backend-config="path=terraform.tfstate" \ -backend-config="project=elated-bebop-161001") end task :plan do sh %Q(#{@docker} plan) end end
- GCPの方の https://developers.google.com/identity/protocols/application-default-credentials に
GOOGLE_APPLICATION_CREDENTIALS
を使えって書いてあった。 - これをすれば、providerの方に
credentials = "${file("account.json")}"
って書かなくてOK
まとめ
俺はなぜあんな無駄な時間を・・ by 三井 寿
teeコマンドの使いどころ
標準入力を標準出力とファイルに出力する
というコマンドです。
なんか使いどころが分からないなーって思ってたけど、コマンドの結果をファイルに書き出してそれを別のコマンドに渡すってのをcircle ci上でやってて、あー、画面上でもこのファイルの中身見たいなーって思って、あっ!こういうときにteeを使いたくなるのか!!!ってなった。
terraformでif文的なものを使ったユースケース例
usecase
EC2インスタンス、ELB、security_groupあたりがまとまったmoduleがあるんだけど、特定の役割のインスタンスにだけiam_roleにSNSFullAccessをつけたい。
設定内容
variable "allow_sns_full_access" { default = false }
- 対象のリソースでtrueならcount = 1、falseならcount = 0にする。
resource "aws_iam_role_policy_attachment" "app_sns_full_access" { count = "${var.allow_sns_full_access ? 1 : 0}" role = "${aws_iam_role.app.name}" policy_arn = "arn:aws:iam::aws:policy/AmazonSNSFullAccess" }
- 利用したいやつだけ、trueにする
module "app" { source = "../modules/app" allow_sns_full_access = true }
結果
+ module.app.aws_iam_role_policy_attachment.app_sns_full_access
policy_arn: "arn:aws:iam::aws:policy/AmazonSNSFullAccess"
role: "app"
AWSのロードバランサーあたりのセキュリティーグループ制限がさくっと緩和できなかった
ロードバランサーあたりのセキュリティグループ 5
ご担当者様 平素よりお世話になっております。 AWSカスタマーサービスの藁品でございます。 折角ご申請いただきましたのに誠に申し訳ございませんが、「Classic Load Balancer」の 「ロードバランサーあたりのセキュリティグループの上限」を緩和することは致しかねます。 理由といたしましては、「ロードバランサーあたりのセキュリティグループ」は 「ネットワークインターフェースあたりのセキュリティグループ」に基づいている為でございます。 なお、下記サービスの上限値の変更を行うことで「ロードバランサーあたりのセキュリティグループの上限」の 緩和をすることも可能でございますが、この変更を行うことによりネットワークインターフェースを使用している 全リソースに影響を及ぼしますのでご留意くださいませ。 1) セキュリティグループあたりのインバウンド/アウトバウンドルール(デフォルト50)を減らす。 2) ネットワークインターフェースあたりのセキュリティグループを(デフォルト5)増やす。 引き続き緩和をご希望ということであれば、ケースにご返信いただくかたちでお知らせいただければ幸いです。 その他ご不明点、ご要望等ございましたら、ご遠慮なくお問い合わせくださいませ。 何卒よろしくお願いいたします。
terraformでsecurity groupをmodule化するときに気をつけること
IPアドレスを定義した共通のsecurity groupを作りたい
あるプロジェクトで外部のIPアドレスなどをまとめたものをsecurity groupとしてmodule化している。このアイディア自体はいいんだけど、環境ごとにこれを利用しようとした場合に失敗する。
例えば、こんな感じでmodulesに外部IPを書いたようなsecurity_groupをつくる。
terraform ├── development │ └── main.tf ├── staging │ └── main.tf ├── production │ └── main.tf └── modules └── external_ips_sg ├── security_group.tf └── outputs.tf
で、これらを各環境のmain.tfで利用すると、同一のsgを作ろうとするので、security groupの名前を変える必要がある。
module "external_ips_sg" { source = "../modules/external_ips_sg" }
ここで、security groupのnameを変数化しておかないと同じ名前のsecurity groupを作ろうとして失敗する。
解決策
当たり前の話だけど、変数でnameを変えられるようにしておく。
└── modules └── external_ips_sg ├── variables.tf ├── security_group.tf └── outputs.tf
variable "env" {} variable "vpc_id" {}
module "external_ips_sg" { source = "../modules/external_ips_sg" env = "dev" vpc_id = "vpc-xxxxxxxx" }
resource "aws_security_group" "a" { name = "external-ips-${var.env}" vpc_id = "${var.vpc_id}"
ちなみに、Security GroupのDescriptionはForce new Resource
なのであとから変更できないので、何も設定しない方がいいかも。ちょっと文言変えたいなーってときにもう変更できない。何も設定しなければ、 Mangaed By Terraform
のデフォルト文言が設定される。何か名前付けをしたい場合は、TagのNameを使おう。
あと、vpc_idを入れるのを忘れがち。vpc_idはなければ自動的にdefautl vpcが設定されるのでうまくいってしまう。
awsのsecurity groupにはルールの上限がある
IPアドレスをたくさん追加しようとしたら:scream:
各拠点のIPアドレスを追加してほしいという依頼で、terraform planで問題なかったので、applyしたらエラーが出た。
* aws_security_group.elb_app: Error authorizing security group ingress rules: RulesPerSecurityGroupLimitExceeded: The maximum number of rules per security group has been reached. status code: 400, request id: 88277351-1fc1-4e48-a005-3fe046a8145b
制限があるのを知らなかった。ドキュメントによると制限は50だった。
多分手動で追加してれば51個目でエラーになるから気づけるけど、terraformだとapplyしてはじめてエラーになる。なので、エラーになった瞬間、該当のsecurity groupの設定は空っぽになってしまう。
気をつけよう :cry:
とくにsecruty groupの変更は要注意
ingressとか書くときに、cidr_blocksと、security_groupsを指定できるけど、つい間違ってcidr_blocksの方にsecurity_groupのidを指定しまったことがあった。これも、planのときは問題ないけど、applyで指定方法が間違ってるってことでエラーになる。
ingress { ... cidr_blocks = [ "${aws_security_group.hoge.id}" ] }
まとめ
validateしたい。terraformにPRチャンスかもしれない。
aws_security_groupのdescriptionはupdateできない
descriptionを気軽な名前で作ってしまって、あとでちょこっと変更したいなーと思って terraform planするとforce new resourceになってしまう。
ドキュメントにも書いてあった。
AWS: aws_security_group - Terraform by HashiCorp
description
- (Optional, Forces new resource) The security group description. Defaults to "Managed by Terraform". Cannot be "". NOTE: This field maps to the AWS GroupDescription attribute, for which there is no Update API. If you'd like to classify your security groups in a way that can be updated, use tags.
aws_security_groupは気軽にdescriptionを設定しないようにしよう!識別したいだけなら、tags.Nameを使えばok
今日覚えたやつメモ 2017/1/27
rubyとかrailsとかRSpecのこと
class << self
Rubyist Magazine - Ruby 初級者のための class << self の話 (または特異クラスとメタクラス)class << self
で定義したクラスメソッドがmoduleがincludeしたときに呼べない。- moduleなんかscalaのtraitとかswiftのprotocol的なものを思い浮かべてしまってはまってしまった。
- Rails の module ClassMethods がやっている事 - Qiita includeするときは色々必要。
- includeせずに、moduleは名前空間的に使える。
Hoge.Fuga.foo
みたいにいけちゃう。scalaのobjectっぽい感じ。 - クラス・モジュールの概念 Ruby - Qiita
- Railsの正規表現でよく使われる \A \z って何?? - Qiita
- ActiveSupport::Concernの使い方とテスト(RSpec) - Qiita
- Rails のルーティング | Rails ガイド
- routingのことほとんど忘れてた
- Railsのモデルの作成、検索、更新、削除のよく使うメソッドのまとめ - Rails Webook
- activerecordのこともほとんど忘れてた
- Rails Assetの管理についてまとめる - Qiita
- 使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」 - Qiita 分かりやすかった
- 使えるRSpec入門・その3「ゼロからわかるモック(mock)を使ったテストの書き方」 - Qiita
- mockするのが超簡単。
allow
とかallow_any_instance
とか - メモ化 Ruby で メモ化カッコカリ( #rubytokai 発表メモ) - 名古屋で数学するプログラマ(仮)
fluentdのこと
- fluent-logger-rubyがunix socket対応したのがつい最近だった。
- fluent-logger-ruby/ChangeLog at master · fluent/fluent-logger-ruby · GitHub
- Macでdmgでtd-agent入れるとunix socketの権限がrootになって通常ユーザーだとconnectできない
dockerとmacでunix domain socketを共有する
ECSでの動的なネットワークポートマッピングとfluentd
固定ポートだとインスタンスの数以上にコンテナが増やせない
ECS上でfluentdを複数コンテナで動かして集約サーバーとして動かそうとしている。 最初だから試しにautoscaling groupで1つのインスタンスだけ立ち上げて、コンテナを2つ(desired count = 2)なサービスで立ち上げようとした。
でも、2台目のコンテナはportが埋まってますよというエラーで立ち上がってくれずにstoppedになってしまう。 そう言われてみればそうだなと。
ALBなら動的なネットワークポートマッピングが可能だが・・
Amazon ECSのELB構成パターンまとめ(ALB対応) | Developers.IOとか、Amazon ECS と AWS Application Load Balancer の組み合わせを試しているメモ - ようへいの日々精進XPとかを見ると動的にポートマッピングしてるからALBを使えばいけそう。しかし、healthcheckはHTTPかHTTPSでやらないといけない。
fluentdの集約サーバーはforwardプロトコルで24224で待ち受けていて、ELBならTCPでのhealthcheckでOKなので問題なかったが、ALBにするならばHTTPでhealthcheckできるようにしないといけない。 healthcheckに関してはfluentdはin httpも受け付けているのでそれでhealthcheckできるようにすればいける。
ただ、healthcheckはいけたとしても、結局forward Output Pluginはforward protocolでhttpじゃないのでダメだ。。
まとめ
集約サーバー側でforwardを使う限りALBは使えない。
fluent-plugin-s3でなかなかs3にアップロードされない
time_slice_formatの値をexampleに従って、%Y%m%d-%H
にしたんだけど、なかなかs3にアップされない。
buffered_pathには吐かれてるんだけどなかなかS3には上がってこない。
instanceのpoliciyかなーとかs3のbucketのpoliciyかなーとか色々迷っててうーんってハマってたらいつのまにかアップされてた。 どうやら1時間ぐらいたってた。
で、これはなぜかっていうと、time_slice_formatが時間ごとの値になってるからファイル名が変更になるまではs3にアップされない。s3にアップされて欲しいintervalを設定するのが、flush_intervalだった。
flush_intervalはplugin-s3のものではなく、そもそもbuffered-outputのもの。
flush_interval 60s
と書けば1分ごとにs3にアップされた。
terraformのecs_registry.repository_urlにhttpsがついてるのを消す
バグだったようだ。mergeされたのでそのうち修正版がリリースされるはず。それまでは"${replace(aws_ecr_repository.test.repository_url, "https://", "")}"
でしのぐ。
追記
terraform v0.8.5 で取り込まれた。
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に戻るのを繰り返してる。 最初は簡単なことも分からず進捗も悪くてかなり苦しいんだけど、だんだんできるようになっていくのが好き。マゾなのかもしれない。