Foreverly

メモ帳

ElasticCacheのイベント通知をSlackに投げ隊

これはなに?

ElasticCacheのイベント通知をSlackに通知する奴

どうして通知するんですか?

他のプロジェクトでDB周りのアラートが発報して確認したらイベント通知にメンテで再起動されていた。

イベント情報を検知できるようにイベント通知をしたくなった。

構成

シンプルにElasticCache→SNS→Lambda→Slack

ElasticCacheのイベント通知先をSNSのトピックにして、サブスクライバーであるLambdaに対して投げつける。ログはCloudWatch Logsに投げる。Lambda FunctionはPythonでSlack通知させました。

ここの一通りの設定を見ていきます。

f:id:oza__shu:20201113191816p:plain

Lambdaで使用するので、Webhook URLを作成

名前と通知先とかわいい画像を指定して作成してください。

SNS通知先の設定

これはElasticCacheの設定でSNSのarnを渡すだけ。

KMSのkeyを作成

keyを作成したら使うのにaliasも必要になるので準備すればOK

SNSでTopicとサブスクライバーの設定

Topicとサブスクリプションを作成します。

Topicは作成したら以下のポリシーを当てるぐらい

statement {
    sid    = "LambdaPublish"
    effect = "Allow"
    principals {
      type        = "AWS"
      identifiers = ["*"]
    }
    actions = [
      "SNS:GetTopicAttributes",
      "SNS:Publish"
    ]
    resources = [
      "arn:aws:sns:ap-northeast-1:${data.aws_caller_identity.self.account_id}:topic",
    ]
  }
}

サブスクリプションはLambdaに送るので送り先のARNをプロトコルでLambdaを指定すればOK.

メッセージ送信をテストで実行してSNSとLambda間で疎通が取れるかテスト可能です。

次はLambdaを用意していきます。

Lambdaに必要なIAMポリシー

Lambda用のRoleが必要なので作成します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Effect": "Allow"
    }
  ]
}

Webhook URLはKMSで暗号化して渡すので、解読させるためにKMSのDecryptの権限ポリシーが必要です。

{
    "Version": "2012-10-17",
    "Statement": [
      {
          "Effect": "Allow",
          "Action": "kms:Decrypt",
          "Resource": "arn:aws:kms:ap-northeast-1:${data.aws_caller_identity.self.account_id}:key/${aws_kms_alias.kms_alias.target_key_id}"
      }
    ]
 }

作成したロールに上のと下2つのポリシーをアタッチすればOK

  • CloudWatchReadOnlyAccess
  • AWSLambdaBasicExecutionRole

SlackのhookのURLをKMSで暗号化

Terraformではできなかったので、手動で暗号化をして、 それをTerraformでLambdaの関数に読ませました。

暗号化したURLは次の手順で作成

  1. 転送時の暗号化に使用するヘルパーの有効化のチェックボックスをチェック
  2. 保管時に暗号化する AWS KMS キーの選択で、作った暗号化キーを選択
  3. kmsEncryptedHookUrlのvalueに、SlackのWebhookのURLを入れる
  4. 暗号化ボタンを押下
  5. 暗号化完了

暗号化したらLambdaの kmsEncryptedHookUrl 環境変数に渡します。

Lambdaでイベント通知の絞り込み

イベント内容で絞り込みしないと毎日のsnapshotのイベントとかで検知してしまうので、

それでイベント通知は以下の2つに絞り込みました。

ElastiCache:FailoverComplete
ElastiCache:CacheNodeReplaceComplete

スクリプトは以下のように暗号化したslackのURLとチャンネル名を渡して、

イベント通知に一致したらslack通知する内容です。

import boto3
import json
import logging
import os

from base64 import b64decode
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError

# The base-64 encoded, encrypted key (CiphertextBlob) stored in the kmsEncryptedHookUrl environment variable
ENCRYPTED_HOOK_URL = os.environ['kmsEncryptedHookUrl']
# The Slack channel to send a message to stored in the slackChannel environment variable
SLACK_CHANNEL = os.environ['slackChannel']

