継続は力なり

タイトル通り定期的な更新を心掛けるブログです。

RI と SP を使って AWS の利用料を落とす

タダです.

業務でシステムの基盤として AWS を使っていますが,運用を続けると付き纏うのがランニングコストです.サービスのスケールに合わせて様々なリソースが増えてきて,ランニングコストも膨らんできました.サービスの定期的な停止やスポットインスタンス使ったり,サーバースペック,ストレージタイプが適切じゃないため見直し等行ってきたものの,コストとして EC2,RDS,ElastiCache,ECS の占める割合が大きかったので,リザーブインスタンスと Saving Plans を8月に購入したのですが,買い方や購入後の効果をまとめていきます.

リザーブインスタンス と Saving Plans の概要

リザーブインスタンス

リザーブインスタンスは1年または3年の間で指定したサーバースペックのものを使うことで利用料金を割引してもらうためのオプションです.

Saving Plans

Saving Plans は1年または3年の期間で特定の使用量(1時間あたりのUSD)のコミットを契約する代わりに,オンデマンド料金と比較して低価格な料金になるプランです.Saving Plans には3つの契約タイプ(Compute Savings Plans/EC2 Instance Savings Plans/SageMaker Savings Plans )があります.それぞれ次の特徴があります.

・Compute Savings Plans

インスタンスファミリー、サイズ、アベイラビリティーゾーン、リージョン、OS、またはテナンシーに関わらず EC2 インスタンスの使用に自動で適用されます。また、Fargate、 Lambda の使用にも適用されます

例えば、C4 インスタンスから M5 インスタンスへの変更、欧州 (アイルランド) から欧州 (ロンドン) へのワークロードのシフト、または EC2 から Fargate へのワークロードの移動などはいつでも行えます。この場合も、Savings Plans の料金は、引き続き自動的に適用されます。

・EC2 Instance Savings Plans

アベイラビリティーゾーン、サイズ、OS、またはテナンシーに関わらず、そのリージョン内で選択されたインスタンスファミリーのコストを自動的に削減します

・SageMaker Savings Plans

SageMaker Studio Notebook、SageMaker On-Demand Notebook、SageMaker Processing、SageMaker Data Wrangler、SageMaker Training、SageMaker Real-Time Inference、SageMaker Batch Transform などの対象となる SageMaker ML インスタンスの使用量に自動的に適用されます

関連情報

aws.amazon.com

それぞれの購入フロー

次にリザーブインスタンス と Saving Plans の購入フローをまとめていきます.

リザーブインスタンス

リザーブインスタンスとして RDS,ElastiCache を買いました.フロー的には各サービスのページにいき,リザーブドインスタンス(リザーブドノード)>リザーブド DB インスタンス(リザーブドノードの購入)に遷移し,購入するインスタンスタイプやその他の情報に誤りがないかを確認します.内容が問題なければ購入します.

RDS の画面イメージ f:id:sadayoshi_tada:20210912165829p:plain

ElastiCache の画面イメージ f:id:sadayoshi_tada:20210912165952p:plain

購入が成功するとステータスがアクティブになります.

RDS の画面イメージ f:id:sadayoshi_tada:20210912170646p:plain

ElastiCache の画面イメージ f:id:sadayoshi_tada:20210912170708p:plain

Saving Plans

Saving Plans の購入はSaving Plans > Savings Plans の購入に遷移することで行えます.購入にあたっては,コミットメントする期間と契約する稼働コストを気にしておきましょう.そのコストと前払いコストが認識が合っていれば,カートに追加します.この時点では購入はされません.

f:id:sadayoshi_tada:20210912164358p:plain f:id:sadayoshi_tada:20210912164441p:plain

確認画面に遷移し,問題なければ注文を送信を押すと決済されます.自分が見た限り決済完了には5~10分の間に完了し,ステータスがアクティブになっていました. f:id:sadayoshi_tada:20210912164629p:plain f:id:sadayoshi_tada:20210912164734p:plain f:id:sadayoshi_tada:20210912170151p:plain

購入にかかったコストはいつ請求されるか?

