継続は力なり

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

暗号化した Aurora スナップショットを別アカウントで復元する

タダです.

本番環境から別アカウントに暗号化した Aurora スナップショットを共有し,仕組み化して復元する時に少し手間取ったので対応をまとめていきます.

暗号化した Aurora スナップショットを共有する設定

Aurora スナップショットを共有する方法は AWS のドキュメントに記載があります.共有したいスナップショットの設定で共有するアカウント番号を指定すれば共有できます.

aws.amazon.com

AWS CLI でやるとスナップショット作成と共有するアカウント設定は以下のコマンドでできます.

aws rds create-db-cluster-snapshot \
    --db-cluster-identifier [クラスター名] \
    --db-cluster-snapshot-identifier [スナップショット名]

aws rds modify-db-cluster-snapshot-attribute \
    --db-cluster-snapshot-identifier [スナップショット名] \
    --attribute-name restore \
    --values-to-add [共有するアカウント番号]

今回は更に KMS(CMK)でスナップショットを暗号化しているため KMS でも別アカウントからアクセス許可を次のようなポリシーの形で与えてあげる必要があります.

{
  "Id": "key-policy-1",
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Allow use of the key",
      "Effect": "Allow",
      "Principal": {"AWS": [
        "arn:aws:iam::111111111111:user/xxx",
        "arn:aws:iam::[共有するアカウント番号]:root"
      ]},
      "Action": [
        "kms:CreateGrant",
        "kms:Encrypt",
        "kms:Decrypt",
        "kms:ReEncrypt*",
        "kms:GenerateDataKey*",
        "kms:DescribeKey"
      ],
      "Resource": "*"
    },
    {
      "Sid": "Allow attachment of persistent resources",
      "Effect": "Allow",
      "Principal": {"AWS": [
        "arn:aws:iam::111111111111:user/xxx",
        "arn:aws:iam:::[共有するアカウント番号]:root"
      ]},
      "Action": [
        "kms:CreateGrant",
        "kms:ListGrants",
        "kms:RevokeGrant"
      ],
      "Resource": "*",
      "Condition": {"Bool": {"kms:GrantIsForAWSResource": true}}
    }
  ]
}               
            

docs.aws.amazon.com

共有されたアカウントでの設定

共有されたアカウント側の設定ですが,Lambda などのサービスをつかってスナップショットを復元に使いたい場合 IAM ポリシーの設定が必要です.下記の権限を当てた IAM ロールを作ってサービスで使っていますが,共有したスナップショットを復元してクラスター起動ができました.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowUseOfTheKey",
            "Effect": "Allow",
            "Action": [
                "kms:Encrypt",
                "kms:Decrypt",
                "kms:ReEncrypt*",
                "kms:GenerateDataKey*",
                "kms:DescribeKey",
                "kms:CreateGrant",
                "kms:RetireGrant",
                "kms:DescribeKey"
            ],
            "Resource": [
                "arn:aws:kms:[リージョン]:[CMK が存在するアカウント番号]:key/[CMK の ID]"
            ]
        }
}

まとめ

暗号化した Aurora スナップショットを別アカウントで復元するための設定と権限周りの定義をさらってきました.共有したアカウントの IAM ポリシーでうまく復元ができずにいたのですが,通る形を確認できてよかったです.同じような仕組み化する方の参考になれば嬉しいです!

pytest と moto でモックを作ったテストを行う

タダです.

以前から気になっていた moto というツールを試す機会があったので,この記事で導入と pytest と組み合わせたテストをチュートリアルの内容を通して見ていきたいと思います.

moto って?

moto とはテストで AWS サービスをモックで作っていくための Python ライブラリです.

github.com

導入はpipでさくっとできます.なお,motoは全ての AWS サービスを呼び出す関数を網羅しているわけではなさそうなので,その点は注意です.

$ pip install moto

moto が対応しているサービス一覧 github.com

moto でモックを作り pytest でテストする

それでは, moto を使ってみようと思います.

$ pipenv install boto3
$ pipenv install --dev pytest moto