HOOK_URL = "https://hooks.slack.com" + boto3.client('kms').decrypt(
    CiphertextBlob=b64decode(ENCRYPTED_HOOK_URL),
    EncryptionContext={
        'LambdaFunctionName': os.environ['AWS_LAMBDA_FUNCTION_NAME']}
)['Plaintext'].decode('utf-8')

logger = logging.getLogger()
logger.setLevel(logging.INFO)

NOTIFICATION_EVENT_TYPE = [
    'ElastiCache:FailoverComplete',
    'ElastiCache:CacheNodeReplaceComplete',
]

def lambda_handler(event, context):
    logger.info("Event: " + str(event))
    print(event['Records'][0]['Sns']['Message'])
    message = json.loads(event['Records'][0]['Sns']['Message'])
    event_time = event['Records'][0]['Sns']['Timestamp']

    # イベントタイプがNOTIFICATION_EVENT_TYPEに含まれない場合は処理を終了
    event_set = set(message.keys())
    notification_event_type_set = set(NOTIFICATION_EVENT_TYPE)
    event_type = event_set & notification_event_type_set
    if not event_type:
        return

    logger.info("Message: " + str(message))

    slack_message = {
        'channel': SLACK_CHANNEL,
        'text': "%s ElastiCache Notification Message: %s" % (event_time, message)
    }

    req = Request(HOOK_URL, json.dumps(slack_message).encode('utf-8'))
    try:
        response = urlopen(req)
        response.read()
        logger.info("Message posted to %s", slack_message['channel'])
    except HTTPError as e:
        logger.error("Request failed: %d %s", e.code, e.reason)
    except URLError as e:
        logger.error("Server connection failed: %s", e.reason)

動作確認

Elasticache の SNS 通知の設定について、 Test Failover API 実行時には、 ElastiCache:FailoverComplete 等のイベントが発報されるので、 コンソール上でフェイルオーバーを実施。

通知完了!!

参考URL

KMSを使用してキーを暗号化&復号する手順

TerraformでIAMポリシーのJSONに変数を埋めたい場合はaws_iam_policy_documentを使う

Terraformでテンプレートを使ってポリシーを定義する

terraformからroleにpolicyをattachするときの話

*.tf 内で AWS アカウント ID を自動参照(取得)する aws_caller_identity Data Source)

キー ID と ARN を検索する

Amazon SNS のアクション、リソース、および条件キー

AWS Lambdaを使ったAmazon SNSへのメッセージ送受信

Terraformで構築するAmazon SNSからAWS Lambdaを呼び出すためのトリガ

【AWS】CloudWatchアラーム通知をLambdaでSlack投稿する

AWSの各種アラートをSlackで受け取る

AWS のリソースを監視して Slack に通知する方法 (または cloudwatch-alarm-to-slack の使い方)

ElastiCacheのイベント通知をLambdaを使ってフィルタしてみた

AWS CloudWatchからSlackへ通知する

ElastiCache イベントの表示

helm2to3

これはなに

helm v2からv3にマイグレするやつです。 v2のサポート期間は2020年11月13日までなので即対応必須奴。

Helmの新しいメジャーリリースへのアップグレードで最も重要な部分の1つは、データの移行で、やるにはhelm-2to3プラグインをつかうのがよい。

このプラグインは、以下をしてくれる奴

  • Helm v2の設定の移行
  • Helm v2リリースの移行
  • Helm v2の設定、リリースデータ、Tillerのデプロイメントのクリーンアップ。

やることリスト

  • [ ] helm3のインストール
  • [ ] helm2to3 pluginのインストール
  • [ ] v2のデータをバックアップ
  • [ ] Helm v2の設定を移行
  • [ ] Helm v2のReleaseを移行
  • [ ] Helm v2データをクリーンアップ(Helm v3が期待通りにHelm v2データを管理していることを確認してから)
  • [ ] cicd周りの修正
  • [ ] terraformとかでk8sクラスターでtillerとか入れてたら削除
  • [ ] helm-diff使ってたら最新にあげたほうがいいです。

事前準備

