Foreverly

メモ帳

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

Leetcodeやっていき(無料Easy編)

とりあえず無料のEasyをすべて解くのを目指す。 ちゃんとテストも書いていく。

771. Jewels and Stones

与えられた文字列の中から、対象の文字列が何個あるか数える問題。 forで一文字ずつcount()を使用して確認していく。 for文も通常の書き方より、リスト内包表記を使用した方が早いし、短い。 最後はsum()で合計を出せばよい。

class Solution:
    def numJewelsInStones(self, J: str, S: str) -> int:
        return sum(S.count(j) for j in J)

ここで覚えるやつ

1342. numberOfSteps

偶数なら2で割って、それ以外なら1で引いた数の合計を求める問題。

そのまま解いた。

class Solution:
    def numberOfSteps(self, num: int) -> int:
        i = 0
        while num > 0:
            if num % 2 == 0:
                num /= 2
                i += 1
            else:
                num -= 1
                i += 1
        return i

1108 Defanging an IP Address

該当の文字列を置き換えればよい問題。

class Solution:
    def defangIPaddr(self, address: str) -> str:
        return '[.]'.join(address.split('.'))

412 Fizz Buzz

fizzbuzzの結果をlistに追加していく問題。

class Solution:
    def fizzBuzz(self, n: int) -> str:
        result = []
        for i in range(1, n + 1):
            if (i % 3) == 0 and (i % 5) == 0:
                result.append("FizzBuzz")
            elif (i % 3) == 0:
                result.append("Fizz")
            elif (i % 5) == 0:
                result.append("Buzz")
            else:
                result.append(str(i))
        return result

1365 How Many Numbers Are Smaller Than the Current Number

sorted()でiterableの要素を並べ替えた新たなリストを返し、index()で要素番号を出力する。

class Solution:
    def smallerNumbersThanCurrent(self, nums: List[int]) -> List[int]:
        return [sorted(nums).index(i) for i in nums]

Envoy Meetup Tokyo #1でLTしてきた

Envoy Meetup Tokyo #1で、 「本番環境でEnvoyを導入するためにやったこと」というタイトルでLTしてきました。

発表資料はこちら

自分はCotrolPlaneを特に用意していないので、Envoyを気軽に導入したい人には参考になるかと思います。

Decksetで発表しようとしたら全表示されなかったので、急遽旧verのpdf化した資料で発表することになったのが、反省point。

会場のEnvoy利用者は1割程度な感じでした。 Istio利用者は数%程度だったのではないでしょうか。

また昨日の全体的な発表内容的にはAWS App MeshやIstioが多い印象だったので、 DataPlaneとしてのEnvoyについてというより、CotrolPlaneについての方が関心が多いのかなという印象をうけました。

Istioも1.5からIstiodが入ったりして、覚えるコンポーネントが少しは減ると思うので、そろそろ触ってみようかな。

外部の勉強会で発表とか2年ぶりくらいなので今年はもっと発表していきたい 💪