以下のようなディレクトリ切って,app.pyとそのテストをするためのtest.pyを作りました.

.
├── app
│   ├── app.py
│   └── __init__.py
└── tests
    ├── __init__.py
    └── test.py

チュートリアルとして S3 に put_object を実行する save 関数があるコードを用意しました.

import boto3

class MyModel(object):
    def __init__(self, name, value):
        self.name = name
        self.value = value

    def save(self):
        s3 = boto3.client('s3', region_name='ap-northeast-1')
        s3.put_object(Bucket='mybucket', Key=self.name, Body=self.value)

テスト用コードとしては次のものを用意しました.@mock_s3を記述することで S3 へのアクセスをモックしてくれてます.save関数を呼んで,オブジェクトを読みファイルの中身がis awesomeかを確認するようになっています.

import boto3
from moto import mock_s3
from app.app import MyModel

@mock_s3
def test_my_model_save():
    conn = boto3.resource('s3', region_name='ap-northeast-1')
    conn.create_bucket(Bucket='mybucket',CreateBucketConfiguration={
        'LocationConstraint':'ap-northeast-1'
    })

    model_instance = MyModel('steve', 'is awesome')
    model_instance.save()

    body = conn.Object('mybucket', 'steve').get()[
        'Body'].read().decode("utf-8")

    assert body == 'is awesome'

テストコードを走らせてみたところテストをパスしたのを確認できました.

$ pipenv run pytest tests/test.py 
=============================================================================== test session starts ===============================================================================
platform linux -- Python 3.7.9, pytest-6.2.3, py-1.10.0, pluggy-0.13.1
rootdir: /xxxx/xxxx/environment
collected 1 item                                                                                                                                                                  

tests/test.py .                                                                                                                                                             [100%]

================================================================================ warnings summary =================================================================================
../.local/share/virtualenvs/environment-QZ1wNgYc/lib64/python3.7/distutils/__init__.py:1
  /xxxx/xxxx/.local/share/virtualenvs/environment-QZ1wNgYc/lib64/python3.7/distutils/__init__.py:1: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
    import imp

-- Docs: https://docs.pytest.org/en/stable/warnings.html
========================================================================== 1 passed, 1 warning in 0.69s ===========================================================================

まとめ

簡単になりますが,motoを使ったテストコードを書くことをやってみました.AWS リソースを作ってまで確認するとコストがかかって確認が遅くなってしまうような状況ならこう言ったツールを作って素早く確認して行けると良さそうです.

Mackerel と Amazon EventBridge を連携してアラート後のリカバリアクションを追加する

タダです.

本稼働しているサーバーの中にアクセスが多くないものの時折 CPU が 100% と高負荷になって突発的にサーバーがダウンしてしまう事象があり,その都度サーバーを再起動することが何度かありました.アクションが決まっているなら Mackerel と Amazon EventBridge を使ってリカバリアクションを仕組み化したいと思って連携設定を試したので,この記事で設定をまとめていきます.

Mackerel と EventBridge

Mackerel は EventBridge と連携することができます.監視している対象リソースの情報を JSON 形式で EventBridge へ通知してその内容をフックに Lambda を起動したりすることができます.今回もこの連携機能を使った設定をしていきます.Mackerel でのアラート検知 -> EventBridge -> Lambda でアラートが発生した対象サーバーにリカバリ処理をかけるといった流れです.この記事では Mackerel と EventBridge の連携および EventBrdige から Lambda を呼び出す設定部分で自分が少してこずったのでそこに重点を置いて記事を書きます.

mackerel.io

Mackerel と EventBridge へのアラート通知設定

Mackerel と EventBridge へのアラート通知設定をしていきます.

1. EventBridge で情報を確認

まずは,EventBridge から イベント > パートナーイベントソースより Mackerel を検索すると下記のような画面が出てきます.設定ボタンから画面が遷移し,Mackerel で必要な設定を確認します.

f:id:sadayoshi_tada:20210406002955p:plain f:id:sadayoshi_tada:20210406003011p:plain