Helm v2と v3 のclientのセットアップ

以下の例みたいにv2とv3のCLIを用意

  • brewで入れたhelm3.3.0(2020/11/12現在は3.4.0が最新)
    • helm (pathが通っている)
  • バイナリでいれたhelm2.14.2
$ helm version
version.BuildInfo{Version:"v3.3.0",GoVersion:"go1.14.6"}


$ helm version
Client: &version.Version{SemVer:"v2.14.2"}
Server: &version.Version{SemVer:"v2.14.2"}

helm-2to3 プラグイン

現在プラグインでは以下のことができる。

  • Helm v2設定の移行
  • Helm v2リリースの移行
  • Helm v2の設定、リリースデータ、Tillerの配置のクリーンアップ
$ helm 2to3 version
Migrate and Cleanup Helm v2 configuration and releases in-place to Helm v3

v2のデータをバックアップと差し戻し手順

Tiller はリリース情報などをすべて Kubernetes ConfigMap オブジェクトに保存しているので、これをバックアップしておけばv2に差し戻しが可能。 あとhelm v2のコマンドとフォルダを保存しておく。

  1. 一覧を出す これらの最新のconfigをバックアップしていく。
$ kubectl get configmap -n kube-system -l "OWNER=TILLER" |cut -d. -f1 |uniq -c|awk '{print $2}'
NAME
hoge-namespace
  1. 最新のconfigmapを保存しておく
kubectl get configmap -n kube-system -o yaml hogehoge-app.v17 > hogehoge-app.v17.yaml

差し戻しのapplyは以下コマンドのを適宜変更して使う。

 kubectl apply -f hogehoge-app.v17.yaml -n kube-system

手順

Helm v2の設定を移行する(localなので不要なら実行しなくてもOK)

まずはHelm v2の設定フォルダとデータフォルダを移行します。 以下を移行する。

  • Chart starters
  • Repositories
  • Plugins

まずは --dry-run をする。

$ helm 2to3 move config --dry-run

実際に移行

$ helm 2to3 move config

WARNING: Helm v3 configuration maybe overwritten during this operation.

[Move Config/confirm] Are you sure you want to move the v2 configration? [y/N]: y

2019/11/14 14:55:00 Helm v2 configuration will be moved to Helm v3 configration.
...
2019/11/14 14:55:00 Helm v2 configuration was moved successfully to Helm v3 configration.

helm v3 のrepo listを実行して確認

$ helm repo list

NAME        URL
stable      <https://kubernetes-charts.storage.googleapis.com>
jfrog       <https://charts.jfrog.io>
rimusz      <https://charts.rimusz.net>
buildkite   <https://buildkite.github.io/charts>
jetstack    <https://charts.jetstack.io>
odavid      <https://odavid.github.io/k8s-helm-charts>
elastic     <https://helm.elastic.co>
appscode    <https://charts.appscode.com/stable>

$ helm plugin list

NAME    VERSION DESCRIPTION
2to3    0.1.0   migrate Helm v2 configuration and releases in-place to Helm v3
edit    0.3.0   Edit a release.
gcs     0.2.0   Provides Google Cloud Storage protocol support.
                <https://github.com/vigles>...
linter  0.1.1   Helm plugin to find hardcoded passwords in values.yaml files
monitor 0.3.0   Query at a given interval a Prometheus, ElasticSearch or Sentry instance...

Helm v2 と同じ Helm リポジトリプラグインが使えるようになっていればOK.

※ Helm v2のプラグインがすべてHelm v3で正常に動作することを確認し、動作しないプラグインは削除すること。 move configは、Helm v3のconfigとdataフォルダが存在しない場合は作成し、存在する場合はrepositories.yamlファイルを上書きする。

このプラグインは、デフォルトではないHelm v2 homeとHelm v3の設定とデータフォルダもサポートする。

$ export HELM_V2_HOME=$HOME/.helm2
$ export HELM_V3_CONFIG=$HOME/.helm3
$ export HELM_V3_DATA=$PWD/.helm3
$ helm3 2to3 move config

Helm v2 リリースの移行

