はじめまして。9月にDIGGLEに入社したSREのTachibanaです。これまではインフラエンジニアやサポートエンジニアをやってきており、スタートアップのSREとして働くのはDIGGLEが初めてになります。入社してまだ3か月程度ですが、今年やってきたことを紹介したいと思います。スタートアップのSREって何をするんだという疑問に答える記事になっていれば幸いです。
- システム構成図の整理
- タスクの整理
- セキュリティの強化
- Datadogに情報を集約
- ECSのtask definitionのJsonnet化
- DBの監視の強化とDB自体のスペックアップ
- ポストモーテムの実施
- IaCの更新と整備
- Renovateの導入
- 振り返りと今後に向けて
- We’re hiring!
システム構成図の整理
どのようなシステム構成でDIGGLEが動いているかの把握に努めました。実際にアプリを触ったり、AWSやTerraformを眺めながらどのようなネットワーク構成になっているのか、どんなAWSサービスを使っているのかを確認していき、詳細の理解を深めていきました。 また合わせてシステム構成図の更新を行い、どのようなことをしたらよさそうかの把握していきました。
タスクの整理
入社して最初にやったことはタスクの整理です。Jiraのチケットの山の中からインフラやセキュリティとラベルがついたチケットを確認しつつ、何を課題としてきてどのような改善がされてきたのかを確認していきました。優先度付けとしては、今必要かという観点で実施していきました。セキュリティ的な観点からすぐにやらなければならないか、顧客からすぐに求められている対応なのか、やったほうがいいが後回しでも大きな問題にはならないものかどうかという観点で分類と整理をしました。
セキュリティの強化
入社前からすでにAWS IAM アイデンティティセンターは導入されており、IAMユーザー自体はほとんど存在しない状態になっていました。ただ、GitHub Actionsのyamlファイル、ECSのtask definition、Dockerfileにはいまだにアクセスキーを利用した処理が残っておりこれらをRoleに切り変えていくのが最初のタスクとなりました。
GitHub Actionsからのアクセスキーの排除
私が対応する前にAWSとGitHub ActionsのOpenID Connectの設定は完了していました。そのため残りの必要な作業は適切な権限をもったRoleの作成だけになります。
状況として一つのIAMユーザーのアクションキーが複数のActionで使われていたため、IAMユーザーの権限をそのままRoleのPolicyとすることはできませんでした。地道にそのActionが必要とする権限を明確にした上でRoleを作成し、アクセスキーを置き換えました。また、一つのアクセスキー(ロールでも同様かと思います)を使い回す運用はいざという時の影響範囲の特定に時間がかかることが想定されるため、セッションネームにbranch名を入れるなどしてどこで使われたものかが判別しやすいようにしていきました。
ECSのtask definitionのアクセスキーをRoleに置き換え
過去にも同様の試みがあったことは過去チケットから確認できており、その際はS3へのアクセスが必要で、そのための認証としてアクセスキーをセットしておかないといけない、と結論づけていました。
Dockerfile内の処理にはS3にアクセスが必要な処理はなく、当時の担当者が何かしらの手順についての誤解をしていたのだろうと思い、実際にはそれほど工数を必要とせずにすぐに修正ができるだろうと考えていました。
アクセスキーなしでDockerビルドをする検証を始めると、ビルド処理内のbundle exec rails assets:precompile
コマンドにてAWSの権限が不足しているということでエラーになることが確認できました。precompile処理自体にはAWSのリソースにアクセスすることはないはずなのに、AWS関連のエラーが発生するのは納得がいきませんでしたが、試行錯誤しているうちにRailsの初期化プロセスに関係しているということが判明しました。
Railsの初期化処理時に読み込まれるファイルの一部としてconfig/application.rb
があるのですが、これはユーザーが自由に変更できるものであり、DIGGLEではAWSのパラメータストアから関連するパラメータを読み込む処理をここに記述していました。
つまりprecompileが直接的にAWSにアクセスしている訳ではなく、railsの起動時に呼び出されるapplication.rb
が原因で、bundle exec rails assets:precompile
のコマンドでエラーになっていた、ということでした。
S3へのアクセスをするためにアクセスキーが必要という過去だされた結論はどうやらprecompile
でエラーになることから、フロントエンドのビルド時の処理にS3にアクセスしているだろうという誤解から生まれた結論だったのではないかと推測をしています。
原因箇所がわかってしまえばあとは簡単です。ここでは一旦はビルドを通すことを目的に適切な権限が渡るようにし、ビルド時のエラーを回避しました。 またビルドエラーが回避できたのでECS task definitionからアクセスキーの代わりにecs task roleを設定することでtask definitionからアクセスキーを排除することができました。
Dockerfileからのアクセスキーの排除
Dockerのビルド時にAWSのアクセスキーを含む秘密情報がARGで渡されていました。ARGの情報はhistoryに残る仕様のため、Docker公式からもパスワードなどを渡す用途では使わないようにという注意書きがあります。
ビルド時にSecretを渡したい場合はARGではなく、Secret Mountsを使う必要があります。
例えばビルド時には以下のようにSecretを渡します。
docker build --secret id=aws-key-id,env=AWS_ACCESS_KEY_ID .
これは環境変数のAWS_ACCESS_KEY_ID
をsecret idがaws-key-idとしてmountするといった意味になります。 これをDockerfileで使うには以下のように書く必要があります。
RUN --mount=type=secret,id=aws-key-id,env=AWS_ACCESS_KEY_ID \
上記はsecret idがaws-key-idのvalueを環境変数のAWS_ACCESS_KEY_IDとして使うといった意味になります。Mount Secretについて知りたい方は以下のドキュメントを参照してください。
Datadogに情報を集約
元々主要なアプリのAPMやログはDatadogで取得できるようにしていました。調査がさらに楽になるようにDatadogでシステムの状況が理解できることを目指し、以下の情報についても収集することにしました。
- ECSのtaskで稼働しているProcess情報
- Ruby Runtimeのメトリクス
- RubyのProfile
- Database Monitoringの有効化
データを活用してもらうためにドキュメントにDatadogのどこに何の情報があるかやDatadogでどのような検索が可能かをNotionにまとめたりSlackで通知したりしています。 おかげさまでDEVチーム内でのDatadogの活用が進んでおり、ありがたい限りです。
Datadogが最初から用意している画面にほしい情報がまとめられているのもあり、これまで発生したことのない障害やエラーが発生した場合にでもそれらの画面や機能を使いこなして原因究明がスピーディに行えるような体制にしたいと考えています。
ECSのtask definitionのJsonnet化
ECSのtask definitionファイルですが各サービス毎に共通のJSONを利用し、deploy時にJSON内の特定の文字列を環境毎の文字列に置換する形で対応していました。
"environment": [ { "name": "SERVICE_ENV", "value": "%%ENV%%" #ここを置換する処理をdeployの前処理として実施していた },
文字列の変換でもこれまでは大きな問題はなかったのですが、今後のメンテナンスを考えると項目の共通化がしやすい設定記述系言語の方がよりよいと考え検討を始めました。設定記述系の言語としてはCUEやJsonnetがよく使われるかと思いますが、今回はecsのデプロイツールであるecspressoがサポートしているJsonnetを使うことにしました。
Jsonnet - Jsonnet Configuration Language
Jsonnetを使うことで共通の設定をまとめることができ、変更時の対応工数を減らすことができます。 例えばnginxの基本的な設定を以下のようにまとめておきます。
{ essential: true, logConfiguration: { logDriver: 'awslogs', options: { 'awslogs-group': 'nginx-test', 'awslogs-region': 'ap-northeast-1', }, }, name: 'nginx', portMappings: [ { appProtocol: '', containerPort: 80, hostPort: 80, protocol: 'tcp', }, ], }
先ほどの設定を以下のように記述することができます。
local nginxTaskDef = import 'nginx.libsonnet'; { containerDefinitions: [ nginxTaskDef { image: imageConfig.nginxImage, }
DBの監視の強化とDB自体のスペックアップ
入社して間もなくくらいの時にアプリのリリース起因でDB負荷が高まりやすい状況になり、数多くのお客様にご迷惑をおかけすることがありました。この障害を反省点として、DBのスペックを一段階あげて暫定的な対処を行いました。また合わせて監視不十分だったことからDatadogのDatabase Monitoringの導入や新しいアラームを導入するなどして、同様の事象が発生した際に即座に気づけるようにしています。アプリ自体の改修によりDBへの負荷が下がったのですが、それに加えて今後同様の事象が発生した際にRead Replicaを利用した負荷分散がおきるようにDEVチームでアプリの改修を積極的に進めていただいている状況です。ありがたい。
ポストモーテムの実施
DB関連の障害が起きてからポストモーテムを実施しました。実施した結果として障害時の対応に直接関わっていなかったメンバーも障害原因に興味を持ってくれたり、そもそもの障害時の対応方法についての見直しについて言及があり、意義のあるものになったなと思っています。
また今後のインシデントに備えて以下のようなテンプレートをNotionで作成しました。
# Postmortem yyyy/MM/dd [障害概要] 種別: ポストモーテム 最終更新日時: 2024年12月16日 15:43 作成者: 橘樹男 | インシデント | 詳細 | | --- | --- | | Status | Active/Stable/Resolved | | 影響範囲 | 内部コンポーネントのみ/顧客影響あり | | 開始時刻 | yyyy/MM/dd HH:mm JST | | インシデントコマンダー | name | | Slack Channel | | # 発生事象 --- 顧客影響のありなしや影響範囲についてわかる範囲で書いてください。 # Timeline --- いつ何がおきたのかを時系列順に書く CloudwatchやDataDogのリンクやスクリーンショットも合わせて載せること # 原因分析 --- ## 根本原因 直接原因を起こすことになったそもそもの原因 ## 直接原因 事象を起こした直接的な原因 # 対応策 ## 短期の対策 - [ ] Action1 - [ ] Action2 ## 中長期の対策 - [ ] Action1 - [ ] Action2 # 障害からの学び 障害からの学びを書いてください。 どんな些細なことでも結構です
IaCの更新と整備
Terraformのバージョンが古かったり、すでに使っていないAnsibleがGitHubリポジトリに存在するといった状況でした。まずはデッドコードの削除を行い、次にTerraformとProviderの更新を行いました。加えてTerraform Cloudの権限の整備を行いました。
Renovateの導入
アプリ側の更新としてはDepfuを利用しているのですが、DepfuだとGitHub Actionsに対応しておらず、いくつかのActionが古い状態で放置されていました。Dependabotでもよかったのですが、設定の豊富さからRenovateを導入しました。Renovateを使ってActionsだけではなく、TerraformやDockerfileの更新についても自動化しています。
振り返りと今後に向けて
振り返ると最初は最低限やらなければやらないといけないことから取り組み始め、徐々に開発の効率をあげるために必要なことにシフトしていったように思えます。 今後は以下の領域に取り組んで行く予定です。
- SLO/SLIの活用の推進
- 本番アクセスのセキュア化
- リソースの最適化
- Toilの削減
- コスト最適化
- などなど
We’re hiring!
DIGGLEではともにプロダクトを開発・運用をしてくれるエンジニアを大募集中です。 少しでも興味があれば、ぜひ下記採用サイトからエントリーください。 カジュアル面談も実施しているので、気軽なお気持ちでご応募いただければと思います!