2. Mackerel のアラート設定

Mackerel の通知チャンネル追加から Amazon EventBridge を選び EventBridge の画面にでていたアカウント番号やチャンネル名をつけていきます.赤運tの番号とイベント名,リージョンは一度設定すると変更ができないので注意が必要です.

f:id:sadayoshi_tada:20210406003830p:plain f:id:sadayoshi_tada:20210406003958p:plain

EventBridge の画面に戻り,イベントバスと関連付ける画面で必要なアクセス許可を選択して関連付けるボタンを押します.すると,下記の画像のようにアクティブステータスになります.これで Mackerel のアラートイベント以降のアクションを設定できるところまで来ました.

f:id:sadayoshi_tada:20210406005542p:plain f:id:sadayoshi_tada:20210406004430p:plain

3. EventBridge でアクションを定義

そして,EventBridge から Lambda を呼び出す部分を定義していきます.イベント > ルールのページに移動し,イベントバスのプルダウンを選ぶと先ほど関連づけたアラートのイベントバスが表示されます.ここで設定したいアラートのイベントを選んでルールを作成から作っていきます.

f:id:sadayoshi_tada:20210406010027p:plain

イベント名や説明は任意のものをつけ,パターンを定義のセクションでイベントパターン>サービスごとの事前定義パターン>サービスパートナー>Mackerelを選択します.

f:id:sadayoshi_tada:20210406010358p:plain

次に,イベントバスで設定した Mackerel のイベントバスを選択します.

f:id:sadayoshi_tada:20210406011324p:plain

そして,アラート後に呼び出したい Lambda を指定して保存します.

f:id:sadayoshi_tada:20210406011210p:plain

これで対象のアラートが飛べば,Mackerel -> EventBridge -> Lambda が呼び出されることになります.下記の画像は一例として Mackerel のアラート発生画面から EventBridge が呼び出されたこと,EventBridge 側は CloudWatch のメトリクスで呼び出されたのを確認した画面です.

f:id:sadayoshi_tada:20210406011803p:plain f:id:sadayoshi_tada:20210406011642p:plain

まとめ

アラート発生時の定型的な対応を Mackerel と EventBridge ,Lambda によって仕組み化することができました.自動で復旧のアクションをとることができるので,アラートの根本問題の対応等に集中していけるなと感じます.今回使ってみて便利だなと感じたので他にも適用箇所があれば積極的に検討していきたいです!

FireLens で出力されたコンテナのログを Athena でクエリする

タダです.

最近もっぱら ECS 周りを触っているんですが,コンテナのログを CloudWatch Logs に出すのはコスト的に避けたかったので FireLens 経由で S3 に出すようにしました.S3 に出しちゃえば Athena からログをクエリできると思ってやってみたのでこの記事でやり方をまとめていきます.

aws.amazon.com

FireLens のログの中身について

まず,FireLens から送られてくるログの中身をみていきましょう.ログの中身は次の項目がはいった JSON 形式になっています.

  • container_id
  • container_name
  • ecs_cluster
  • ecs_task_arn
  • ecs_task_definition
  • log
{"container_id":"xxx","container_name":"xxx","ecs_cluster":"xxx","ecs_task_arn":"arn:aws:ecs:xxx:xxx:task/xxx/xxx","ecs_task_definition":"xxx:xxx","log":"INFO: xxx"}

なので,これら形式に合うように Athena のテーブルを作る必要があります.

Athena のテーブル作成

Athena でテーブルを作っていくんですが,全てのログは必要ないため今回は絞ってcontainer_id, container_name,logをカラムにしました.下記のクエリが Athena のテーブル作成用のクエリ例です.

CREATE EXTERNAL TABLE xxx (
    container_id string,
    container_name string,
    log string
)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
LOCATION 's3://xxx/'

これでクエリができるようになったので,クエリをしてみたところ下記の画像のようにクエリ結果を得ることができました.logの中にログの詳細が全て含まれてしまっているのでログの中身で絞り込みたい時は LIKEなどを使って絞り込みすると良さそうです.