リリースの移行を開始する準備ができたので移行テスト。

listで対象を確認

$ helm list

まずは --dry-run

$ helm 2to3 convert --dry-run hoge-realease

では、実際に移行を実行してみましょう。

$ helm 2to3 convert hoge-realease

一撃ワンライナー

~/bin/helm2/helm ls --output json | jq -r '.Releases[] | select(.Namespace != "default" and .Namespace != "hoge-namespace") | .Name' | xargs -t -I {} helm 2to3 convert {}

移行が成功したかどうかをチェックしてみましょう。 v2とv3でlistで確認

(v2) helm list


$ helm list -n hoge_namespaces

※ --delete-v2-releasesフラグを指定していないので、Helm v2 のリリース情報がそのまま残るが、 後からhelmの2to3クリーンアップで削除することができる。

すべてのリリースを移動する準備ができたら、hellmリストをループで実行し、hellm v2の各リリースに対してhellm 2to3の変換RELEASEを適用することで、 自動化することができる。

Helm v2データのクリーンアップ

最後のステップは、古いデータのクリーンアップ。 以下がクリーンアップされる

  • Configuration (Helm home directory)
  • v2 release data
  • Tiller deployment

まずは --dry-run

どのリリースが削除されるか、kube-system名前空間からTillerサービスが削除されるか、Helm v2のホームフォルダが削除されるかが表示される。

$ helm 2to3 cleanup --dry-run

2019/11/14 15:06:59 NOTE: This is in dry-run mode, the following actions will not be executed.
2019/11/14 15:06:59 Run without --dry-run to take the actions described below:
2019/11/14 15:06:59
WARNING: "Helm v2 Configuration" "Release Data" "Release Data" will be removed.
This will clean up all releases managed by Helm v2. It will not be possible to restore them if you haven't made a backup of the releases.
Helm v2 may not be usable afterwards.

[Cleanup/confirm] Are you sure you want to cleanup Helm v2 data? [y/N]: y
2019/11/14 15:07:01
Helm v2 data will be cleaned up.
2019/11/14 15:07:01 [Helm 2] Releases will be deleted.
2019/11/14 15:07:01 [Helm 2] ReleaseVersion "postgres.v1" will be deleted.
2019/11/14 15:07:01 [Helm 2] ReleaseVersion "redis.v1" will be deleted.
2019/11/14 15:07:01 [Helm 2] Home folder "/Users/rimasm/.helm" will be deleted.

Hem v2 のデータをクリーンアップする準備ができたら、--dry-run フラグを指定せずにコマンドを実行。

$ helm 2to3 cleanup --dry-run

ci/cd周りの修正

helmとかもろもろversion upする

terraformの修正

helmのupgradeとtillerの削除 ここらへん↓を修正

https://ghe.ca-tools.org/odessa/infrastructure/blob/394cc738b716d2d6b332d8b2ff6065f9a762f2bc/gcp/env/common/gke/locals.tf#L2-L96

helm-diffプラグインのupgrade

installシェルでhelm homeというコマンドが使われているけど、helm v3ではhelm homeが消えてインストールできなかったが、helm-diffのバージョンを最新にしたところインストールできました。

参考

How to migrate from Helm v2 to Helm v3helm-2to3プラグインhelm v3.3.0helm2からhelm3に(削除履歴も含めて)マイグレーションできるのか試してみたHelm_v2->v3_移行How Helm Uses ConfigMaps to Store Datahelm2からhelm3に(削除履歴も含めて)マイグレーションできるのか試してみた

Terraform0.13へのUpgrade

Terraform0.13にUpgradeの手順と詰まったPointをメモ

事前準備

こんなアドバイスを受けていたので、先にproviderのバージョンをあげます。

古い環境からterraform v0.13.0+aws providerv3に一気に上げるとtfstateが不整合っぽいエラーでまくるので、
terraformv0.12.x+aws provider v3で一度apply通してから(何のリソースでもok)だとエラーなくなるので、その順番で0.13に上げると良さそうです

手順

各providerも0.13に対応させる terraform 0.13upgradeを各ステートに実行 terraform init,applyを実行して通ればOK