リザーブインスタンスと Saving Plans を買ったはいいけどその請求はどのタイミングで発生するのかが気になります.AWS の専用ページにまとめられていますが,即時の支払いになります.

月額料金やサブスクリプション料金(リザーブインスタンスの前払い金など)はいつ頃請求されますか?(クレジットカード払いの場合)

サブスクリプション料金(リザーブインスタンスの前払い金など一度限りのお支払)購入された時点で即時に請求書が発行されます。請求書が発行された時点でクレジットカードに決済を行わせていただきます。

関連情報

aws.amazon.com

適用後の効果

適用前のコストとの比較

ざっくりとですが,適用前のコストと比べると4~5割ほどのコスト削減ができました.コスト削減の方法として正攻法だと思っていたのですが,効果を初めてみてびっくりしました.長く使うのが見えている場合は,リザーブインスタンスと Saving Plans の購入を検討すると良いと言われていたのを実感できました.

まとめ

コスト削減としてリザーブインスタンスと Saving Plans の概要,購入フロー,効果をまとめました.サービスの伸長と同時にシステムもスケールしていく中でどんどんコストがデカくなっていっていたので,今回対応しましたが購入後の効果に出ると嬉しいものです.

関連記事

sadayoshi-tada.hatenablog.com

GCP の課金状況をモニタリングできるよう設定する

タダです.

GCP を利用していると無料の利用枠で使えているうちはいいのですが,課金され始めるとびっくりするみたいな経験があります.そのため,BigQuery に課金データをエクスポートして状況をウォッチできるようにしたり,課金が嵩むような使い方をしていないかをチェックするよう監視を仕込んでみたのでこの記事にやったことをまとめます.

cloud.google.com

今回やること

大きく二つのことをやっていきます.

  1. BigQuery に課金データをエクスポート
  2. BigQuery の課金要素にアラートを設定

BigQuery に課金データをエクスポート

課金データを BigQuery へのエクスポートを下記ドキュメントを参考に設定していきます.

cloud.google.com

GCP のメニューからお支払い > 課金データのエクスポートの画面に移動します.初期の画面は画像のような状態です.料金をエクスポート するために 標準の使用料金から設定を編集でこの設定を有効化し,どのプロジェクトでどのデータセットに対して課金データをエクスポートするかを設定します. f:id:sadayoshi_tada:20210906190210p:plain

設定完了後の画面が画像のような状態で,myproject_costというデータセットを課金データエクスポートの設定で作り,そのデータセットに課金データのテーブルを作るように設定しました.

f:id:sadayoshi_tada:20210906191554p:plain

設定後1日経ったら,課金データがgcp_billing_export_v1_[乱数]のテーブルに出てきます.自分が見てみた感じエクスポートの設定を入れる以前の課金情報は出てこないため,その点は注意です.課金データは Redash などを使えば可視化できるので自分もこちらの記事などを参考にさせていただきました.画像で出しているのはプロジェクト全体とサービスごとの課金状況を出力した例です.

BigQuery のテーブル f:id:sadayoshi_tada:20210906191905p:plain

Redash に出してみた例 f:id:sadayoshi_tada:20210906192310p:plain

BigQuery の課金要素にアラートを設定

次に,BigQuery の課金要素になるところにアラートを設定していきます.GCP の課金でよくみるのが BigQuery のコストが嵩むのを見かけます.業務でも BigQuery を分析で使っていたりするので,課金要素にアラートを設定していければと思いました.BigQuery ではストレージのコストと,クエリ実行時にかかる費用があります.また,クエリ実行時にかかる費用として,オンデマンド料金定額料金の二つのモデルがあるのが特徴です.前者はクエリで処理したデータのバイト数に応じて課金され,後者がクエリの処理につかった 仮想 CPU のスロットを購入する時にかかるものと理解しました.そのため,ストレージの費用とクエリで処理したデータ量を気にしてくことにし,ストレージの費用は課金データのエクスポートで状況をウォッチできるので,重い処理をしてるクエリが投げられた時にアラートが出るよう設定していくことにしました.