f:id:sadayoshi_tada:20210331204944p:plain

まとめ

小ネタですが,FireLens で出力された S3 のログに Athena からクエリを発行してみました.今担当しているコンテナのログは一個しか出てないのですが,状況見ながら出し分け先を変えたりも FireLens ではできそうなのでまたそれもできたら記事にしていきたいと思います.

docs.aws.amazon.com

『JAWS DAYS 2021 - re:Connect -』でシステムリリースフローの刷新の取り組みを話してきた

タダです.

3/20 開催の「JAWS DAYS 2021 - re:Connect -」にて「スタートアップ企業での散乱したシステムリリースフローをととのえる話」というタイトルでオンライン登壇させていただきました.開発者が安心かつスムーズなリリースフローを作り、開発生産性を向上させたいと言う課題感からリリースフロー刷新に取り組んだお話をしました.この記事で発表を振り返ってきます.

メインサイト jawsdays2021.jaws-ug.jp

セッション情報 jawsdays2021.jaws-ug.jp

発表資料

発表資料はこちらです.

発表の振り返り

発表した取り組みはリリース周りでまだ仕組み化されてなかったり,複雑になっていることで開発のやりづらさがあるのではないかという課題感や開発サイドへのヒアリングから刷新をしていくことにしました.

改善に向けての取り組みはいくつかの記事でも紹介しておりましたが,GitHub Actions を中心にした形に変えていき,GitHub を見ればリリースに関する設定もわかるし開発者も慣れているツールだから最悪自分がいなくなってもリリースが止まることはないだろうとも思い,採用していきました.また,慣れているからこそ GitHub Actions の処理がうまくいかない時は助言もくれたりしてくれています.

関連記事

sadayoshi-tada.hatenablog.com

sadayoshi-tada.hatenablog.com

sadayoshi-tada.hatenablog.com

システムリリースの部分で課題に感じたところの刷新をしていけたのですが,デプロイしているコードの中にはテストが十分に書けてなかったりしてリリースフローに組み込めてなかったり,発表の中で触れたまとまったコマンドスクリプトがうまくいかない事象が発生し始めたので原因究明して改善していかないといけなかったりと課題が残っているのを潰してよりよい仕組みに育てていければと思います.とはいえ,開発者からはいいリアクションをもらっているのはありがたいことです.

質問への回答

発表に対するツイートで反応くださった方ありがとうございました! 拙い発表においてよかったとリアクションをいただけたりして大変ありがたかったです🙇‍♂️ Twitter で質問いただいていたことについて回答させていただきます.

Q. デプロイのスクリプトがサーバーによって異なるのはなぜか?

デプロイスクリプトがサーバーごとに違うことについて質問いただいていました.ヒアリングの時に判明したのがリリースの対象サーバーにコードを git pull する処理やミドルウェアの再起動などといった処理は共通しているのですが,Railsdb:migrate 処理が1台のサーバーだけで実行するようにしていたいためスクリプトの処理が現状異なっています.

Q. DB は1つなのか?

今後の改善で別れる場合もあるのですが,DB は現状1つです.

Q. どのタイミングで GitHub Actions を実行しているのか?

GitHub Actions ですが,EC2 のシステムはプルリクマージ後に指定した時間に Run Command を走らせる設定を定義できるように設定していくようにしています.Lambda/Step Functions は2つのタイミングで走っててプルリクが上がってきたタイミングで差分検出した結果を一時的に S3 に入れるのと,マージ後にデプロイする処理が走っています.

まとめ

登壇の振り返りや質問いただいたことを書いてみました.いつもは JAWS DAYS は見るだけな参加を数年続けてきて CFP を出したのは二回目で登壇の機会は初めていただけたので大変ありがたかったです! 登壇者として準備にあたりリハーサルや,配信環境,PR,マイクを送っていただいたりと運営の皆様の手厚いサポートや熱量を非常に感じたイベントでした.また登壇できるように AWS の使い込んで発表していければと思います.ありがとうございました!!