詰まったところ

  1. providerが読み込めないと怒られる

workspaceごとにterraform init,applyをしてstateを最新にしたら突破できました。

Error: Could not load plugin


Plugin reinitialization required. Please run "terraform init".

Plugins are external binaries that Terraform uses to access and manipulate
resources. The configuration provided requires plugins which can't be located,
don't satisfy the version constraints, or are otherwise incompatible.

Terraform automatically discovers provider requirements from your
configuration, including providers used in child modules. To see the
requirements and constraints, run "terraform providers".

7 problems:

- Failed to instantiate provider "registry.terraform.io/-/archive" to obtain
schema: unknown provider "registry.terraform.io/-/archive"
- Failed to instantiate provider "registry.terraform.io/-/aws" to obtain
schema: unknown provider "registry.terraform.io/-/aws"
  1. regionを指定しろと怒られる
provider.aws.region
  The region where AWS operations will take place. Examples
  are us-east-1, us-west-2, etc.
  Enter a value: 
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

providerのregionの指定の仕方が変わっていたこれを参考に書き直しました。

参考手順

Upgrading to Terraform v0.13 Terraform AWS Provider Version 3 Upgrade Guide Terraform Google Provider 3.0.0 Upgrade Guide

CNDT2020で発表してきた

CloudNativeDays2020で以下のタイトルで発表してきました。

Amebaアフィリエイト基盤のGKEアーキテクチャとマイクロサービス | CloudNative Days Tokyo 2020

会社のスポンサー枠ということで40分の発表枠を頂きました。 無論40分話せる自信がないので、サーバサイドの @youta1119 君に無理言って一緒に発表してもらいました。

資料はこちら

会社の名前を出しての発表とオンラインの発表が共に初なので、結構発表を準備して望みました。 オンラインの発表とか大変そう〜とか思っていたぐらいなので、発表することになると決まってすぐにマイクをポチりました。

今まで運用構築請負業務をやってきたので、 AmebaPickは自分が担当した自社サービスということで 割と思い入れのあるサービスなので発表する機会を頂けたのは幸運でした。 社内のKubernates自信ニキが多数いて、色々構成を相談してできたものなので、 世の中に公開できて、供養できてのは本当によかったです。

発表の元ネタ的にはいくつかの会社の発表資料を参考にしたりしてたりします。 伝えたかったこととしては、システムの価値を維持・向上し続けていくぞ!でした。 あえて言わなかったこともあるし、もっと言いたかったこともありますので、 今後も継続的にシステム改善できたら、どっかで言いたいです。

発表内容の後半のサービスや開発面についても語れるようになればSREにジョブチェンジできるのかもしれないなあと思って、語れるようにならんとなあというお気持ちでいっぱいです。

ほなまた

sendgridのeventdataをS3に送るやつです

構成

SendGridのエラーをS3に格納するfunctionを実行する構成です。

f:id:oza__shu:20200717200041p:plain

各サービスの役割とポイントとなる設定についてみていきましょう。 S3→Lambda→APIGateway→SendGridの順にみていきましょう〜〜〜

S3

  • eventdataの保存先にS3を使用します。bucketを用意してlifecycleで保存期間を設定して終わりです。

AWS Lambda

  • API Gatewayで受けたeventdataをS3に保存するために、AWS Lambdaを使います。公式ドキュメントをみるとイベントデータはJSON配列で送信されるようです。今回はPython3でfunctionを書きました。forで回して1件ずつ処理してS3に格納していきます。内容をざっと見ると、timestampのunixstampをJSTに変換しログ名にもいれて、dict型からstr型bytes型に変換しioモジュールでファイルに書き出し、それをboto3でアップロードさせました。Lambda関数のコード抜粋を載せます。
# configure with env vars
BUCKET_NAME = os.environ['LOG_S3_BUCKET']

