sendgridのeventdataをS3に送るやつです
構成
SendGridのエラーをS3に格納するfunctionを実行する構成です。
各サービスの役割とポイントとなる設定についてみていきましょう。 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のコードはとりあえず、ここらへんから参考に育てていきました。あとここらへんとかも。
- Event WebhookのHTTPリクエストをLambda Proxyを経由してAPI Gatewayで受けます。API Gatewayはリソースを作成後、ANYメソッドを作成します。プロキシリソースとの Lambda プロキシ統合を参考に設定します。設定内容としては、greedy パス変数 {proxy+} を使用してプロキシリソースを作成します。そしてプロキシリソースに ANY メソッドを設定します。API Gateway REST API の AWS_PROXYで指定されるLambda プロキシ統合は、バックエンドの Lambda 関数と統合するために使用します。Lambda プロキシ統合を使うと何がよいかなのですが、API Gatewayがリクエストとレスポンスのマッピング設定をよしなに設定してくれるので、マッピングテンプレートを書かなくなることです。以上でAPI Gatewayの設定は完了です。最後にAPIをterraformでapplyしてデプロイしてAPI Gatewayの設定は完了です。こっちのドキュメントも参照した方がいいと思います。 この記事も見やすかったです。
SendGrid
- Event WebhookはSendGridダッシュボードの「Settings > Mail Settings > Event Notification」で設定します。HTTP POST URLにAPI 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環境を用意したので、そのメモ。
事前準備
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
新規にシナリオを追加したい時
以下の手順で増やしていけばいいだけ。
src
配下にシナリオを追加helm_vars
配下にシナリオごとに変更したい値をoverrideさせるyamlを書くskaffold.yaml
のprofiles
に追加したいシナリオの情報を追記する
番外編
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
今度はあんまりゴツくない!?「わりとゴツいKubernetesハンズオン」そのあとに
[アップデート]AWS CLI v2 で $ aws ecr get-login を使うときの注意点
Google発のコンテナアプリケーション開発支援ツール「Skaffold」や「Kaniko」を使ってみる
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
ここで覚えるやつ
- f-string
- f-string他の使い方
- bin()
- 後置if文
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年ぶりくらいなので今年はもっと発表していきたい 💪
terraformerを使ってコード化対応
手動で管理していたCloudDNSをコード管理したかったので、terraformerを使ってコード化しました。
Installation
brew install terraformer
事前準備
コマンド実行したら以下エラーが出たので、ディレクトリにpluginをinstall
Copy your Terraform provider's plugin(s) to folder ~/.terraform.d/plugins/{darwin,linux}_amd64/, as appropriate.
mkdir -p ~/.terraform.d/plugins/darwin_amd64 cd ~/.terraform.d/plugins/darwin_amd64 wget https://releases.hashicorp.com/terraform-provider-google/2.17.0/terraform-provider-google_2.17.0_darwin_amd64.zip unzip terraform-provider-google_2.17.0_darwin_amd64.zip
Command実行
CloudDNSの以下リソースのコード化を実行
terraformer import google --resources=dns --projects=hogehoge --regions=asia-northeast1 2019/10/23 13:47:53 google importing project hogehoge region asia-northeast1 2019/10/23 13:47:54 google importing... dns ... 2019/10/23 13:48:00 google Connecting.... 2019/10/23 13:48:00 google save dns 2019/10/23 13:48:00 google save tfstate for dns
tfstateファイルとtfファイルが作成されていることを確認
ll generated/google/hogehoge/dns/asia-northeast1/ default.tfstate* dns_managed_zone.tf* dns_record_set.tf* outputs.tf* provider.tf*
GoでTravis-CIを使ってのテストとデプロイ
Goで何か書いた時にCIの設定をまとめてみた。
もっといい方法がありそうなので、ブラッシュアップしていきたい。
golangci-lint
でsyntaxチェックして goreleaser
でdeployの流れ。
.travis.yml
travisの設定は以下の用にする。
branches: ciの設定(ブランチがマスターブランチ以外にプッシュされた場合、CIは実行しないように)
notifications: slackの設定
jobs: lintチェックさせる。 ./bin/golangci-lint
pathに注意
deploy: deployはgoreleaserを使う
language: go branches: only: - master go: - 1.11.x env: - GO111MODULE=on git: depth: 1 before_script: - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s v1.13.2 install: true notifications: slack: secure: hogehoge jobs: include: - stage: lint script: - go get -v -t -d ./... - ./bin/golangci-lint run --config .golangci.yml - stage: test script: go test -v -race -cover ./... - stage: build script: go build deploy: - provider: script skip_cleanup: true script: curl -sL https://git.io/goreleaser | bash on: tags: true
.golangci.yml
golangci configは以下の内容で大体十分。
run: deadline: 5m linters: disable-all: true enable: - gofmt - golint - govet - staticcheck - unused - ineffassign - misspell - gocyclo - deadcode - typecheck
.goreleaser.yml
builds.binary: バイナリ名 release: githubのリポジトリのownerとnameを入れておきます。
before: hooks: - go mod download builds: - binary: hogehoge goos: - darwin - linux goarch: - amd64 changelog: filters: exclude: - .github - Merge pull request - Merge branch archive: format: tar.gz name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}" files: - README.md replacements: darwin: Darwin linux: Linux amd64: x86_64 release: github: owner: hogehoge name: hogehoge