オンデマンド料金. この料金モデルでは、各クエリによって処理されたバイト数に基づいて課金されます。処理されるクエリデータは毎月 1 TB まで無料です。

定額料金. この料金モデルでは、仮想 CPU であるスロットを購入します。スロットを購入すると、クエリの実行に使用できる専用の処理容量を購入したことになります。スロットは次のコミットメント プランで使用できます。

cloud.google.com

こちらもドキュメントを参考に進めていきます.GCPMonitoring > アラート のページに移動し CREATE POLICY をクリックしていきます.ADD CONDITION でアラートの条件を設定するんですが,次のように設定しました.東京リージョンで BigQuery を使うと $6.00 per TB がかかるので,100GB以上のクエリを何回も投げつけてないかをチェックするようにしました.

項目 設定値
Find resource type and metric Resource type: Global
Metric: Scanned bytes billed
Group By priority
Aggregator sum
Period 1 day
Condition triggers if Any time series violates
Condition is above
Threshold 100000000000
For most recent value

100GBを超えるようなクエリが投げられたらアラートが画像のように Slack に通知されるようになりました.アラートによって課金の上昇を感知することができてよかったです.

f:id:sadayoshi_tada:20210906194541p:plain

まとめ

GCP の利用料金を BigQuery にエクスポートして,Redash で可視化したり,BigQuery のアラートを出す設定をしてみました.これで課金状況だったり,課金になりそうな要素を見ていけるようになりました.業務でも課金の増加に感知できてなかったのが課金が増えた時にも気付けるようになったので,今後も運用していければと思います.

k6 でシナリオテストをやってみた

タダです.

API のリプレイスをしている中で負荷試験やシナリオ試験ができていなくて,そういったテストをできるようなツールとして k6 を同僚から教えてもらって試してみました.この記事で k6 を触ってみたので試したことなどまとめていきます.

k6 とは

k6JavaScript でかけるオープンソースの負荷テストツールです.特徴として次のようなものがあります.

  • CLI のテストツールであること
  • 後述しますが,負荷テストを行って確認項目をチェック表示したり,パーセンタイルで出せたりいろんなオプションがある
  • テストの結果を Grafana で可視化できること

k6.io

k6 の導入

k6 の導入は簡単で Mac なら以下のコマンド1発で終わります.

$ brew install k6

k6.io

k6 でテストを実行する

k6 でのテストを実行していきます.まずは Getting Started な内容で書いていきます.export default function ()の中で API を呼び出す順番だったり,条件を記述してシナリオを作っていきます.

import http from 'k6/http';
import { sleep } from 'k6';

export default function () {
  http.get('https://test.k6.io');
  sleep(1);
}

上記のテストを実行してみます.テストの実行は k6 run テストファイル名 で行います.