def put_to_s3(data: dict, bucket: str, key: str):
    xray_recorder.begin_subsegment('s3 upload')
    strdata = json.dumps(data)
    bindata = strdata.encode()
    try:
        with io.BytesIO(bindata) as data_fileobj:
            s3_results = s3.upload_fileobj(data_fileobj, bucket, key)

        logger.info(f"S3 upload errors: {s3_results}")

    except S3UploadFailedError as e:
        logger.error("Upload failed. Error:")
        logger.error(e)
        import traceback
        traceback.print_stack()
        raise
    xray_recorder.end_subsegment()

def handler(event, context):
    logger.info(event)
    data_list = event['body']
    data_dicts = json.loads(data_list)
    logger.info(data_dicts)
    for _, data in enumerate(data_dicts):
        unix_timestamp = data['timestamp']
        jst_time = datetime.fromtimestamp(unix_timestamp)
        key = data['event'] + "/" + jst_time.strftime("%Y-%m/%d/%H/%Y-%m-%d-%H:%M:%S-") + "-" + data['sg_event_id'] + ".log"
        put_to_s3(data, BUCKET_NAME, key)

eventに何が受け取るのか最初わからずハマりました。文字列がきていたのでjson.loadsでdictに変更して値がとれるようになりました。 Lambda関数には、S3へのUpload権限を与えるのを忘れないようにしてください。

公式ドキュメントのここらへんが参考になりそう。

functionのzip化はarchive_fileを使うでもいいと思います。

Pythonのコードはとりあえず、ここらへんから参考に育てていきました。あとここらへんとかも。

API Gateway

  • Event WebhookのHTTPリクエストをLambda Proxyを経由してAPI Gatewayで受けます。API Gatewayはリソースを作成後、ANYメソッドを作成します。プロキシリソースとの Lambda プロキシ統合を参考に設定します。設定内容としては、greedy パス変数 {proxy+} を使用してプロキシリソースを作成します。そしてプロキシリソースに ANY メソッドを設定します。API Gateway REST APIAWS_PROXYで指定されるLambda プロキシ統合は、バックエンドの Lambda 関数と統合するために使用します。Lambda プロキシ統合を使うと何がよいかなのですが、API Gatewayがリクエストとレスポンスのマッピング設定をよしなに設定してくれるので、マッピングテンプレートを書かなくなることです。以上でAPI Gatewayの設定は完了です。最後にAPIをterraformでapplyしてデプロイしてAPI Gatewayの設定は完了です。こっちのドキュメントも参照した方がいいと思います。 この記事も見やすかったです。

SendGrid

  • Event WebhookはSendGridダッシュボードの「Settings > Mail Settings > Event Notification」で設定します。HTTP POST URLAPI GatewayのエンドポイントのURLを設定します。SELECT ACTIONSで受け取りたいイベントのチェックボックスをONにして設定を保存します。今回はエラーを受け取りたいのでDropped, Deffered, Bouncedに✅を入れます。Event Notification設定画面で「Test Your Integration」ボタンを選択して、S3にテストデータが保存されていることが確認できれば設定は完了です。ここらへんに書いてあります。

まとめ

いかがでしたか?SendgridのeventdataをAWSを利用して保存する方法をみてきました。S3は耐障害性が高く、Lifecycleでログ保存期間についても簡単に設定することができます。今回の構成をeventdataの保存を考える際に参考にしてみてください。

datadogのアラート通知先を環境毎に変えたい

環境毎にアラート項目を作り分けてて面倒くさかったので、一つの監視で複数環境を監視できるようにした。 環境毎に閾値を変えたいという要望は捨て置く。

datadogでタグを使って1モニターから複数の通知先を出し分け設定する こちらを参考にした。

ここでの想定は

  • envタグとかenviromentタグにprd,dev,stg.sbx,shd,lrd...などなど付いている
  • slackチャンネルが hoge_alert 部屋と hoge_dev_alert 部屋があり、slackのwebhookのurlをdatadogのslackのintegrationで設定し、通知可能であることです。

slackが落ちた時に大丈夫なように、prdのcriticalだけMail通知もすると安全そう

以下のように書けば、一つの監視で通知先を分けれるし、ステータス毎にメンションも分けられるのでおすすめです。

