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
Terraformの基本
terraformの基本事項についてaws providerを使用して確認していく。 terraformコマンドで構築する前にクレデンシャル情報を環境変数に渡しておくのを忘れずに。
export AWS_ACCESS_KEY_ID=hogehoge export AWS_SECRET_ACCESS_KEY=hogehoge export AWS_DEFAULT_REGION=hogehoge
コマンド
terraform init
はプロバイダ用のバイナリをダウンロードする。
terraform plan
はdry-run
terraform apply
でplanの内容を実行する
要注意メッセージ
リソースの再作成
# aws_instance.example must be replaced
例えば、awsのproviderでinstanceを作っていたとして、
何かtfファイルを編集したらインスタンスが再作成されてしまったなんてことが起きるので注意。
destroyも要確認。
Terraform の構成要素
- 変数
variableで変数の定義ができる。
variable "example_instance_type" { default = "t3.micro" } resource "aws_instance" "example" { ami = "ami-0f9ae750e8274075b" instance_type = var.example_instance_type }
locals
でローカル変数が定義できる。
variableはコマンド実行時に変数を上書きできるがlocalsは上書きできない違いがある。
- output
output
で値を出力することができる
apply時にターミナル上で値が確認できるようになる。
他にはmoduleから値を取得する時に使う。
variable "example_instance_type" { default = "t3.micro" } resource "aws_instance" "example" { ami = "ami-0f9ae750e8274075b" instance_type = var.example_instance_type } output "example_instance_id" { value = aws_instance.example.id }
applyすると、実行結果に、作成されたインスタンスの ID が出力される。
Outputs: example_instance_id = i-090d4a8d3ec3fac74
- データソース
データソースを使うと外部データを参照できる。
最新のAmazonLinux2のAMIを以下のように定義して参照してみる、
filter
などを使って検索条件を指定し、most_recent
で最新のAMIを取得している。
data "aws_ami" "recent_amazon_linux_2" { most_recent = true owners = ["amazon"] filter { name = "name" values = ["amzn2-ami-hvm-2.0.????????-x86_64-gp2"] } filter { name = "state" variables = ["abailable"] } } resource "aws_instance" "example" { ami = data.aws_ami.recent_amazon_linux_2.image_id instance_type = "t3.micro" }
- provider
AWS、GCP、Azure、Openstackなど構築する際、そのAPIの違いを吸収するものがプロバイダ。
リージョンの定義をaws mojuleを使ってするとこうなる。
provider "aws" { region = "ap-northeast-1" }
Interpolation Syntax
- 参照
EC2 向けセキュリティグループの定義
80 番ポートを許可すると以下 ※接続元のIPアドレスを制限していないです
resource "aws_security_group" "example_ec2" { name = "example-ec2" ingress { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] }
vpc_security_group_ids
からセキュリティグループへの参照を追加し、EC2インスタンスと紐づけます。
vpc_security_group_ids
はリスト形式で渡すため、値を []
で囲んでいます。
TYPE.NAME.ATTRIBUTE
の形式で他のリソースの値を参照できます。
variable "example_instance_type" { default = "t3.micro" } resource "aws_instance" "example" { ami = "ami-0f9ae750e8274075b" instance_type = var.example_instance_type vpc_security_group_ids = [aws_security_group.example_ec2.id] user_data = <<EOF #!/bin/bash yum install -y httpd systemctl start httpd.service EOF } output "example_public_dns" { value = aws_instance.example.public_dns }
- 条件分岐
Terraform では、三項演算子が使える。 本番環境と開発環境でインスタンスタイプを切り替えたい時。
variable "env" {} resource "aws_instance" "example" { ami = "ami-0f9ae750e8274075b" instance_type = var.env == "prod" ? "m5.large" : "t3.micro" }
env変数をTerrafor 実行時に切り替えると、plan 結果が変わる。
$ terraform plan -var 'env=prod' $ terraform plan -var 'env=dev'
ただ環境を分けたい時はディレクトリで分けたりしてもよいかな。
- 組み込み関数
Terraform には、文字列操作やリスト操作、よくある処理が組み込み関数として提供されている。 外部ファイルを読み込む file 関数を使ってみる。
ユーザデータを user_data.sh
としてスクリプトで外に出す。
main.tf
ファイルと同じディレクトリに置く。
#!/bin/bash yum install -y httpd systemctl start httpd.service
以下でapply すると、user_data.sh
ファイルを読み込んでくれる。
resource "aws_instance" "example" { ami = "ami-0f9ae750e8274075b" instance_type = "t3.micro" user_data = file("./user_data.sh") }
- テンプレート
Terraform には、実行時に値を埋め込むテンプレート機能がある。
user_data.sh
を user_data.sh.tpl
とテンプレートファイル化してみる。
インストールパッケージを差し替えられるように、「package」変数を定義する。
#!/bin/sh yum install -y ${package} systemctl start ${package}.service
これを利用するために template_file
データソースを定義する
template
に、テンプレートファイルのパスを指定する
vars 句を記述すると、テンプレートの変数に値を代入できる
data "template_file" "httpd_user_data" { template = file("./user_data.sh.tpl") vars = { package = "httpd" } }
template_file
データソースを参照する
data.template_file.httpd_user_data.rendered
のように記述することで、テンプレートに変数を埋め込んだ結果を取得できる
resource "aws_instance" "example" { ami = "ami-0f9ae750e8274075b" instance_type = "t3.micro" user_data = data.template_file.httpd_user_data.rendered }
tfstateファイル
Terraform が変更した差分を検出して、必要な部分だけ変更できることが確認できた。
この判断をtfstateファイルで行っている。
tfstate ファイルは Terraform が生成するファイルで、現在の状態が記録されている。
Terraform は tfstate ファイルと、HCL で記述されたコードの内容に差分があれば、その差分のみを変更するよう振る舞う。
terraform.tfstate
ファイルは terraform apply
を実行していれば作成される。
中身を見るとJSON文字列に、現在の状態が記述されているのがわかる。
tfstateファイルは terraform apply
を実行したローカルに保存されるが、
これだとチーム開発したときに、他の人にtfstateファイルがないことになってしまい、
全リソース再作成が起きるおそれがあるので、リモートのストレージを、バックエンドとして利用しましょう。AWSならS3などを利用しましょう。
tfstateの格納先が記載されたファイル init.tf
を作り、事前作成したバケットを指定します。
terraform { backend "s3" { bucket = "tfstate-pragmatic-terraform-on-aws" key = "example/terraform.tfstate" region = "ap-northeast-1" } }
リモートのバックエンドとして使用するS3バケットには、バージョニング設定をすることが強く推奨されます。 S3に保存すれば、tfstateファイルから、いつでも以前の状態に戻せるようにもなります。 また、DynamoDB と組み合わせると、ロックも可能
リソースの削除
terraform destroy
でリソース削除できます。実行には注意しましょう。
モジュール
Terraform にもモジュール化の仕組みがあります。
モジュールは別ディレクトリにする必要があるので、まずは modules
ディレクトリを 作成します。
そして、モジュールを定義する main.tf
ファイルを作成します。
利用する側を resources
ディレクトリなどにして、そこに環境ごとにpathを切ってmain.tfを置けば
modules
配下のmoduleを環境毎に再利用することも可能になります。
- モジュールの定義
http_server モジュールを実装します。
Apache をインストール した EC2 インスタンスと、80 番ポートを許可したセキュリティグループを定義してみます。
http_server モジュールのインタフェースは次のとおりです。
- 入力パラメータ instance_type
- EC2 のインスタンスタイプ
- 出力パラメータ public_dns
- EC2 のパブリック DNS
variable "instance_type" {} resource "aws_instance" "default" { ami = "ami-0f9ae750e8274075b" vpc_security_group_ids = [aws_security_group.default.id] instance_type = var.instance_type user_data = <<EOF #!/bin/bash yum install -y httpd systemctl start httpd.service EOF } resource "aws_security_group" "default" { name = "ec2" ingress { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } output "public_dns" { value = aws_instance.default.public_dns }
- モジュールの利用
モジュール利用側の main.tf ファイルを以下のように実装します。
利用するモジュールは source
に指定します。
module "dev_server" { source = "./http_server" instance_type = "t3.micro" } output "public_dns" { value = module.dev_server.public_dns }
apply はモジュール利用側のディレクトリで実行します。
ただし、モジュールを使用する場合、もうひと手間必要です。
terraform get
コマンドか terraform init
コマンドを実行して、モジュールを事前に取得しておく必要があります。
terraform init
を実行するスクリプトを作成して、circle ciなどを利用してinitで失敗したらbuildが失敗するようにする運用がいいと思う。
GKE + datadogの監視の仕組みをhelmfileを使っての下準備
GKE + datadogの監視をするのにhelmfileを使ってdatadog-agentを入れるのと、 helmfileをCircle CIで回してパッケージ管理の準備までしてしまおうというもの。
Integrationの有効化
Datadogなので、まずはintegrationの有効化 必要に応じてだけど、代替以下
Docker Google Cloud Platform Google Container Engine Google Compute Engine Google CloudSQL Google Cloud Pubsub Google Cloud Storage kubernates
こちら参考 GCPのIntegrationを有効化するために サービスアカウント が必要なので作成しておく。
監視項目
監視したい内容は以下とする
- Kubernetes 上のアプリ/クラスタコンポーネントの監視
種類 | 監視対象 | 監視項目 | 監視方法 (使うメトリクス項目) |
---|---|---|---|
Work metrics | アプリの Pod (web, api, worker) | unavailable な Pod がいないことを確認 | kubernetes_state.deployment.replicas_unavailable |
Work metrics | アプリの Pod (web, api, worker) | Pod が restart loop に陥っていないことを確認 | "CrashLoopBackOff/n分間にn回以上再起動していないかどうか" |
Work metrics | kube-system namespace の Pods | unavailable な Pod がいないことを確認 | |
Work metrics | Kubernetes worker nodes | 全ての node status が Ready であることを確認 | check: kubernetes_state.node.ready |
Work metrics | Kubernetes worker nodes | 全ての node が schedulable であることを確認 | "metrics: kubernetes_state.node.status/tag: status:schedulable == 0" |
Resources metrics | Kubernetes worker nodes | node のリソースのキャパシティに余裕があることを確認 | "kube_node_status_capacity_cpu_cores/kube_node_status_capacity_memory_bytes" |
- GCEインスタンスの監視
種類 | 監視対象 | 監視項目 | 監視方法 (使うメトリクス項目) |
---|---|---|---|
Resources metrics | Kubernetes worker nodes | Keepalive (Connectivity) 監視 | 従来の VM 運用と同じやり方 |
Resources metrics | Kubernetes worker nodes | CPU Usage | 従来の VM 運用と同じやり方 |
Resources metrics | Kubernetes worker nodes | Memory Usage | 従来の VM 運用と同じやり方 |
Resources metrics | Kubernetes worker nodes | Disk Usage | 従来の VM 運用と同じやり方 |
- Datadog Dashboard に出しておきたい項目
項目 | メトリクス |
---|---|
CPU core 使用率 | kubernetes.cpu.usage.total / kubernetes.cpu.capacity |
Memory 使用率 | kubernetes.memory.usage / kubernetes.memory.capacity |
Disk 使用率 | kubernetes.filesystem.usage_pct |
CPU reqests/limits | kubernetes.cpu.requests / kubernetes.cpu.limits |
Mem reqests/limits | kubernetes.memory.requests / kubernetes.memory.limits |
DatadogのAgentの用意
Monitoring Kubernetes with Datadog How to monitor Google Kubernetes Engine with Datadog
datadogの記事をみるとGKEなどのkubernatesをdatadogでモニタリングするには各ノードにdatadog-agentをdeamonsetで常駐させるのが一般的らしい。 Pod間通信でメトリクスを取ってくるようです。
以下のようなものを用意して、 kubectl create -f datadog-agent.yaml
で当てる感じ。
datadogのkubernatesのintegrationのページにも書いてありまする。
TIPS
- DaemonSetのAPIversionはKubernetesバージョン1.9以降の場合は、
apps/v1
を使用 apps/v1
ではspec.selectorが必要になります。- 他のpodからcustom metricsをDogStatsD経由で送信するため、8125/UDPをHost(ノード)に公開します
- Traceの機能も同様に他のpodから使いたいため8126/TCPをHost(ノード)に開ける。
- API_KEYはSecretから取得。作成コマンドは以下参照。
kubectl create secret generic datadog-secret --from-literal=api_key=*****
apiVersion: extensions/v1beta1 kind: DaemonSet metadata: name: datadog-agent spec: selector: matchLabels: name: datadog-agent template: metadata: labels: app: datadog-agent name: datadog-agent spec: serviceAccountName: datadog-agent containers: - image: datadog/agent:latest imagePullPolicy: Always name: datadog-agent ports: - containerPort: 8125 # Custom metrics via DogStatsD - uncomment this section to enable custom metrics collection # hostPort: 8125 name: dogstatsdport protocol: UDP - containerPort: 8126 # Trace Collection (APM) - uncomment this section to enable APM # hostPort: 8126 name: traceport protocol: TCP env: - name: DD_API_KEY valueFrom: secretKeyRef: name: datadog-secret key: api-key - name: DD_COLLECT_KUBERNETES_EVENTS value: "true" - name: DD_LEADER_ELECTION value: "true" - name: KUBERNETES value: "true" - name: DD_KUBERNETES_KUBELET_HOST valueFrom: fieldRef: fieldPath: status.hostIP - name: DD_APM_ENABLED value: "true" resources: requests: memory: "256Mi" cpu: "200m" limits: memory: "256Mi" cpu: "200m" volumeMounts: - name: dockersocket mountPath: /var/run/docker.sock - name: procdir mountPath: /host/proc readOnly: true - name: cgroups mountPath: /host/sys/fs/cgroup readOnly: true livenessProbe: exec: command: - ./probe.sh initialDelaySeconds: 15 periodSeconds: 5 volumes: - hostPath: path: /var/run/docker.sock name: dockersocket - hostPath: path: /proc name: procdir - hostPath: path: /sys/fs/cgroup name: cgroups
Kubernetesのintegrationをみると manifestを書いて、それにk8sクラスタに当てる必要がある。 manifest管理をどうするかの問題が発生するが、今回はhelmを使ってインストールをする。
helm + helmfileを用いたdatadog-agentインストール
ますhelmとはK8sのパッケージ管理ツール。
リポジトリに登録された構成情報(Chart)を、 installコマンドに適時引数で設定情報を与え、簡単に自分のクラスタに導入(Release)することができる。
そしてhelmfileはk8sクラスタのhelm releasesを管理してくれるもの。
helmfile.yaml
にデプロイしたいhelm Chartを書いて、 helmfile sync
を実行するとインストールやアップグレードを gemfile
のように冪等に管理してくれるもの。
datadog-agentをクラスタに配置する場合は以下のYAMLを書くだけ。
- datadogのhelm chart
prd.values.yaml
にdatadog-apiキーを入れて読み込ませる
releases: - name: datadog namespace: kube-system chart: stable/datadog version: 1.27.2 values: - ./datadog/prd.values.yaml
Circle CIでhelmインストールする
GKEなので、kubectl,helm,helmfileをインストールして、 gcloudコマンドでログインして、クラスタのクレデンシャルとって、helmを当てていく流れ。
以下はCircle CI側の環境変数に入れておく。
COOGLE_CLOUD_REGION
GCLOUD_SERVICE_KEY
GOOGLE_PROJECT_ID
version: 2 defaults: &defaults working_directory: ~/hogehoge docker: - image: google/cloud-sdk:242.0.0-slim global_env: &global_env GOOGLE_PROJECT_ID: hogehoge COOGLE_CLOUD_REGION: asia-northeast1 K8S_CLUSTER: hogehoge common_steps: install_base_packages: &install_base_packages command: apt-get update && apt-get install wget kubectl install_helm: &install_helm name: install helm command: | # install helm wget https://storage.googleapis.com/kubernetes-helm/helm-v2.13.1-linux-amd64.tar.gz tar zxvf helm-v2.13.1-linux-amd64.tar.gz mv ./linux-amd64/helm /usr/local/bin/helm helm init --client-only helm plugin install https://github.com/databus23/helm-diff # install helmfile wget https://github.com/roboll/helmfile/releases/download/v0.54.2/helmfile_linux_amd64 chmod +x ./helmfile_linux_amd64 mv ./helmfile_linux_amd64 /usr/local/bin/helmfile gcloud_login: &gcloud_login name: gcloud login command: | echo ${GCLOUD_SERVICE_KEY} | base64 -d | gcloud auth activate-service-account --key-file=- gcloud --quiet config set project ${GOOGLE_PROJECT_ID} setup_cluster_and_helm: &setup_cluster_and_helm name: setup cluster credential and helm environment command: | gcloud beta container clusters get-credentials ${K8S_CLUSTER} --region ${COOGLE_CLOUD_REGION} --project ${GOOGLE_PROJECT_ID} helm repo update jobs: helmfile-sync-dry-run: <<: *defaults environment: <<: *global_env steps: - checkout - run: *install_base_packages - run: *install_helm - run: *gcloud_login - run: *setup_cluster_and_helm - run: name: helmfile diff command: helmfile --file prd.helmfile.yaml diff - run: name: helmfile sync dry-run command: helmfile --file prd.helmfile.yaml sync --args --dry-run helmfile-sync: <<: *defaults environment: <<: *global_env steps: - checkout - run: *install_base_packages - run: *install_helm - run: *gcloud_login - run: *setup_cluster_and_helm - run: name: helmfile sync command: | helmfile --file prd.helmfile.yaml sync workflows: version: 2 dry-run-and-sync: jobs: - helmfile-sync-dry-run: filters: branches: only: /.*/ - helmfile-sync: requires: - helmfile-sync-dry-run filters: branches: only: master
- サービスアカウントを使用してアクセス承認
gcloud
コマンドライン ツールでアクティブなプロジェクトを設定gcloud --quiet config set project ${GOOGLE_PROJECT_ID}
gcloud
は対話型インターフェースなので、自動化には邪魔なので、プロンプトの無効化を--quiet
でする。--quiet
フラグは先頭の項目の右側に挿入する。
- クラスタの認証情報を取得する
gcloud beta container clusters get-credentials [CLUSTER_NAME] [--internal-ip] [--region=REGION | --zone=ZONE, -z ZONE] [GCLOUD_WIDE_FLAG …]
でクラスタに対して認証を行う。- gcloud beta container clusters get-credentialsとは
- Helm CLI を初期設定
helm init --client-only
ちなみにインターネット環境がない場合は--skip-refresh
- helmクライアント側でキャッシュしているので、最新のリポジトリ内の情報を得るために
helm repo update
を実行して、再取得することでキャッシュをリフレッシュさせる。 helm diff
でreleasesのdiffをとるhelmfile diff
サブコマンドは、マニフェストで定義されているすべてのcharts/releases
にわたってhelm-diffプラグインを実行。helmfile --file prd.helmfile.yaml diff
- 使うには helm-diffパッケージが必要。
helmfile --file prd.helmfile.yaml sync --args --dry-run
- Helmfileは実行中にさまざまなイベントをトリガーとする。 イベントが発生すると、argsを指定してコマンドを実行することで、関連するフックが実行される。
helmfile --file prd.helmfile.yaml sync
- helmfile syncサブコマンドは、helmfileの説明に従ってクラスタの状態を同期する。 デフォルトのhelmfileはhelmfile.yaml。任意のyamlファイルを
--file path/to/your/yaml/file
フラグを指定することで渡すことができる。 - helmfileはマニフェストで宣言されたreleaseごとに
helm upgrade --install
を実行。必要に応じて、secretsを復号化してhelmチャートの値として使用する。 また、指定されたチャートリポジトリを更新し、参照されているローカルチャートの依存関係も更新する。 helm upgrade --install
は releaseが存在するかどうかに応じてインストールまたはアップグレードすることができる。
- helmfile syncサブコマンドは、helmfileの説明に従ってクラスタの状態を同期する。 デフォルトのhelmfileはhelmfile.yaml。任意のyamlファイルを
これで、helmで Datadog agent のインストールができる。また、何かhelmで追加したくなったらhelmfileに書いていくことになる。
気になりンゴ
--dry-run
は実はまだ機能追加されてない?helpに特に出てこない。
--args
でhookさせているけど、helmfileにhookさせるものをかかないとhookされないのかな
releases: - name: myapp chart: mychart # *snip* hooks: - events: ["prepare", "cleanup"] command: "echo" args: ["{{`{{.Environment.Name}}`}}", "{{`{{.Release.Name}}`}}", "{{`{{.HelmfileCommand}}`}}\ "]