% k6 run test.ts 

          /\      |‾‾| /‾‾/   /‾‾/   
     /\  /  \     |  |/  /   /  /    
    /  \/    \    |     (   /   ‾‾\  
   /          \   |  |\  \ |  ()  | 
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: test.ts
     output: -

  scenarios: (100.00%) 1 scenario, 1 max VUs, 10m30s max duration (incl. graceful stop):
           * default: 1 iterations for each of 1 VUs (maxDuration: 10m0s, gracefulStop: 30s)


running (00m01.3s), 0/1 VUs, 1 complete and 0 interrupted iterations
default ✓ [======================================] 1 VUs  00m01.3s/10m0s  1/1 iters, 1 per VU

     data_received..................: 132 kB 105 kB/s
     data_sent......................: 516 B  411 B/s
     http_req_blocked...............: avg=198.22ms min=198.22ms med=198.22ms max=198.22ms p(90)=198.22ms p(95)=198.22ms
     http_req_connecting............: avg=8.85ms   min=8.85ms   med=8.85ms   max=8.85ms   p(90)=8.85ms   p(95)=8.85ms  
     http_req_duration..............: avg=51.22ms  min=51.22ms  med=51.22ms  max=51.22ms  p(90)=51.22ms  p(95)=51.22ms 
       { expected_response:true }...: avg=51.22ms  min=51.22ms  med=51.22ms  max=51.22ms  p(90)=51.22ms  p(95)=51.22ms 
     http_req_failed................: 0.00%  ✓ 01  
     http_req_receiving.............: avg=26.08ms  min=26.08ms  med=26.08ms  max=26.08ms  p(90)=26.08ms  p(95)=26.08ms 
     http_req_sending...............: avg=78µs     min=78µs     med=78µs     max=78µs     p(90)=78µs     p(95)=78µs    
     http_req_tls_handshaking.......: avg=172.55ms min=172.55ms med=172.55ms max=172.55ms p(90)=172.55ms p(95)=172.55ms
     http_req_waiting...............: avg=25.06ms  min=25.06ms  med=25.06ms  max=25.06ms  p(90)=25.06ms  p(95)=25.06ms 
     http_reqs......................: 1      0.796752/s
     iteration_duration.............: avg=1.25s    min=1.25s    med=1.25s    max=1.25s    p(90)=1.25s    p(95)=1.25s   
     iterations.....................: 1      0.796752/s
     vus............................: 1      min=1      max=1
     vus_max........................: 1      min=1      max=1

テスト実行後に表示されるアウトプットが多数表示されますが,それぞれの意義は次のとおりです.

表示名 説明
execution k6 の実行モードを表わし,local か cloud がある
output テストの結果がでるが,デフォルトは出力されない
script テストファイルのパス
scenarios テストシナリオの概要
data_received 受信したデータの量
data_sent 送信したデータの量
http_req_blocked リクエストを開始するまでにブロックされていた時間
http_req_connecting リモートホストとのTCP接続確立に要した時間
http_req_duration サーバーがリクエストを受け付けてからレスポンスを返すまでにかかった時間
http_req_failed リクエストの失敗割合
http_req_receiving サーバーからのレスポンスデータの受信にかかった時間
http_req_sending サーバーへのデータ送信にかかった時間
http_req_tls_handshaking サーバーとのTLSセッションのハンドシェイクにかかった時間
http_req_waiting サーバーからのレスポンスを待つ時間
http_reqs k6 が生成したHTTPリクエストの総数
iteration_duration テスト1回の反復処理に要した時間
iterations テストスクリプトを実行した回数の総計
vus アクティブな仮想ユーザー数
vus_max 最大の仮想ユーザー数

関連情報

k6.io k6.io

k6 のオプション

k6 ではテストを作っていく時に様々なオプションがあります.ここでは自分が使ってみてよく使いそうなものをまとめていきます.

ユーザーの増加

サービスを本番に載せたときに稼働状況からユーザーが段階的に増えることはよくあると思います.そんなケースを k6 で表現できます.以下のコードは最初はユーザー数5だけど,10秒ごとに30->50->80と target で指定した分,ユーザー数が段階的に増えるようなイメージです.

export let options = {
  scenarios: {
    'api_scenario': {
            executor: 'ramping-vus',
            startVUs: 5,
            stages: [
                { duration: '10s', target: 30},
                { duration: '10s', target: 50},
                { duration: '10s', target: 80},
                { duration: '10s', target: 110},
                { duration: '10s', target: 140},
                { duration: '10s', target: 170},
                { duration: '10s', target: 200}
            ],
            gracefulRampDown: '10s'
        }
    }

テスト結果のイメージとしては次のよう表示になり,api_scenario部分のパラメーターがどんどん増えるような動作をします.

running (1m20.0s), 000/200 VUs, 38 complete and 172 interrupted iterations
api_scenario ✓ [======================================] 170/200 VUs 

テストのサマリーを好みの表示にする

テスト結果をシステムの目標値,例えばレスポンスタイムの99パーセンタイルでみたら 0.5 秒以下になるかみたいなのをすぐに見れると楽ですよね? そういったテストの結果の表示をカスタマイズできます.

export let options = {
    summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(95)', 'p(99)', 'count'],
};

デフォルトですと'avg', 'min', 'med', 'max', 'p(95)'が表示されますが,更に追加で 'p(99)', 'count'を表示するようにしました.

     checks.........................: 100.00% ✓ 12510    
     data_received..................: 4.6 MB  51 kB/s
     data_sent......................: 385 kB  4.3 kB/s
   ✓ failed requests................: 0.00%   ✓ 01251 
     http_req_blocked...............: avg=6.38ms   min=0s      med=1µs     max=237.1ms  p(95)=33.8ms   p(99)=70.11ms  count=1251
     http_req_connecting............: avg=2.05ms   min=0s      med=0s      max=221.34ms p(95)=9.88ms   p(99)=16.56ms  count=1251
   ✓ http_req_duration..............: avg=88.12ms  min=23.63ms med=68.47ms max=1.87s    p(95)=151.46ms p(99)=420.5ms  count=1251
       { expected_response:true }...: avg=88.12ms  min=23.63ms med=68.47ms max=1.87s    p(95)=151.46ms p(99)=420.5ms  count=1251
     http_req_failed................: 0.00%   ✓ 01251 
     http_req_receiving.............: avg=174.17µs min=40µs    med=128µs   max=21.53ms  p(95)=348.5µs  p(99)=538µs    count=1251
     http_req_sending...............: avg=188.65µs min=24µs    med=166µs   max=973µs    p(95)=371µs    p(99)=525.5µs  count=1251
     http_req_tls_handshaking.......: avg=4.19ms   min=0s      med=0s      max=181.1ms  p(95)=23.69ms  p(99)=35.61ms count=1251
     http_req_waiting...............: avg=87.75ms  min=23.34ms med=68.05ms max=1.85s    p(95)=151.09ms p(99)=420.16ms count=1251
     http_reqs......................: 1251    13.89481/s
     iteration_duration.............: avg=1m28s    min=1m27s   med=1m28s   max=1m29s    p(95)=1m29s    p(99)=1m29s  count=6   
     iterations.....................: 6       0.066642/s
     vus............................: 71      min=5      max=99 
     vus_max........................: 100     min=100    max=100

テストの合格条件を定める

テストの合格条件を自分はこれまで目視でチェックしたり計算していましたが,k6 では合格条件の数値を指定してそれをチェックできます.例えば,次のコードでは k6 の結果で出てくる項目の failed requestshttp_req_duration で合格条件をチェックするようにしています.条件としては次のようなもの意図して設定しています.

  • エラー率が1%未満
  • レスポンスタイムが99%タイルで500ms以下
export let options = {
  thresholds: {
    'failed requests': ['rate<0.01'], 
    'http_req_duration': ['p(99)<500'] 
  },
};

関連情報

k6.io

k6.io

k6.io

まとめ

k6 の概要,導入,テスト実行,オプションをの簡単な紹介を記事にまとめました.テストでチェックしたい合格基準の閾値を設定すれば,そのチェックをしてくれたり実際の利用シーンのようなユーザー数の増加も調整できたりと,使ってみてよかったです.本番に乗せる前に API の機能でどれくらいパフォーマンスが出て,目標値に達していそうかがわかるのが良かったので,気になる方は使うきっかけになれば嬉しいです!

ISUCON 11 予選に参加してきたので振り返る

タダです.

8/21開催の ISUCON 11予選に Podcast をやっているメンバー @hassaku_63@KazuyaKatoと出場してきました.チーム内での振り返りは Podcast でやったんですが,個人的な振り返りもこの記事でやります.

anchor.fm

結果

結果としては失格でした.理由はセキュリティグループのルールを追加していたのを予選終了時に外していなかったためです.競技中に追加のセキュリティグループは NG という運営側と選手間でのやりとりを見ていたので,自分が追加したのは外したけど,他のメンバーへの共有が漏れており申し訳ない気持ちでした...

f:id:sadayoshi_tada:20210829002237p:plain

振り返り

準備

準備段階ではツールのセットアップやミドルウェア設定変更のコマンドをまとめてみたり,チームでの練習環境構築のために Terraform を作ったりしていました.練習環境には matuu さんの AMI を使わせていただきました,ありがとうございます 🙏

予選当日

予選が開始してからは全員でレギュレーションやマニュアル,アプリケーションの説明などを読み込んだ後,作業に当たっていきました.Podcast でも話してましたが,反省として各人がそれぞれ改善に手をつけたので,ルールを読んだもののスコアが上がる部分にチームとして方針立てて進められなかったです.講評を見ていても手をつけられたのもあれば,手をつけられなかったのがあるので,自分自身の能力的にそこがたりてなくてアプリケーション改善は2人にお任せになってしまっていたのが悔しかったです.

isucon.net

Podcast でも話したように最後に Python ではスコアが上がらず,初期実装の Go に戻してみたらどうかってことで切り替え後ベンチマーク走らせたら,800~1000のスコアで止まっていたところからいきなり20000点台にスコアアップしました.この時に全員で思わずずっと「え?まじ?」しか言ってませんでしたw

これまで ISUCON には3回でてきて今まで Python に実装を変更して臨んできたのですが,今回の Go に切り替えてスコアが格段に変わったのをみて,次回は Go で臨めるようになりたいなという想いが強くなりました.過去のスコアを見ていても1万点以上をチームで出したことがなく Go 実装のソースコード に手を入れてなくてミドルウェア周りをいじっただけで2万点以上を取れたことからも Go で ISUCON に挑めるようになるのがよいのだろうと感じます.Mackerel 周りでも Go が使われていたりと以前から Go に対する関心があってちょくちょく触っていたのですが,来年に向けて継続的にコードを書いて手を動かしていきたいというのが来年に向けての準備の一つにしたいと思いました.

まとめ

ISUCON11予選の個人的な振り返りをしました.ISUCON が年々参加すること自体が難しくなっているような気がするので,次回も開催されて参加できるように1年間準備していけたらと思います.そーだいさんもおっしゃっていましたが,ISUCON に出たことで楽しむことだけでなく自分へ新しい刺激が入っていくのを感じます.来年開催されたら今年以上に楽しめるようやっていきます!

普段、自分の武器で仕事をこなしてるからこそ、新たな自分の武器を磨くチャンスとしてISUCONは常に新しい課題をくれる。

soudai.hatenablog.com

Terraform で Secrets Manager に登録するレコードをコード内に直書きせず保存する

タダです.

プログラムで使うトークンや DB 接続情報,鍵情報といった秘匿情報を Secrets Manager に入れています.Secrets Manager に保管してる情報を Terraform で管理しようとした時コードで直書きし,GitHub などで管理するのはリスキーなので別の方法を探していた時に以下のブログを見つけ,参照しつつ試したことをこの記事にまとめていきます.

blog.gruntwork.io

ブログで紹介されていた秘匿情報の取り扱い方法

冒頭のブログで紹介されていた方法として以下の3つがあります.

  1. 変数にして外出しする
  2. ファイルを暗号化してバージョン管理する
  3. Secrets Manager や Hashicorp Vault などに格納する

一つ目は毎回任意のコマンドを打てる環境ならいいのですが,自動デプロイ を仕組んでいる場合にその運用コストが高そうだなと思ったのと,三つ目は現状やっていることでやりたいことに当たらなかったので,二つ目の手法を使ってアプローチを試みることにしました.

大まかな作業の流れ

二つ目の手法の大まかな流れとして,まず Secrets Manager で扱う情報群を YAML ファイルに書き出します.その後,YAML ファイルを KMS を使って暗号化し,そのファイルを Terraform のコードで復号化 -> パラメーターごとにマッピングするといった作業を行います.

秘匿情報を格納する YAML ファイルの作成

secrets.yaml というファイルを作成し,その中に Secrets Manager に格納したい キー:バリュー の順番で転記します.そして,secrets.yamlリポジトリで管理しないように .gitignore で除外しておきます.

hoge_token: hogehoge
dbhost: hoge.cluster-xxxx.ap-northeast-1.rds.amazonaws.com
dbhost_replica: hogecluster-ro-xxxx.ap-northeast-1.rds.amazonaws.com
dbname: hogedb
password: hogehoge
username: hoge

KMS での暗号化処理

次に,KMS で secrets.yaml を暗号化します.AWS CLI で次のようなコマンドを実行して secrets.yaml.encrypted が生成されることを確認します.また,このファイルはリポジトリで管理するようにします.

aws kms encrypt \
  --key-id xxxx-xxxx-xxxx-xxxx-xxxx(個別に発行した CMK) \
  --region ap-northeast-1 \
  --plaintext fileb://secrets.yaml \
  --output text \
  --query CiphertextBlob \
  > secrets.yaml.encrypted

なお,生成された secrets.yaml.encrypted の中身は見てもらうとわかりますが,全然何が書いているか当然わからないようになっています.

Terraform での復号化と秘匿情報のマッピング

secrets.yaml.encrypted の生成が終わったら Terraform のコードを書いていきます.復号化の処理は data.aws_kms_secretsyamldecode(data.aws_kms_secrets.secrets.plaintext["secrets"]) で行います. hoge_secrets の中に復号化した秘匿情報が入っているのですが,そのデータを aws_secretsmanager_secret_versionsecret_stringマッピングしていきます.local.hoge_secrets.[YAML ファイルで定義したキー] といった具合です.

コード例

data "aws_kms_secrets" "secrets" {
    secret {
        name = "secrets"
        payload = file("./secrets.yaml.encrypted")
    }
}

resource "aws_secretsmanager_secret" "hoge_secrets" {
    name = "hoge-secrets"
    recovery_window_in_days = 30
}

locals {
    hoge_secrets = yamldecode(data.aws_kms_secrets.secrets.plaintext["secrets"])
}


resource "aws_secretsmanager_secret_version" "hoge_secrets_detail" {
    secret_id = aws_secretsmanager_secret.hoge_secrets.id
    version_stages = ["AWSCURRENT"]
    secret_string = jsonencode({
        username = local.hoge_secrets.username
        password = local.hoge_secrets.password
        dbhost = local.hoge_secrets.dbhost
        dbname  = local.hoge_secrets.dbname
        dbhost_replica = local.hoge_secrets.dbhost_replica
        hoge_token = local.hoge_secrets.hoge_token
    })
}

関連情報

Terraform の適用と結果確認

Terraform の適用と結果確認していきます.secret_stringの中身は(sensitive value)という形で見えないようになっているので,どの秘匿情報を Secrets Manager に保存しようとしているかはわからないです.

Terraform will perform the following actions:

  # aws_secretsmanager_secret.hoge_secrets will be created
  + resource "aws_secretsmanager_secret" "hoge_secrets" {
      + arn                     = (known after apply)
      + id                      = (known after apply)
      + name                    = "hoge-secrets"
      + name_prefix             = (known after apply)
      + policy                  = (known after apply)
      + recovery_window_in_days = 30
      + rotation_enabled        = (known after apply)
      + rotation_lambda_arn     = (known after apply)
      + tags_all                = (known after apply)

      + rotation_rules {
          + automatically_after_days = (known after apply)
        }
    }

  # aws_secretsmanager_secret_version.hoge_secrets_detail will be created
  + resource "aws_secretsmanager_secret_version" "hoge_secrets_detail" {
      + arn            = (known after apply)
      + id             = (known after apply)
      + secret_id      = (known after apply)
      + secret_string  = (sensitive value)
      + version_id     = (known after apply)
      + version_stages = [
          + "AWSCURRENT",
        ]
    }

適用の結果を見ると,意図通りの設定値が入っていました.

f:id:sadayoshi_tada:20210828175644p:plain f:id:sadayoshi_tada:20210828175651p:plain

まとめ

Secrets Manager にいれる秘匿情報を Terraform で管理する時どうするかを悩んでいたので,冒頭のブログを参考に KMS を使いつつ試してみました.Terraform のコードに秘匿情報は載らないし,暗号化して管理できかつTerraform の適用時にも秘匿情報がターミナル上にも出てこないため運用上も良さそうに思います.何か参考になれば嬉しいです!