この記事について
DMMグループ Advent Calendar 2020
の 17日目の投稿記事となります
https://qiita.com/advent-calendar/2020/dmm
また、発表に向けた記事のため、スライドモードでの回覧を推奨しています。
自己紹介
名前 : 松田 弘樹
入社 : 2015年5月入社の6年目
担当 : バックエンド、インフラ担当
趣味 : 乗馬🐎 全乗協の5級ライセンス取得しました。
想定読者
- IaC (Terraformなど)の概要を理解している人
- CI/CDによる、自動デプロイの概要を理解している人
- AWSのEC2インスタンスを構築したことがある人
発端
通常の運用で
ある日、Terraformにて構築済みのEC2インスタンスから、作業のために mysql
コマンドで接続をしてみると
mysql: command not found
というエラーコードが返却された
発端
!?
数日前まで、使えていたコマンドが使えなくなっていること示すをエラーが返却された。
histroyを確認してみると、実行したコマンド履歴が一切無い。つまり、EC2インスタンスが初期化されている。
自分「(構築後にインフラので変更していないはずなのに)何もしていないのに壊れた」
発端
というわけで
本記事では私が遭遇した なにもしてないのにEC2インスタンスが壊れた
について、ストーリ仕立てでお話をしていきます。
最後にまとめがあるので、忙しい人はそちらへどうぞ
状況整理
まずは事象と影響範囲の確認から
事象
Terraformのtfstateで管理さている、EC2インスタンスが初期化された。
影響範囲
他のTerraform管理のインフラ要素に影響はなし。
環境について
概要をざっくり説明
インフラ構成管理
基本と、Terraformを利用し、インフラ構成をコードで管理している
環境について
Terraformコードを管理するリポジトリは2種類
- inf: VPC、サブネット、ネットワーク定義など管理。デプロイは手動適用。
- app: アプリ特有の定義管理。WEBサーバ、RDB定義など。EC2もここで管理。デプロイはCI/CDに組み込まれ自動適用。
環境について
CI/CDフロー
利用しているツール類は次の通り。
- GithubEnterprise (ソースコードのリポジトリ管理)
- CircleCI (Docker Build、コンテナ上での単体テスト、Terraformのapply、Docker ImageのPUSH)
- AWS ECR (Docker Image用リポジトリ)
- AWS ECS (コンテナの実行環境)
上から順番に連携するフローとなっている
調査
ここからは調査です。
可能性のありそうな仮設をたてながら、検証を繰り返していきました。
調査
仮設1 手動で、メンバーか削除した可能性
一番わかり易いのは、誰かが手動でEC2を作り直したことです。
しかし、メンバーに確認をとってみましたが、該当者がいないため、この説否定されました
調査
仮設2 tfstateの状態が破損し、作り直した可能性
何かの理由でstateファイルが破損したことでEC2インスタンスがterraform管理から外れ、
新しくTerraform管理とするため作り直された可能性を考えました。
tfstateファイルの状態確認のため、手動で Terraform plan
を実行しました。
結果、エラーなく正常に動作し、tfstateファイルは正しく機能しているため、この説は否定されました
調査
仮設3 TerraformのEC2の記述が修正されて、EC2が再構築した可能性
EC2インスタンスを管理しているtfファイルの直近の修正履歴を確認しましたが、直近ではtfファイルは修正されていませんでした。
症状発生時には編集されていないため、この説は否定されました
調査
他にどんな原因が考えられるかと仮設をかんがえていたなかで、調査をお願いしていたメンバーから新たな情報をいただく
新く判明した情報
CloudTrail上のイベントにおいて、Terraform実行ユーザが、EC2インスタンス対して Terminateinstance
を複数回、実行していることが判明。
調査
上記の情報より、原因がTerraformが怪しいとの絞り込みができたので新たな仮設が浮かぶ。
仮設4 Terraformにおいて、意図せずEC2が再構築した可能性
EC2の何かしらのリソースが動的取得しており、その部分が更新されるたびにEC2インスタンスが再構築されるのではないか。
調査
というわけで、EC2関連の記載について細かく見ていきました。
当時のソース
AMI情報をaws_ami
の filter
で絞り込んだあと、aws_instance
に指定する流れとなっています。
# AMI情報を取得
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "architecture"
values = ["x86_64"]
}
filter {
name = "root-device-type"
values = ["ebs"]
}
filter {
name = "name"
values = ["amzn2-ami-hvm-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
filter {
name = "block-device-mapping.volume-type"
values = ["gp2"]
}
}
# AMIのIDを指定して構築
resource "aws_instance" "rds_access_ec2" {
ami = data.aws_ami.amazon_linux.id
instance_type = "t2.micro"
}
調査
aws_ami
のパラメータをひとつづつ確認していると、怪しい記述を発見しました。
怪しい部分
most_recent - (Optional) If more than one result is returned, use the most recent AMI.
most_recent-(オプション)複数の結果が返される場合は、最新のAMIを使用します。
これだ!!!!!!!!!!!!!!!
原因が特定できました。
原因
CDCDの自動デプロイフローにおいて、EC2のAMIを動的に取得しており、デプロイのたびに最新版のAMIを取得して、更新がある場合はEC2再構築を行うため。
発生時のフロー
- GithubEnterpirseに、コードをpushする。
- 連携しているCircleCIで、push検知してCI/CDが起動する。
- デプロイ処理のなかで、毎回最新のAMIイメージを取得する。
- AMIイメージが更新されている場合、古いEC2インスタンスを削除し、新しいAMIイメージでEC2インスタンスを作成する。
このため、直接のEC2の定義部分のtfファイルの修正なくとも、EC2インスタンスが新しいものに差し替わってしまったのです。
対応
原因がわかったので対応をしていきましょう。
案1
EC2インスタンスの再作成を許容する。
EC2インスタンスの更新のたびに、コマンドが消失しないように、aws_instance
の user_data
に必要なインストールコマンドを定義しておく。
resource "aws_instance" "rds_access_ec2" {
user_data = <<EOF
#!/bin/bash
sudo yum install -y mysql-community-client
EOF
}
欠点は、ローカルにおいたファイルなどが消えてしまうことです。
対応
案2
AMIの値を固定値に変更する。
更新したい場合は、AMIの値を修正する運用とする。
resource "aws_instance" "rds_access_ec2" {
# 固定値に修正
ami = "ami-00f045aed21a55240"
}
欠点は、手動運用の手間が増えることとなります。
対応
どちらの案も欠点があるので、要件などと照らし合わせて判断すればいいかなと思います。
まとめ
次の環境の場合は、意図せずにリソースが更新されることがあるので注意しましょう。
- インフラの構成を、Terraformなどでコード管理している
- デプロイが、手動ではなく自動でデプロイする
- リソースを動的に取得している部分がある
Special Thanks
調査に協力してくれた @moroishisan
宣伝
弊社ではエンジニアを積極募集中です!
https://dmm-corp.com/recruit/
個人あてに、カジュアルにお話が聞きたいかたもどうぞ!
引用
Data Source: aws_ami
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami