はじめに
イタンジSREチームの田渕です!
イタンジではクラウド管理のIaCツールとしてterraformを使っています。
terraformとクラウドの状態の差分を定期的に監視する仕組みを作ったところ、うまくワークし、かなり運用負荷が下がったため、皆様にも紹介します!
導入の背景
terraformを理想的に運用できていれば、常にtfstateの状態とクラウドの状態が一致しているはずですが、現実はいくつかの理由で差分ができてしまうことがあります。
代表的なものは以下の3つでしょうか。
- applyする際に不適切な書き方をした場合に、applyは想定通りに成功するがplanをした際に差分が出てしまう。
- awsやterraform provider等の仕様変更により、今までは差分がなかった部分で差分が出てしまう。
- 稀に何らかの事情で手作業を行ってしまい、terraformへの反映も漏れてしまい差分が出てしまう。
確実に差分が出ないようにすることは難しいです。
しかし、差分が残ってしまうと、以下のような弊害があると考えられます。
- すでに差分が出てしまっているtfファイルを変更した際に、想定していない部分の差分に気づかず、誤った変更を加えてしまう。
- インシデントが発生するか、防げたとしてもオートメーション恐怖症の原因になり得る。
- 差分が出てしまっているtfファイルを参考に構成を作り、クラウドの構成に差ができてしまう。
- 構成ドリフトの発生に繋がる。
- 差分に気づいた場合でも、いつまでは差分がなかったかを確かめる手段がないため、原因特定に時間がかかってしまう。
イタンジSREチームでもこのような悩みを抱えていました。
そこで、terraformとクラウドの状態の差分を定期的に監視する仕組みを作ってみました。
構成
terraform planを-detailed-exitcode
のオプション付きで実行し、差分があった場合にはSlackに通知するRubyのスクリプトを作成しました。
そのスクリプトを、CircleCIのワークフローの定期実行という機能を使い定期的に実行しました。
CircleCIを使った理由は、そもそもterraformのCI/CD環境をCircleCI上で作っており、実装の都合が良かっただけなので、定期実行できるサーバであればどんなものでも問題ありません。
実際のスクリプト
実際に動いているものとは少し違いますが、重要な部分だけ取り出します。
# frozen_string_literal: true require 'open3' require 'shellwords' require 'uri' require 'net/http' require 'json' def check(target) command = "cd #{Shellwords.escape(target)} && terraform init && " \ 'terraform plan --detailed-exitcode' Shell.execute(command) rescue StandardError SlackClient.post("#{target}に差分があるよ!!!!!!!!") end class Shell def self.execute(command) puts command o, e, s = Open3.capture3(command) raise e if s.exitstatus.positive? puts o, e end end class SlackClient def self.post(message) uri = URI.parse(ENV['SLACK_WEBHOOK_URL']) req = Net::HTTP::Post.new(uri) req['Content-Type'] = 'application/json' req.body = { text: message }.to_json Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http| http.request(req) end end end all_tf_directories = Dir.glob('**/*.tf').map { |f| File.dirname(f) }.uniq all_tf_directories.each { |target| check(target) }
やっていることは簡単で、tfファイルが存在する全てのディレクトリで、terraform plan --detailed-exitcode
を実行し、exit codeが1以上だったらslackに通知しています。
slackの通知はディレクトリのみを通知しています。
CircleCIの設定ファイル
こちらも実際に動いているものとは少し違いますが、重要な部分だけ取り出します。
jobs: terraform_check: steps: - checkout - setup_env - install_terraform - run: name: terraform check command: ruby lib/terraform_check.rb workflows: version: 2 weekly: triggers: - schedule: cron: "0 22 * * 0" filters: branches: only: - master jobs: - terraform_check: context: AWS
先ほど作成したスクリプトを、定期的に実行しているだけです。
slackに通知が来た場合は、気づいた人が差分が出ないように修正しています。
週次で実行しており、最大でも数件程度しか差分が出てこないので、修正も数分から数十分程度で終わります。
差分があった際も、1週間前までは差分がなかったことが保証されるので、原因特定が非常にスムーズです。
おわりに
terraformは便利ですが、どうしても差分が出てしまうとお悩みの方は多いのではないでしょうか? それほど実装は大変ではなく、効果はかなり大きいと思いますので、是非試して見てください!
今後も、SRE業務中にいい感じのハックができたら、皆さまに紹介していきたいと思います。