{{#is_alert}}  
Criticalだよ!
{{#is_match "env" "prd"}}  <!channel>  @slack-Slack_Account_Hook_PRD-hoge_alert {{/is_match}}
{{^is_match "env" "prd"}}  <!here>  @slack-Slack_Account_Hook_DEV-hoge_dev_alert  {{/is_match}}
{{/is_alert}}


{{#is_warning}}
WARNINGだよ。 
{{#is_match "env" "prd"}} <!here> @slack-Slack_Account_Hook_PRD-hoge_alert {{/is_match}}
{{^is_match "env" "prd"}} @slack-Slack_Account_Hook_DEV-hoge_dev_alert  {{/is_match}}
{{/is_warning}} 

{{#is_no_data}}
NoDataだよ!
{{#is_match "env" "prd"}} <!here>  @slack-Slack_Account_Hook_PRD-hoge_alert {{/is_match}}
{{^is_match "env" "prd"}} @slack-Slack_Account_Hook_DEV-hoge_dev_alert  {{/is_match}}
{{/is_no_data}} 

{{#is_recovery}}
復旧したよ!
{{#is_match "env" "prd"}} @slack-Slack_Account_Hook_PRD-hoge_alert {{/is_match}}
{{^is_match "env" "prd"}} @slack-Slack_Account_Hook_DEV-hoge_dev_alert  {{/is_match}}
{{/is_recovery}}

EKS環境に対してlocust podをskaffoldでデプロイして負荷試験準備した

EKSは別に関係ないです。 負荷試験環境を用意した時、skaffoldでlocust環境を用意したので、そのメモ。

事前準備

  1. docker daemonの起動とskaffoldのインストール
  2. ecrにimageを置くためのリポジトリを用意

Description

Projectのlocustを動かすmanifestとシナリオ群を用意する。

パッケージ構成はこんな感じになった

├── Dockerfile                    //locustのimageの元
├── README.md                     //説明書
├── src                           //シナリオ置き場
│   └── {scenario_name}.py        // シナリオ
├── scripts                       //locust起動script置き場
│   └── docker-entrypoint.sh
└── deployment                    // シナリオmanifest置き場
     ├── helm                     // シナリオ毎にpathを切ります
     │  ├── Chart.yaml            // locustで実行するスクリプト
     │  ├── templates             // 
     │  │  ├─ _helpers.tpl        // helmのhelper
     │  │  ├─ master-deploy.yaml  // locustのmasterのmanifest
     │  │  ├─ master-svc.yaml     // locustのmasterのserviceのmanifest
     │  │  ├─ worker-deploy.yaml  // locustのworkerのmanifest
     │  │  └- worker-hpa.yaml    // hpaのmanifest
     │  └── values.yaml           // locustのチャートの値
     ├── helm_vars                // シナリオ毎にpathを切ります
     │  └─ values.yaml            // locustのチャートの値のoverride用
     └── skaffold.yaml            // skaffoldのmanifest

locust

locust自体はこの方がやっていることをそのまま参考にしています

docker-entrypoint.shはこんな感じにした。

#!/usr/bin/env sh

if [ -z "${TARGET_URL}" ]; then
    echo "ERROR: TARGET_URL not configured" >&2
    exit 1
fi

LOCUST_MODE="${LOCUST_MODE:=standalone}"
_LOCUST_OPTS="-f ${LOCUSTFILE_PATH:-/locustfile.py} -H ${TARGET_URL}"

if [ "${LOCUST_MODE}" = "master" ]; then
    _LOCUST_OPTS="${_LOCUST_OPTS} --master"
elif [ "${LOCUST_MODE}" = "slave" ]; then
    if [ -z "${LOCUST_MASTER_HOST}" ]; then
        echo "ERROR: MASTER_HOST is empty. Slave mode requires a master" >&2
        exit 1
    fi

    _LOCUST_OPTS="${_LOCUST_OPTS} --slave --master-host=${LOCUST_MASTER_HOST} --master-port=${LOCUST_MASTER_PORT:-5557}"
fi

echo "Starting Locust in ${LOCUST_MODE} mode..."
echo "$ locust ${LOCUST_OPTS} ${_LOCUST_OPTS}"

exec locust ${LOCUST_OPTS} ${_LOCUST_OPTS}

Dockerfile はこんな感じ

ENTRYPOINT ["./docker-entrypoint.sh"] にしたらpermission errorで実行できなかった。

FROM python:3.8-alpine
RUN apk add --no-cache -U zeromq-dev ca-certificates
COPY requirements.txt /tmp
RUN apk add --no-cache -U --virtual build-deps libffi-dev g++ gcc linux-headers &&\
    pip install --upgrade pip &&\
    pip install -r /tmp/requirements.txt && \
    apk del build-deps
EXPOSE 443 5557 5558
WORKDIR /locust
COPY ./src src
COPY ./scripts/docker-entrypoint.sh .
ENTRYPOINT ["sh", "./docker-entrypoint.sh"]

locustのmasterとworkerのマニフェストはhelmで書いて、 サービス毎にシナリオを追加できるようにしました。

master-deploy.yaml の中身

skaffold.yaml

こんな感じにマニフェスト作成 profilesを使って、対象を分けた。

apiVersion: skaffold/v1
kind: Config

build:
  local: {}
  artifacts:
    - image: {{AWSアカウントID}}.dkr.ecr.ap-northeast-1.amazonaws.com/project/locust
      context: .
  tagPolicy:
    sha256: {}
portForward:

profiles:
  - name: project-locust1
    deploy:
      kubeContext: arn:aws:eks:ap-northeast-1:{{AWSアカウントID}}:cluster/{{クラスタ名}}
      helm:
        releases:
          - name: project-locust1
            namespace: locust-test
            chartPath: ./deployment/helm
            valuesFiles:
              - ./deployment/helm_vars/locust1-values.yaml
            values:
              image: {{AWSアカウントID}}.dkr.ecr.ap-northeast-1.amazonaws.com/project/locust
            imageStrategy:
              helm: {}
  - name: project-locust2
    deploy:
      kubeContext: arn:aws:eks:ap-northeast-1:{{AWSアカウントID}}:cluster/{{クラスタ名}}
      helm:
        releases:
          - name: project-locust2
            namespace: locust-test
            chartPath: ./deployment/helm
            valuesFiles:
              - ./deployment/helm_vars/locust2-values.yaml
            values:
              image: {{AWSアカウントID}}.dkr.ecr.ap-northeast-1.amazonaws.com/project/locust
            imageStrategy:
              helm: {}
    portForward:
      - resourceType: service
        resourceName: locust2-master-svc
        namespace: locust-test
        port: 8089

Usage

deployとdeleteはこんな感じ。 -p でprofileを指定して、locustのターゲットを指定した。

## deploy
$ skaffold run -f deployment/skaffold.yaml -p project-locust1

## delete
$ skaffold delete -f deployment/skaffold.yaml -p project-locust1

新規にシナリオを追加したい時

以下の手順で増やしていけばいいだけ。

  1. src 配下にシナリオを追加
  2. helm_vars 配下にシナリオごとに変更したい値をoverrideさせるyamlを書く
  3. skaffold.yamlprofiles に追加したいシナリオの情報を追記する

番外編

aws2コマンドにしたらaws ecrへのloginコマンドが変わっていた。 以下でイケました。

 aws ecr get-login-password \
              --region {{リージョン名}} \
          | docker login \
              --username AWS \
              --password-stdin {{AWSアカウントID}}.dkr.ecr.ap-northeast-1.amazonaws.com

参考URL

【AWS】初めてのECR

今度はあんまりゴツくない!?「わりとゴツいKubernetesハンズオン」そのあとに

[アップデート]AWS CLI v2 で $ aws ecr get-login を使うときの注意点

Locustを触ってみた

Google発のコンテナアプリケーション開発支援ツール「Skaffold」や「Kaniko」を使ってみる

skaffold run

Kubernetesのアプリケーション開発で楽をしたい。そうだ、Skaffoldを使ってみよう!

/docker-entrypoint.sh": permission denied