Terraformで配列をloopする時はfor_eachを使った方がいい
AWSのパラメータストアをTerraformで作成したのですが、for_eachを知らずにcountを使って作成したところ、追加や削除の際に色々と意図しない挙動になったので、回避策について備忘録を残しておきたいと思います。
環境
terraform(0.12.07)
やりたかったこと
パラメータストアに登録するデータが複数あったため、配列のように定義して一括登録したかった。
発生した問題
登録後にさらにパラメータストアを追加したところ、追加分だけでなく他のパラメータについても差分と判定された。
はじめに書いたtfファイル
以下が最初に書いたパラメータストアの設定になります。作成は問題なく完了しました。
paramete_store.tf
resource "aws_ssm_parameter" "ssm_parameter_app" { count = "${ length( var.ssm_parameter_app_list.params ) }" name = "/${var.env}/${values(var.ssm_parameter_app_list.params)[count.index].name}" value = "${values(var.ssm_parameter_app_list.params)[count.index].value}" type = "SecureString" }
variable.tf
variable "env" { default = "dev" } variable "ssm_parameter_app_list"{ default = { params = { param1 = { name = "param1" value = "xxxxxxxxxxxx" } param2 = { name = "param2" value = "xxxxxxxxxxxx" } param3 = { name = "param3" value = "xxxxxxxxxxxx" } ・ ・ ・ param10 = { name = "param10" value = "xxxxxxxxxxxx" } } } }
この書き方の問題点
- 追加の際に追加分以外の差分が発生する
- 途中の要素の削除においても、削除分以外の差分が発生する
例えば、param11を追加した場合、param2以降の全ての要素が差分として発生します。また、削除においてもparam5を削除した場合も同様にparam5以降の要素が差分となります。
なぜこのような結果になるかというのはstateファイルを確認することで分かりました。以下stateファイルの抜粋になります。
要素のkeyが数字となっており、param1-10でソートされていることが分かります。
stateファイル(抜粋)
"instances": [ { "index_key": 0, "schema_version": 0, "attributes": { "allowed_pattern": "", "arn": "arn:aws:ssm:ap-northeast-1:123456789012:parameter/dev/param1", "description": "", "id": "/dev/param1", "key_id": "alias/aws/ssm", "name": "/dev/param1", "overwrite": null, "tags": {}, "tier": "Standard", "type": "SecureString", "value": "xxxxxxxxxxxx", "version": 1 }, "private": "bnVsbA==" }, { "index_key": 1, "schema_version": 0, "attributes": { "allowed_pattern": "", "arn": "arn:aws:ssm:ap-northeast-1:123456789012:parameter/dev/param10", "description": "", "id": "/dev/param10", "key_id": "alias/aws/ssm", "name": "/dev/param10", "overwrite": null, "tags": null, "tier": "Standard", "type": "SecureString", "value": "xxxxxxxxxxxx", "version": 1 }, "private": "bnVsbA==" }, { "index_key": 2, "schema_version": 0, "attributes": { "allowed_pattern": "", "arn": "arn:aws:ssm:ap-northeast-1:123456789012:parameter/dev/param2", "description": "", "id": "/dev/param2", "key_id": "alias/aws/ssm", "name": "/dev/param2", "overwrite": null, "tags": null, "tier": "Standard", "type": "SecureString", "value": "xxxxxxxxxxxx", "version": 1 }, "private": "bnVsbA==" }, { "index_key": 3, "schema_version": 0, "attributes": { "allowed_pattern": "", "arn": "arn:aws:ssm:ap-northeast-1:123456789012:parameter/dev/param3", "description": "", "id": "/dev/param3", "key_id": "alias/aws/ssm", "name": "/dev/param3", "overwrite": null, "tags": null, "tier": "Standard", "type": "SecureString", "value": "xxxxxxxxxxxx", "version": 1 }, "private": "bnVsbA==" },
配列のキー(param1-10)でソートされた結果としては以下のようになります。param10がparam1の次に並び替えされてしまうため、上記のような差分が発生することになります。param1ではなくparam01と定義することでソートの問題は解決できるのですが、追加は正常にできるものの削除の問題は残ったままとなります。
param1 param10 param2 ・ ・ param9
このように意図しない差分を防ぐためには、for_eachを使えば解決できることがわかりました。
for_eachを使った書き方(その1)
variableに key=valueの連想配列を定義し、for_eachを利用して以下のようにセットすることができます。
paramete_store.tf
resource "aws_ssm_parameter" "ssm_parameter_app" { for_each = var.ssm_parameter_app_list name = "/${var.env}/${each.key}" type = "SecureString" value = each.value }
variable.tf
variable "ssm_parameter_app_list"{ default = { "param1" = "xxxxxxxxxxxx" "param2" = "xxxxxxxxxxxx" "param3" = "xxxxxxxxxxxx" ・ ・ "param10" = "xxxxxxxxxxxx" } }
上記の書き方でparameter storeを作成すると以下のようなstateファイルになります。index_keyにparam1-10が設定されるため、追加や削除の際に意図しない差分が発生することはありません。
"instances": [ { "index_key": "param1", "schema_version": 0, "attributes": { "allowed_pattern": "", "arn": "arn:aws:ssm:ap-northeast-1:123456789012:parameter/dev/param1", "description": "", "id": "/dev/param1", "key_id": "alias/aws/ssm", "name": "/dev/param1", "overwrite": null, "tags": null, "tier": "Standard", "type": "SecureString", "value": "xxxxxxxxxxxx", "version": 1 }, "private": "bnVsbA==" }, { "index_key": "param10", "schema_version": 0, "attributes": { "allowed_pattern": "", "arn": "arn:aws:ssm:ap-northeast-1:123456789012:parameter/dev/param10", "description": "", "id": "/dev/param10", "key_id": "alias/aws/ssm", "name": "/dev/param10", "overwrite": null, "tags": null, "tier": "Standard", "type": "SecureString", "value": "xxxxxxxxxxxx", "version": 1 }, "private": "bnVsbA==" }, { "index_key": "param2", "schema_version": 0, "attributes": { "allowed_pattern": "", "arn": "arn:aws:ssm:ap-northeast-1:123456789012:parameter/dev/param2", "description": "", "id": "/dev/param2", "key_id": "alias/aws/ssm", "name": "/dev/param2", "overwrite": null, "tags": null, "tier": "Standard", "type": "SecureString", "value": "xxxxxxxxxxxx", "version": 1 }, "private": "bnVsbA==" },
ただし、このようにkeyとvalueのセットでリソースを定義できるのであればこの書き方で問題ないのですが、各要素ごとに複数の設定を持つのであればこの方法では難しくなります。
for_eachを使った書き方(その2)
例えば、これまではtypeは「SecureString」固定でしたが、これも変数で定義したいとなった場合は上記の方法では対応できません。
この場合については以下の書き方で対応ができました。
paramete_store.tf
resource "aws_ssm_parameter" "ssm_parameter_app" { for_each = var.ssm_parameter_app_list name = "/${var.env}/${lookup(each.value, "name")}" value = lookup(each.value, "value") type = lookup(each.value, "type") }
variable.tf
variable "ssm_parameter_app_list"{ type = map(map(string)) default = { param1 = { name = "param1" value = "xxxxxxxxxxxx" type = "SecureString" } param2 = { name = "param2" value = "xxxxxxxxxxxx" type = "String" } param3 = { name = "param3" value = "xxxxxxxxxxxx" type = "SecureString" } ・ ・ param10 = { name = "param10" value = "xxxxxxxxxxxx" type = "SecureString" } } }
"instances": [ { "index_key": "param1", "schema_version": 0, "attributes": { "allowed_pattern": "", "arn": "arn:aws:ssm:ap-northeast-1:123456789012:parameter/dev/param1", "description": "", "id": "/dev/param1", "key_id": "alias/aws/ssm", "name": "/dev/param1", "overwrite": null, "tags": null, "tier": "Standard", "type": "SecureString", "value": "xxxxxxxxxxxx", "version": 1 }, "private": "bnVsbA==" }, { "index_key": "param10", "schema_version": 0, "attributes": { "allowed_pattern": "", "arn": "arn:aws:ssm:ap-northeast-1:123456789012:parameter/dev/param10", "description": "", "id": "/dev/param10", "key_id": "alias/aws/ssm", "name": "/dev/param10", "overwrite": null, "tags": null, "tier": "Standard", "type": "SecureString", "value": "xxxxxxxxxxxx", "version": 1 }, "private": "bnVsbA==" }, { "index_key": "param2", "schema_version": 0, "attributes": { "allowed_pattern": "", "arn": "arn:aws:ssm:ap-northeast-1:123456789012:parameter/dev/param2", "description": "", "id": "/dev/param2", "key_id": "", "name": "/dev/param2", "overwrite": null, "tags": null, "tier": "Standard", "type": "String", "value": "xxxxxxxxxxxx", "version": 1 }, "private": "bnVsbA==" },
まとめ
今回は配列をループしたかったためにcountを使いましたが、あまりよくない使い方だったため思わぬところでハマってしまいました。幸いまだ本番リリースされていないサービスだったので再作成することが可能でしたが、terraformの制御文は気をつけるポイントが多いですね。
EKSでEBSのReadIOが高騰した話
はじめに
あるシステムのステージング環境を運用している中で、EKSクラスタのワーカーノードが頻繁にダウンするというか事象が発生しました。
色々な調査の末、なんとか解決できましたので発生の経緯と対応内容を書いておきたいと思います。
環境
- EKS(1.14)
- ワーカーノード(インスタンスタイプ:t3.medium)
発生経緯
あるサービスのステージング環境を作成し開発側に引き渡し、アプリのデプロイが行われシステムテストが進められていました。
突然のNot Ready
開発担当からシステムからレスポンスがなくなったと連絡があったので、確認したところノードのステータスがNot Readyとなっていました。
開発作業に支障が出ていたため、とりあえず該当のEC2を再起動して復旧させました。
その後メトリクスをざっと確認したとところCPUが100% でしばらく張り付いていたので、該当時間帯にCPU使用率の高かったコンテナを調べて開発側に連絡して対応しました。開発中なのでよくあることだと思い、この時はそれ以上深追いもしませんでした。
以降1日1回程度Not Readyが発生
数回は再起動で対処していたのですが、流石に頻度が多くなってきていたのでインスタンスタイプをt3.mediumからc4.largeに変更ししました。別件で手を取られていたこともあるのですが、安易にタイプ変更をおこない一旦様子を見ることにしました。
が、それでも相変わらずNot Readyとなるため、本格的に原因調査することにしました。
その時点でやっと気が付いたのですが、当初見ていたメトリクスがCloudWatchから取得していたものではなくホスト上のメトリクスだったため実際は張り付くまでCPUが高くもなくCPUクレジットを使い切っている状況でもなかったことが分かりました。ここは始めにしっかり見るべきところだったと思います。
EBSの読み取り高負荷
CPU使用率をよく見たところのIOwaitの占める割合が異常に高いことに気がつきました。
そこでEBSへの読み込みもしくは書き込みが高くなっているのではと考えメトリクスを確認したところ、読み取りが最大秒間150MiBでしばらく張り付き、バーストバランスを使い切って0%となっていることが分かりました。バーストバランスがなくなるタイミングでNot Readyとなっていたため、原因はEBSのRead高負荷ということが分かりました。
ReadIOが高いプロセスの調査
Not Readyになるトリガーは分かりましたが、EBSのReadIOが高い原因を引き続き調査しました。
バッチ処理を行うPodがそのノードで常に起動していたため開発側でも調査してもらいましたが、特段不審な処理はないようでした。
インフラ面からは、IOの高いプロセスを特定するため、ノードにログインしてiostatコマンドで高負荷プロセスを確認することにしました。
ログ出力のfluentdのIOが高い可能性があることは分かっていましたが、さすがに秒間150MiBは高すぎですが、再発したタイミングでプロセスを確認したところ、ログ出力用のfluentdが秒間100MiBを超えるIOとなっていました。ログの読み込み設定を変更するなど試しましたが、いずれもIOは高騰するため最終的にfluentdのPodを落とすことにしました。
しかしながら、fluentdを落として以降も別のプロセスでIOが高騰し、しかも特定プロセスではなく様々なプロセスのIOが高いという状況となりいまいち原因がつかめませんでした。
ReadIOとメモリ
しばらくプロセスを眺めるもよく分からない状況が続きました。
そして改めて事象発生時のメトリクスを確認して見たとことろ、メモリと相関関係にあることに気がつきました。当初メモリも確認していたものの数百MBの空きがあったことや、OOM killerも発生していなかったので関連をあまり見ていなかったのですが、下のグラフを見るとメモリ使用量がある一定の閾値を超えた時点でReadIOが高騰し始めるように見えました。他の発生時点のメトリクスも同様の傾向を示していましたのでおそらくReadIOが高騰するトリガーとしてはメモリ使用量だということが分かりました。同時にCPUも上がっていますが、これはReadIOが高騰した結果、IO待ちが発生してCPU使用率が上がったものと考えられます。そのため、今回の発生順としては、メモリが使用率が上がった結果、ReadIOが高騰し、それによりCPU使用率も上がったものと推測できます。
対策
メモリが発生トリガーだと分かったため、以下2点の対策を取ることにしました。
1ヶ月ほど様子見しましたが、今のところ再発もしていないので解消されたものと思われます。
まとめ
今回の事象はなかなか原因がつかめず調査にも苦労しました。また、メモリ使用率が上がったことでなぜReadIOが上がるのかは気になったので調べてみたのですが、明確な原因は分かりませんでした。もしご存知の方がいれば教えて欲しいです。
kubernetesのServiceを使ってプライベートサブネットのRDSに接続する方法
EKSとRDSを利用して構築している環境があるのですが、RDSについてはプライベートサブネットに配置しているため、DB接続するには踏み台サーバを置いてポートフォワードするなどの対応が必要になります。
この方法だと踏み台サーバが必要になるため、以下のような構成でRDSに対して接続経路を用意しています。このPodはsocatが同梱されておりプロキシサーバーとなっています。
Podでプロキシを行いRDSに接続する構成
負荷も高いわけではなく特に問題はないのですが、このためだけにPodを起動させておくのもなーと思い、Podを使わずにプライベートなRDSへ接続できる方法がないか検証した結果、以下のような構成で接続できましたので、やり方を紹介したいと思います。ただし、RDSの接続先についてはDNSではなくIP指定となるためあまり使い勝手の良い方法ではないと思います。
Podを使わずにRDSへ接続する構成
通常ServiceはPodをメンバとして登録しますが、このケースではEndpointsというリソースをメンバとして登録します。そしてEndpointsからRDSへ接続するという形になります。
ELBの作成
以下の設定でロードバランサーを作成します。
当然ですが、セキュリティグループも以下に合わせて作成しておいてください。
Type: CLB
Protocol: TCP
Load Balancer Port: 13306
Instance Port: 30306
Kubernetesのリソースデプロイ
以下がRDS(MySQL)に接続するサンプルになります。
RDSのIPを自環境に合わせて書き換えてapplyしてください。
apiVersion: v1 kind: Service metadata: name: rds-service spec: type: NodePort ports: - protocol: TCP port: 3306 targetPort: 3306 nodePort: 30306 --- apiVersion: v1 kind: Endpoints metadata: name: rds-service subsets: - addresses: - ip: 10.10.213.27 #RDSのIP ports: - port: 3306
注意点としてはServiceとEndpointsのnameは同じにしておく必要があります。
また、マルチAZを利用している場合は、フェイルオーバーした場合にIPを書き換えるという一手間が必要になりますので注意してください。
以下のコマンドで接続できます。
mysql -h ELB_DNS -u mysql_user -P 13306 -p
初回接続は何故か数十秒かかります。またkeep aliveを調整しておかないと接続がすぐ切れるので長めに設定しておくことをお勧めします。
まとめ
Podを使わずに接続できたのはよかったのですが、Endpointsには接続先をIPでしか登録できないところがすごく惜しい機能です。DNSが指定できるようになったらいいですね。
EKSのPod起動数の制限
最近、いくつかEKSの環境構築を進めているなかで、EKSのPodの起動数の制限がネットワークとインスタンスタイプに依存することが分かりました。すでに知っている人も多いと思いますが、備忘録として残しておきたいと思います。
Podの起動数
通常、Podの起動を考える時に一番気にするポイントとしてはノードのリソース(CPU、メモリ)ではないでしょうか?
当然ですが、ノードインスタンスのCPUやメモリに余裕がないと新たにPodを起動できませんし高負荷時にスケールアウトもできなくなります。
Manifestにもrequestやlimitなどの設定項目がありますしリソースの確保については気を使われているのではないでしょうか?
EKSでは、ノードのリソース状況に加えてネットワークについても意識する必要があります。
Podのネットワーク制限
通常のKubernetesのPod用のネットワークはノードインスタンスとは別の内部ネットワークとなりあまり気にする必要はありません。
しかし、EKSではAmazon VPC CNI plugin for KubernetesというネイティブなVPCを利用可能とするプラグインを用いることで、VPCネットワーク上と同じIPをPodに割り当てることなります。つまり、PodはVPC上のIPを利用して通信するため、Podの起動数がVPCで利用可能なIP数に依存するということになります。
EKSのネットワークの詳細については以下を参照してください。
docs.aws.amazon.com
一般的にAWSのサブネットについては、24ビット以上で切られることも多いと思います。仮に24ビットでサブネットを作成した場合、AWS予約分を除くと利用可能なIP数は251となります。
AWSで指定可能な最も小さな28ビットで作成した場合は、11IPしか利用することができません。
ノードインスタンスをいくつ起動するかにもよりますが、インスタンス自身にもIPが必要となります。
また、RDSやElasticacheなどその他のリソース用のIPも確保しておく必要があるため、実際にPodで利用可能なIPはもっと少なくなります。
さらに、ノードインスタンスのスケールやPodのスケーリングがどれくらいになるかも想定しておく必要があります。
これらを踏まえてネットワーク設計にあたってはIPに余裕をもったCIDRを検討してください。
インスタンスタイプごとの制限
IP数に余裕があって、インスタンスのリソースにも余裕があった場合、1インスタンスに際限なくPodを起動できるのでしょうか?
AWSでは、インスタンスタイプごとに割り当て可能なIP数が制限されているため、こちらも意識しておく必要があります。具体化にはインスタンスタイプごとにアタッチ可能なネットワークインターフェイス(ENI)の数に制限があり、1つのネットワークインターフェイスあたりの利用可能なIP数に制限があります。
実際にc5.large(or m5.large)で見てみると、制限は以下の通りとなります。
ENIの最大数 | 3 |
---|---|
ENIあたりの IPv4 アドレス | 10 |
この場合、利用可能IPは30となります。
特にt系のmicro、smallなどは場合は、利用可能なIP数も少ないため注意する必要があります。
各インスタンスタイプごとの制限は以下のドキュメントを参照してください。
https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/using-eni.htmldocs.aws.amazon.com
スケールした場合のIPの消費
例として以下のVPCのケースで考えてみます。
VPC CIDR | 192.168.0.0/16 |
---|
Publicサブネットを2つPrivateサブネットを2つ作成したとして、EKSのノードグループをPrivateサブネットに配置するものとします。
Subnet CIDR | 利用可能IP数 | |
Private A | 192.168.10.0/24 | 251 |
Private C | 192.168.11.0/24 | 251 |
構成
インスタンスタイプ | c5.large |
---|---|
通常時起動台数 | 10 |
ピーク時起動台数 | 20 |
上記のケースの場合、ノードグループは2つのPrivateサブネットに配置されているため502のIPが利用可能です。
c5.largeは最大で30ip利用可能ですが、1台あたり20ip程度利用していたと想定します。
通常時では200ip程度消費することとなり300程度の余裕があります。
しかし、ピーク時では倍の400ipとなり残りが100しかなくなります。
そうすると、さらに負荷が増大した場合にスケールアウト可能なインスタンスが5台までとなり、あまり余裕がありません。
さらにローリングアップデートやBlue・Greenデプロイメントを行うのであれば、余分にipが必要となるためそれらの考慮も必要となってきます。
通常のEC2であれば、24ビットのCIDRで十分なことも多かったかと思いますが、EKSではかなりのipが消費されることになるため注意が必要です。
EKSにおけるSecurity Group変更の注意点
長くなるので結論を先に書きます。
EKSのノードインスタンスにおいてセキュリティグループを変更(アタッチ or デタッチ)する際は、ノードグループを更新しましょう。要するに手動での変更は止めましょうということです。ちなみにアタッチ済のセキュリティグループのルール変更は問題ありません。
手動で変更したために、疎通に関して問題が起こったので経緯と原因について書きたいと思います。
やりたかったこと
以下のような構成でPodからRDSへ疎通させたかった。
構成
対応内容
上記のような構成でノードのセキュリティグループを手動で変更しました。
具体的な設定内容としては、自己参照設定されているセキュリティグループををノードインスタンスとRDSにアタッチしました。
そもそもノードグループはオートスケーリンググループのため、手動変更がよくないことは認識していましたが、開発環境ということや諸々の状況から、一時的な対応として手動で新しいセキュリティグループをアタッチしました。(要するに手抜きでやってしまいました。)
発生事象
上記の対応後に、PodからRDSに接続できたりできなかったりする事象が発生。
原因調査
この時点で以下の確認を行いました。
1.セキュリティグループ
ソース、ポートに設定誤りがないことを確認
全てのインスタンスにセキュリティグループが付与されていることを確認
2.ネットワークACL
何も設定されていないことを確認
3.テスト用Podから接続確認
PodをCreateした直後は高確率でRDSに対して接続可能となるが、Podをkillしてリスタートすると接続できたりできなかったりというかなり微妙な結果となりました。特定のノードで接続できないとか特定のPodに問題があるのかなど確認しましたが特にそういった問題は見受けられませんでした。
4.セキュリティグループの設定変更
RDSのSGにおいて、自己参照ではなくインバウンドのソースにSharedNodeSecurityGroup(eksctlでデフォルトで作成されるノードのSG)を指定するように変更したところ、RDSへ接続が可能となることが判明。
この時点では、明確な原因が分かるところまでは調査できませんでした。
他の環境において全く同様の設定でDB接続に問題は発生していなかったため、自己参照のSGに問題があるとは考えづらかったのですが、状況から見ると何か問題がありそうということで、一旦自己参照における設定はやめることとしました。(結果的に自己参照が問題ではありませんでした。)
余談ですが、この調査の際に以下の方法を利用してホスト側に接続しtcpdumpなどで調査を進めました。
SSH接続しなくてよいのでかなり便利です。
dev.classmethod.jp
EKSのネットワークについて調査
原因がはっきりとしなかったため、後日改めてEKSのネットワークについて調べてみました。
EKSのネットワークについては、Amazon VPC CNI plugin for Kubernetesというプラグインを使用することで、PodについてもVPCネットワーク上のアドレスと同じIPを利用することが可能となっています。
このため、ノードインスタンスではPod数分のIPを確保する必要があるため、ネットワークインターフェースを1ノードに複数アタッチする構成となっています。以下AWSの資料です。
問題が起こった環境では、インスタンスタイプはt3.mediumを利用していました。このタイプのネットワーク制限は以下となり、最大18個のIPが利用可能となります。
■t3.medium
アタッチ可能なENI数 | 3 |
---|---|
ENIあたりのIPv4数 | 6 |
ここでENIが複数アタッチされていることに改めて着目しました。
そもそもセキュリティグループはインスタンスに対して割り当てられるものではなく、ENIにセットされるものとなります。通常、あまり複数ENIを利用することも少ないこともあり、EC2のコンソールからはインスタンスにセットされているように見えますが実際にはENIにセットされていることになります。
この時、EC2のコンソールからセキュリティグループを追加した際に全てのENIに反映されていないのではないかと思い至り、検証してみました。
もし上記の通りの動作だとすると、セキュリティグループがアタッチされたENIからのみRDSへ疎通が可能となり、それ以外のENIからは通信できないという動きになると考えられます。
ENIに対しセキュリティグループがどのようにアタッチされるか?
以下のようにセキュリティグループが割り当てられているインスタンスを用意しました。
3つのセキュリティグループが割り当てられています。
このインスタンスには以下のように3つのENIがアタッチされており、セキュリティグループについても全て同様となっています。
このインスタンスに対してEC2コンソールからadd_sgというセキュリティグループをアタッチしました。
アタッチ完了後にENIを確認したところプライマリENIのみadd_sgが付与されていることが分かりました。
■プライマリENI
■セカンダリENI
検証結果から上記の想定通りの動作ということが確認できました。
接続不具合となった原因
改めて状態を図にしてみると以下のようなイメージになります。
EC2コンソールから手動でセキュリティグループを変更するとENI_1にのみadd_sgがアタッチされることとなります。
そのため、ENI_1に紐付くIPが割り当てられたPodからのみRDSへの疎通が可能となり、そうでないENIに紐付くIPが割り当てられたPodからは疎通ができません。ということで、今回発生した事象について原因が判明しました。
ちなみに何度か検証した際に気付きましたが、EC2コンソールからのセキュリティグループ変更時には、以下のようにインターフェースIDが表示されていました・・・コンソールはしっかり確認する必要がありますね。
全てのENIに手動でセキュリティグループをアタッチすれば問題ないか?
完全に一時しのぎとしてなのですが、仮に全てのENIのセキュリティグループを手動で変更すれば問題ないかという観点でもう少し考えてみました。オートスケーリンググループのテンプレートの問題は一旦おいておきます。
ノードインスタンスは、起動直後にPodの数が少ない場合、ENIは1つしかアタッチされていません。
そこからPodの数が増えて1つのENIで割り当て可能なIP数を超えた場合は、新たにENIがアタッチされることになります。その際、増えたENIはどのようなセキュリティグループがセットされているかを確認してみました。
以下のようなENIが一つのみのインスタンスを用意し、add_sgというセキュリティグループをセットします。
インスタンにENIが一つアタッチされている状態です。
この状態でPodの数を増やし、各ENIの状態を確認しました。
プライマリENIにはadd_sgが付与されていることが分かります。
追加された2つのENIを確認すると、add_sgは付与されていませんでした。
おそらくテンプレートから引っ張ってきてると思うので当然の動作かもしれません。
ということから、仮に全てのENIのセキュリティグループを手動で変更したとしてもENIが勝手に増える可能性があるため、突然、通信できなくなる問題が起こりそうですね。
Amazon CloudWatch SyntheticsでURL監視を設定
Amazon CloudWatch Syntheticsがプレビューされていましたので早速試してみました。
CloudWatch Syntheticsとは、サービスのエンドポイントやAPIのエンドポイントなどをモニタリングするサービスになります。DataDogでも同様のサービスがありますね。
現時点では、US East (N. Virginia), US East (Ohio), and EU (Ireland)のいずれかのリージョンでし利用できませんので、今回は、バージニア北部を利用しました。
Canaryを作成
以下の画面からCanaryを作成を選択します。
デフォルトで以下のように選択されていますのでそのまま次進みます。
Canary名と監視対象のURLを入力します。
スクリプトエディタの内容は自動で生成されるます。
以降はデフォルトのままとし、Canaryを作成します。
モニタリングの実行
作成直後はステータスがグレイアウトされていますが、しばらくすると以下のように実行結果が表示されます。
詳細を確認するとスクリーンショットやHARファイルなどを見ることができます。
実際の画面を取得してくれるのはすごくいいですね。
ログを確認してみると、Lambdaのログに似ていたので、Lambdaのコンソールを確認したところsynthetics用の関数が自動で作成されていました。消したりしないように注意が必要ですね。
アラート通知
また、Threshholdsを有効にし、Canaryでしきい値を有効にすると、アラームが作成されますので、これをもとにchatbotやLambdaと連携することでアラート通知が可能になります。
カスタマイズ
スクリプトがカスタマイズできるので、Basic認証やログインしての監視なども可能ですね。
ちなみにログイン情報などの秘匿情報が必要な場合はSecrets Managerの利用が推奨されています。
料金
1canaryあたり月額$0.0012となっています。
まとめ
特にドキュメント読むことなく簡単に設定することができました。また、スクリプトをカスタマイズできるなど拡張性も高いと思いますので、ぜひ使っていきたいですね。
CloudFormationでDataDogのIntegration設定
CloudFormationで以下のアップデートがありサードパーティ製品についてもCoudFormationで設定できるようになりました。
DataDogのIntegration設定などをCloudFormationで設定できるようなので、早速試してみました。
aws.amazon.com
CloudFormationへDatadogリソースの登録
aws cliで以下のコマンドを実行しリソースの登録を行います。
実行前にawscliのアップデートが必要になります。(pip install -U awscli)
aws cloudformation register-type \ --region ap-northeast-1 \ --type RESOURCE \ --type-name "Datadog::Integrations::AWS" \ --schema-handler-package s3://datadog-cloudformation-resources/datadog-integrations-aws/datadog-integrations-aws-1.0.0.zip
※type-name、schema-handler-packageの設定値についてはこちらのgithubを参照してください。
以下の通りRegistrationTokenが表示されれば登録は完了です。
{ "RegistrationToken": "122c2c39-12c8-4e93-a711-f6eb1234eae0" }
登録されているかは以下で確認できます。
$aws cloudformation list-types { "TypeSummaries": [ { "Description": "Datadog AWS Integrations", "LastUpdated": "2019-11-20T03:13:25.218Z", "TypeName": "Datadog::Integrations::AWS", "TypeArn": "arn:aws:cloudformation:ap-northeast-1:123412341234:type/resource/Datadog-Integrations-AWS", "DefaultVersionId": "00000001", "Type": "RESOURCE" } ] }
リージョンごとにリソース登録が必要となるようですが、このあたりの設定は今後もっと簡略化されていくと嬉しいですね。
Integration設定
事前に設定するDataDogのAPIキーとApplicationキーを取得しておいてください。
以下のyamlをファイルに保存しCloudFormationから実行し、パラメータにて上記のキーを設定して実行してください。
--- AWSTemplateFormatVersion: '2010-09-09' Description: 'IAM Role and IAM Policy for Datadog AWS Integration' Parameters: DatadogAPIKey: Description: "Datadog's API Key" Type: String DatadogAPPKey: Description: "Datadog's APP Key" Type: String Resources: DatadogAWSIntegrationResource: Type: 'Datadog::Integrations::AWS' Properties: AccountID: !Ref AWS::AccountId RoleName: DatadogAWSIntegrationRoleTest HostTags: ["env:staging"] AccountSpecificNamespaceRules: {"ec2": true, "api_gateway": false} DatadogCredentials: ApiKey: !Ref DatadogAPIKey ApplicationKey: !Ref DatadogAPPKey
DataDogの設定画面を確認すると以下の通り登録されていれば完了です。
AWS側のRoleも合わせて作成してくれないか期待しましたが、残念ながらそこまではしてくれないようでした。
また、ExternalIDの取得方法が現状ではなさそうなので今後に期待したいと思いますが、機能が拡充されていくと思いますので、まとめてCloudFormationで管理することが可能になりますね。