Foreverly

メモ帳

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 の構成要素

  1. 変数

variableで変数の定義ができる。

variable "example_instance_type" {
  default = "t3.micro"
}

resource "aws_instance" "example" {
  ami           = "ami-0f9ae750e8274075b"
  instance_type = var.example_instance_type
}

locals でローカル変数が定義できる。 variableはコマンド実行時に変数を上書きできるがlocalsは上書きできない違いがある。

  1. 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
  1. データソース

データソースを使うと外部データを参照できる。

最新の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"
}
  1. provider

AWSGCP、Azure、Openstackなど構築する際、そのAPIの違いを吸収するものがプロバイダ。

リージョンの定義をaws mojuleを使ってするとこうなる。

provider "aws" {
  region = "ap-northeast-1"
}

Interpolation Syntax

  1. 参照

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
}
  1. 条件分岐

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'

ただ環境を分けたい時はディレクトリで分けたりしてもよいかな。

  1. 組み込み関数

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")
}
  1. テンプレート

Terraform には、実行時に値を埋め込むテンプレート機能がある。 user_data.shuser_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を環境毎に再利用することも可能になります。

  1. モジュールの定義

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
}  
  1. モジュールの利用

モジュール利用側の 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を有効化するために サービスアカウント が必要なので作成しておく。

監視項目

監視したい内容は以下とする

種類 監視対象 監視項目 監視方法 (使うメトリクス項目)
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"
種類 監視対象 監視項目 監視方法 (使うメトリクス項目)
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)することができる。 そしてhelmfilek8sクラスタのhelm releasesを管理してくれるもの。 helmfile.yaml にデプロイしたいhelm Chartを書いて、 helmfile sync を実行するとインストールやアップグレードを gemfile のように冪等に管理してくれるもの。

datadog-agentをクラスタに配置する場合は以下のYAMLを書くだけ。

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 auth activate-service-account が正常に完了すると、gcloud initgcloud auth login と同様に、サービスアカウントの認証情報がローカルシステムに保存され、指定したアカウントがCloudSDKの構成のアクティブなアカウントに設定される。
    • サービスアカウントは事前に作成しておく(DatadogのGCPのintegrationするときに必要になる。)
    • --key-file=-- を渡すことで標準入力でキーを渡すことができる
  • gcloud コマンドライン ツールでアクティブなプロジェクトを設定
    • gcloud --quiet config set project ${GOOGLE_PROJECT_ID}
    • gcloud は対話型インターフェースなので、自動化には邪魔なので、プロンプトの無効化--quiet でする。 --quiet フラグは先頭の項目の右側に挿入する。
  • クラスタの認証情報を取得する
  • Helm CLI を初期設定
    • helm init --client-only ちなみにインターネット環境がない場合は --skip-refresh
  • helmクライアント側でキャッシュしているので、最新のリポジトリ内の情報を得るために helm repo update を実行して、再取得することでキャッシュをリフレッシュさせる。
  • helm diff でreleasesの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が存在するかどうかに応じてインストールまたはアップグレードすることができる。

これで、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}}`}}\
"]

Creating multi master cluster with kubeadm(ver1.14以上)

前回kubeadmでマルチマスターのクラスタ構築をしたが、kubeadm1.14で証明書の発行と配布がされるようになったので試してみた。

document

https://kubernetes.io/docs/setup/independent/install-kubeadm/ https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/ kubeadmのトラブルシューティング

始める前に

  1. 以下のいずれかを実行している1台以上のマシンを用意
  2. Ubuntu 16.04+
  3. CentOS 7
  4. マシンごとに2 GB以上のRAM(それ以下にすると、アプリケーション用のスペースが狭まる)
  5. 2 CPU以上
  6. クラスタ内のすべてのマシン間の完全なネットワーク接続(パブリックまたはプライベートネットワークは問題なし)
  7. ノードごとに固有のホスト名、MACアドレス、およびproduct_uuid。
  8. マシンでは特定のポートを開けとく
  9. スワップ無効(kubeletを正しく動作させるためにスワップを無効にする。)

今回の構成

ホスト名 役割
dev-proxy01 ロードバランサー(HAProxy)
dev-master01 MasterNode
dev-master02 MasterNode
dev-master03. MasterNode
dev-worker01 WorkerNode
dev-worker02 WorkerNode
dev-worker03 WorkerNode

クライアントに必要なもの

cfsslとkubectlをインストールする

HAProxyロードバランサーのインストール

3つのKubernetesマスターノードを配置するので、 トラフィックを分散させるためにそれらの前にHAPRoxyロードバランサーを配置する必要がある。

  1. LBにするサーバにSSHで接続します。
  2. OS update
  3. $ sudo yum update -y
  4. HAProxyをインストール
  5. $ sudo yum install haproxy
  6. 3つのKubernetesマスターノード間でトラフィックを負荷分散するようにHAProxyを設定。
$ sudo vim /etc/haproxy/haproxy.cfg
global
...
default
...
#---------------------------------------------------------------------
# main frontend which proxys to the backends
#---------------------------------------------------------------------
frontend kubernetes
    bind 10.64.20.226:6443
    option tcplog
    mode tcp
    default_backend kubernetes-master-nodes
#---------------------------------------------------------------------
# static backend for serving up images, stylesheets and such
#---------------------------------------------------------------------
backend static
    balance     roundrobin
    server      static 127.0.0.1:4331 check

#---------------------------------------------------------------------
# round robin balancing between the various backends
#---------------------------------------------------------------------
backend kubernetes-master-nodes
    mode tcp
    balance     roundrobin
    option tcp-check
    server  dev-master01 10.64.20.223:6443 check
    server  dev-master02 10.64.20.225:6443 check
    server  dev-master03 10.64.20.222:6443 check
  1. HAProxyを再起動。
$ sudo systemctl restart haproxy
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
exclude=kube*
EOF

# Set SELinux in permissive mode (effectively disabling it)
setenforce 0
sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config

yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes

systemctl enable --now kubelet

以下は各kubeノードで実行

sudo visudo で一般ユーザにroot権限を付与 sudo yum -y install vim

Dockerのインストール

最新の手順はこちら

$ sudo yum install -y yum-utils \
  device-mapper-persistent-data \
  lvm2
$ sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo
$ sudo yum install docker-ce docker-ce-cli containerd.io
$ sudo systemctl start docker & sudo systemctl enable docker

kubeadmのインストール

最新の手順はこちら

まず事前準備として、全ノードで Docker、CLI 等の関連パッケージのインストールと、 クラスタ内で利用するオーバーレイネットワーク用にカーネルパラメータを変更しておきます。

# 必要な依存パッケージのインストール
$ sudo yum -y update
# リポジトリの登録と更新
$ sudo su -
$ cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
exclude=kube*
EOF

# SELinux disabling it)
setenforce 0
sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config

# Kubernetes 関連パッケージのインストール
yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes

systemctl enable --now kubelet
# オーバーレイネットワーク用にカーネルパラメータを変更 
# RHEL / CentOS 7の一部のユーザーは、iptablesがバイパスされているために
# トラフィックが誤ってルーティングされるという問題を報告しています。
# あなたのnet.bridge.bridge-nf-call-iptables設定で1に設定されていることを確認する必要がある
cat <<EOF >  /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sysctl --system

kubeadmを使ったHAクラスタの作成

最新の手順はこちら

クラスターの初期化とkubeadmでTLS証明書を調査する

マスターノードに1台ログインして以下を実行

  1. ディレクトリを作成
mkdir /etc/kubernetes/kubeadm
  1. kubeadm-configを作成する。

  2. kubernetesVersion 使用するKubernetesバージョンに設定する必要があります。この例では stable を使用。

  3. controlPlaneEndpoint ロードバランサのアドレスまたはDNSとポートを一致させる必要があり。
  4. kubeadm、kubelet、kubectl およびKubernetesのバージョンを一致させることがお勧め。
  5. IPはproxyサーバのIPです。
$ vim /etc/kubernetes/kubeadm/kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1beta1
kind: ClusterConfiguration
kubernetesVersion: stable
# REPLACE with `loadbalancer` IP
controlPlaneEndpoint: "10.64.20.226:6443"
networking:
  podSubnet: 10.64.0.0/18
  1. 指定されたupload-certsと設定でクラスタを初期化します。
# kubeadm init \
     --config=/etc/kubernetes/kubeadm/kubeadm-config.yaml \
     --experimental-upload-certs

この出力をテキストファイルにコピーし。後で他のコントロールプレーンノードをクラスタに参加させるために必要になるのでメモ。

certificate-key は kubeadm certs secret の復号化を可能にするのでメモ。

You can now join any number of the control-plane node running the following command on each as root:

  kubeadm join 10.64.20.226:6443 --token ruf1ng.rcjtrdkczkto9t5d \
    --discovery-token-ca-cert-hash sha256:d17549120dec4b237497d95a7131b6387f00b1612b58be6341b7ea451f0939d0 \
    --experimental-control-plane --certificate-key d54f93a776345163398ec944fc67021517e96f11e3a59fb3d659ba5ab8f60b17
  1. 利用するユーザで以下を実行。 kubeconfigコマンドを実行する推奨のkubectlアクセス用。
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
  1. kube-systemネームスペースのkubeadm-certシークレットを調べる。 etcd、kube-apiserver、およびサービスアカウントの証明書が表示されます。
kubectl get secrets -n kube-system kubeadm-certs -o yaml
  1. ownerReferences 配下の、name を調べる。 これは一時的なkubeadmトークンと相関しています。 そのトークンが期限切れになると、このsecretも失効します。
  ownerReferences:
    - apiVersion: v1
    blockOwnerDeletion: true
    controller: true
    kind: Secret
    name: bootstrap-token-6o49ct
    uid: c6d15490-55b0-11e9-84b0-fa163e4b8c32
  1. kubeadm で利用可能なトークンリスト一覧を取得する。 6o49ct が上記のステップの所有者参照であることに注意してください。 これは結合トークンではなく、kubeadm-certsでttlを有効にするプロキシです。 参加するときには、依然として ruf1ngトークンを使用する必要があります。
$ kubeadm token list
TOKEN                     TTL       EXPIRES                     USAGES                   DESCRIPTION                                           EXTRA GROUPS
6o49ct.fxxdtesig28iohi5   1h        2019-04-03T12:35:34+09:00   <none>                   Proxy for managing TTL for the kubeadm-certs secret   <none>
ruf1ng.rcjtrdkczkto9t5d   23h       2019-04-04T10:35:35+09:00   authentication,signing   <none>                                                system:bootstrappers:kubeadm:default-node-token
  1. CNI-plugin である calico を 前のconfigで設定した podSubnet にマッチする podのCIDRでインストールする。
kubectl apply -f https://gist.githubusercontent.com/joshrosso/ed1f5ea5a2f47d86f536e9eee3f1a2c2/raw/dfd95b9230fb3f75543706f3a95989964f36b154/calico-3.5.yaml
  1. NodeのSTATUSが Ready であることを確認する。
$ kubectl get nodes
NAME                 STATUS   ROLES    AGE   VERSION
dev-master01   Ready    master   16m   v1.14.0
  1. kube-system PodがRunningであることを確認する。
$ kubectl get pods -n kube-system
NAME                                         READY   STATUS    RESTARTS   AGE
calico-node-blstp                            1/1     Running   0          13m
coredns-fb8b8dccf-7nmlh                      1/1     Running   0          16m
coredns-fb8b8dccf-jr9nc                      1/1     Running   0          16m
etcd-dev-master01                      1/1     Running   0          15m
kube-apiserver-dev-master01            1/1     Running   0          15m
kube-controller-manager-dev-master01   1/1     Running   0          15m
kube-proxy-sz8cz                             1/1     Running   0          16m
kube-scheduler-dev-master01            1/1     Running   0          15m

2台目のMaster Nodeを追加する

  1. メモっておいた以下のコマンドを実行する。
kubeadm join 10.64.20.226:6443 --token ruf1ng.rcjtrdkczkto9t5d \
    --discovery-token-ca-cert-hash sha256:d17549120dec4b237497d95a7131b6387f00b1612b58be6341b7ea451f0939d0 \
    --experimental-control-plane --certificate-key d54f93a776345163398ec944fc67021517e96f11e3a59fb3d659ba5ab8f60b17
  1. 上のコマンドが完了したあとは、2台目のMaster Nodeがいることを確認する
$ kubectl get nodes
NAME                 STATUS   ROLES    AGE   VERSION
dev-master01   Ready    master   39m   v1.14.0
dev-master02   Ready    <none>   15m   v1.14.0
  1. ROLESがnoneだったので、LABELを付与
$ kubectl label node dev-master02 node-role.kubernetes.io/master=
node/dev-master02 labeled
$ kubectl get nodes
NAME                 STATUS   ROLES    AGE   VERSION
dev-master01   Ready    master   41m   v1.14.0
dev-master02   Ready    master   16m   v1.14.0
  1. Podが作成されていることを確認する
$ kubectl get pods -n kube-system
NAME                                         READY   STATUS    RESTARTS   AGE
calico-node-blstp                            1/1     Running   0          38m
calico-node-vprt5                            1/1     Running   0          17m
coredns-fb8b8dccf-7nmlh                      1/1     Running   0          41m
coredns-fb8b8dccf-jr9nc                      1/1     Running   0          41m
etcd-dev-master01                      1/1     Running   0          41m
etcd-dev-master02                      1/1     Running   0          17m
kube-apiserver-dev-master01            1/1     Running   0          41m
kube-apiserver-dev-master02            1/1     Running   0          17m
kube-controller-manager-dev-master01   1/1     Running   1          40m
kube-controller-manager-dev-master02   1/1     Running   0          17m
kube-proxy-bdwgx                             1/1     Running   0          17m
kube-proxy-sz8cz                             1/1     Running   0          41m
kube-scheduler-dev-master01            1/1     Running   1          40m
kube-scheduler-dev-master02            1/1     Running   0          17m

新しいTokenを使って3台目のMaster Nodeを追加

ここで3番目のMaster Nodeを最後のMasterとして追加するが、 最初に既存のkubeadmトークンをすべて削除する。 新規で構築する場合は不要だが、これは、Kubernetesクラスターが既に実行されているときにマスターを追加する方法です。

  1. まず、動いているMasterから全てのTokenを表示
$ kubeadm token list
TOKEN                     TTL       EXPIRES                     USAGES                   DESCRIPTION                                           EXTRA GROUPS
6o49ct.fxxdtesig28iohi5   1h        2019-04-03T12:35:34+09:00   <none>                   Proxy for managing TTL for the kubeadm-certs secret   <none>
ruf1ng.rcjtrdkczkto9t5d   23h       2019-04-04T10:35:35+09:00   authentication,signing   <none>                                                system:bootstrappers:kubeadm:default-node-token
  1. 全ての存在するTokenを削除 kubeadm-certsシークレットの有効期限が切れて削除されたため、以前に記録されたjoinコマンドは機能しません。 暗号化キーは無効になり、joinトークンは機能しなくなります。
kubeadm token delete 6o49ct.fxxdtesig28iohi5
kubeadm token delete ruf1ng.rcjtrdkczkto9t5d

削除されたことを確認

$ kubeadm token list
TOKEN     TTL       EXPIRES   USAGES    DESCRIPTION   EXTRA GROUPS
  1. 10分のTTLで新しいトークンを作成します。 以下のIPはロードバランサーのホストを反映している必要があります。
$ kubeadm token create --ttl 10m --print-join-command
kubeadm join 10.64.20.226:6443 --token y5hs8z.nu2my2jhq44b74i5     --discovery-token-ca-cert-hash sha256:d17549120dec4b237497d95a7131b6387f00b1612b58be6341b7ea451f0939d0
  1. kubeadm initのupload-certsフェーズを実行。
$ sudo su -
# kubeadm init phase upload-certs --experimental-upload-certs
[upload-certs] Storing the certificates in ConfigMap "kubeadm-certs" in the "kube-system" Namespace
[upload-certs] Using certificate key:
d0e3299a47812ebe52497e4d47676786c39460b0e137844c87862059f5436f7d
  1. Masterに追加したいNodeにSSH接続し、前の手順の出力を使用して、コントロールプレーンへのjoinコマンドを実行します。
kubeadm join 10.64.20.226:6443 \
    --experimental-control-plane \
    --certificate-key d0e3299a47812ebe52497e4d47676786c39460b0e137844c87862059f5436f7d \
    --token y5hs8z.nu2my2jhq44b74i5 \
    --discovery-token-ca-cert-hash sha256:d17549120dec4b237497d95a7131b6387f00b1612b58be6341b7ea451f0939d0

以下は出力の抜粋

This node has joined the cluster and a new control plane instance was created:

* Certificate signing request was sent to apiserver and approval was received.
* The Kubelet was informed of the new secure connection details.
* Control plane (master) label and taint were applied to the new node.
* The Kubernetes control plane instances scaled up.
* A new etcd member was added to the local/stacked etcd cluster.

To start administering your cluster from this node, you need to run the following as a regular user:

        mkdir -p $HOME/.kube
        sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
        sudo chown $(id -u):$(id -g) $HOME/.kube/config

Run 'kubectl get nodes' to see this node join the cluster.

上の出力にあるように kubectl コマンドを実行できるようにする

$ kubectl get nodes
The connection to the server localhost:8080 was refused - did you specify the right host or port?
$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config

Master Nodeとして追加されたことを確認

$ kubectl get nodes
NAME                 STATUS   ROLES    AGE   VERSION
dev-master01   Ready    master   80m   v1.14.0
dev-master02   Ready    master   56m   v1.14.0
dev-master03   Ready    master   74s   v1.14.0

workerノードのクラスタ組み込み

さきほどの kubeadm join のコマンドを --experimental-control-planeをつけないで 実行。 1台ずつpodが組み込まれて完了したことを確認してから実行していくこと。

実行したら error execution phase preflight: unable to fetch the kubeadm-config ConfigMap: failed to get config map: Unauthorized とエラーが出た。

# kubeadm join 10.64.20.226:6443     --certificate-key d0e3299a47812ebe52497e4d47676786c39460b0e137844c87862059f5436f7d     --token y5hs8z.nu2my2jhq44b74i5     --discovery-token-ca-cert-hash sha256:d17549120dec4b237497d95a7131b6
387f00b1612b58be6341b7ea451f0939d0
[preflight] Running pre-flight checks
        [WARNING IsDockerSystemdCheck]: detected "cgroupfs" as the Docker cgroup driver. The recommended driver is "systemd". Please follow the guide at https://kubernetes.io/docs/setup/cri/
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'
error execution phase preflight: unable to fetch the kubeadm-config ConfigMap: failed to get config map: Unauthorized

この場合はTokenの期限が切れていることが原因

[root@dev-master03 ~]# kubeadm token list
TOKEN                     TTL         EXPIRES                     USAGES                   DESCRIPTION                                           EXTRA GROUPS
y5hs8z.nu2my2jhq44b74i5   <invalid>   2019-04-03T11:57:38+09:00   authentication,signing   <none>                                                system:bootstrappers:kubeadm:default-node-token
ym6spi.2e7jfvyaqvtg4wce   1h          2019-04-03T13:49:27+09:00   <none>                   Proxy for managing TTL for the kubeadm-certs secret   <none>

再度作り直す

[root@dev-master03 ~]# kubeadm token delete y5hs8z.nu2my2jhq44b74i5
bootstrap token with id "y5hs8z" deleted
[root@dev-master03 ~]# kubeadm token create --ttl 10m --print-join-command
kubeadm join 10.64.20.226:6443 --token 9drkxn.oyse1f63s715af4g     --discovery-token-ca-cert-hash sha256:d17549120dec4b237497d95a7131b6387f00b1612b58be6341b7ea451f0939d0
[root@dev-master03 ~]# kubeadm token list
TOKEN                     TTL       EXPIRES                     USAGES                   DESCRIPTION                                           EXTRA GROUPS
9drkxn.oyse1f63s715af4g   9m        2019-04-03T12:48:20+09:00   authentication,signing   <none>                                                system:bootstrappers:kubeadm:default-node-token
ym6spi.2e7jfvyaqvtg4wce   1h        2019-04-03T13:49:27+09:00   <none>                   Proxy for managing TTL for the kubeadm-certs secret   <none>

作り直したTokenを使用してjoinされることを確認

kubeadm join 10.64.20.226:6443 \
    --certificate-key d0e3299a47812ebe52497e4d47676786c39460b0e137844c87862059f5436f7d \
    --token 9drkxn.oyse1f63s715af4g \
    --discovery-token-ca-cert-hash sha256:d17549120dec4b237497d95a7131b6387f00b1612b58be6341b7ea451f0939d0

出力はこんな感じ

[preflight] Running pre-flight checks
        [WARNING IsDockerSystemdCheck]: detected "cgroupfs" as the Docker cgroup driver. The recommended driver is "systemd". Please follow the guide at https://kubernetes.io/docs/setup/cri/
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'
[kubelet-start] Downloading configuration for the kubelet from the "kubelet-config-1.14" ConfigMap in the kube-system namespace
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Activating the kubelet service
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...

This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

Nodeとして追加されていることを確認

$ kubectl get nodes
NAME                 STATUS   ROLES    AGE     VERSION
dev-master01   Ready    master   131m    v1.14.0
dev-master02   Ready    master   106m    v1.14.0
dev-master03   Ready    master   51m     v1.14.0
dev-worker01   Ready    <none>   6m53s   v1.14.0
dev-worker02   Ready    <none>   3m54s   v1.14.0
dev-worker03   Ready    <none>   3m42s   v1.14.0

ROLESがnoneなのでworkerと付与してもいい。 こんな感じで。

$ kubectl label node dev-worker01 node-role.kubernetes.io/worker=
node/dev-worker01 labeled
$ kubectl label node dev-worker02 node-role.kubernetes.io/worker=
node/dev-worker02 labeled
$ kubectl label node dev-worker03 node-role.kubernetes.io/worker=
node/dev-worker03 labeled
$ kubectl get nodes
NAME                 STATUS   ROLES    AGE    VERSION
dev-master01   Ready    master   158m   v1.14.0
dev-master02   Ready    master   134m   v1.14.0
dev-master03   Ready    master   79m    v1.14.0
dev-worker01   Ready    worker   34m    v1.14.0
dev-worker02   Ready    worker   31m    v1.14.0
dev-worker03   Ready    worker   31m    v1.14.0

etcdの冗長化はされないので、するなら別にしないとだめ。 みなさんどうされているか、よければおしえてください。

[hoge@dev-master01 ~]$ kubectl get componentstatus
NAME                 STATUS    MESSAGE             ERROR
scheduler            Healthy   ok
controller-manager   Healthy   ok
etcd-0               Healthy   {"health":"true"}

Terraform and CORS-Enabled AWS API Gateway and AWS WAF

この記事はAWS WAF と API Gateway endpoint で CORS を有効化について。

以下にsampleを載せてあります。

REST API リソースが API 独自のドメイン以外のドメインからリクエストを受け取る場合、 リソースの選択されたメソッドで Cross-Origin Resource Sharing (CORS) を有効にする必要があるので、

CORS 対応のために、今回は以下のレスポンスヘッダーを付与させます。

API GatewayREST APIの設定

WAFとAPI Gatewayをつなげるので、 endpoint_configurationの設定が必要になる。

    ## API Gateway - Rest API
    resource "aws_api_gateway_rest_api" "rest_api" {
      name                     = "${var.env}-${var.name}-api-gateway"
      description              = "${var.env}-${var.name}-api-gateway"
      minimum_compression_size = 0
    
      endpoint_configuration {
        types = ["REGIONAL"]
      }
    }

API GatewayのResourcesの設定

TerraformのProviderを参考に作成するだけで大丈夫。

    ## API Gateway - Resources
    resource "aws_api_gateway_resource" "resource" {
      depends_on  = ["aws_api_gateway_rest_api.rest_api"]
      rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}"
      parent_id   = "${aws_api_gateway_rest_api.rest_api.root_resource_id}"
      path_part   = "${var.name}"
    }

API GatewayのMethodの設定

response_modelsresponse_parameters をつける以外は

TerraformのProviderを参考に作成するだけで大丈夫。

CORS対応のために Access-Control-Allow-Originをレスポンスヘッダに付与させます。

    ## API Gateway - Method Setting
    # post
    resource "aws_api_gateway_method" "method_post" {
      depends_on       = ["aws_api_gateway_resource.resource"]
      rest_api_id      = "${aws_api_gateway_rest_api.rest_api.id}"
      resource_id      = "${aws_api_gateway_resource.resource.id}"
      http_method      = "POST"
      authorization    = "NONE"
      api_key_required = false
    
      request_parameters = {
        "method.request.querystring.key" = true
      }
    }
    
    resource "aws_api_gateway_method_response" "method_response_post_200" {
      depends_on  = ["aws_api_gateway_method.method_post"]
      rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}"
      resource_id = "${aws_api_gateway_resource.resource.id}"
      http_method = "${aws_api_gateway_method.method_post.http_method}"
      status_code = "200"
    
      response_models = {
        "application/json" = "Empty"
      }
    
      response_parameters {
        "method.response.header.Access-Control-Allow-Origin" = true
      }
    }
    
    resource "aws_api_gateway_method_response" "method_response_post_400" {
      depends_on  = ["aws_api_gateway_method.method_post"]
      rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}"
      resource_id = "${aws_api_gateway_resource.resource.id}"
      http_method = "${aws_api_gateway_method.method_post.http_method}"
      status_code = "400"
    
      response_models = {
        "application/json" = "Empty"
      }
    
      response_parameters {
        "method.response.header.Access-Control-Allow-Origin" = true
      }
    }

API GatewayのIntegrationの設定

  • ハマりポイント
    • selection_pattern が必要
    • aws_api_gateway_integration_responseaws_api_gateway_method_responseに依存する。
    ## API Gateway - Integration Setting
    resource "aws_api_gateway_integration" "integration_post" {
      depends_on              = ["aws_api_gateway_method.method_post"]
      rest_api_id             = "${aws_api_gateway_rest_api.rest_api.id}"
      resource_id             = "${aws_api_gateway_resource.resource.id}"
      http_method             = "${aws_api_gateway_method.method_post.http_method}"
      integration_http_method = "POST"
      type                    = "HTTP"
      uri                     = "${var.url}"
    }
    
    resource "aws_api_gateway_integration_response" "integration_response_post_200" {
      depends_on  = ["aws_api_gateway_method_response.method_response_post_200"]
      rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}"
      resource_id = "${aws_api_gateway_resource.resource.id}"
      http_method = "${aws_api_gateway_method.method_post.http_method}"
      status_code = "${aws_api_gateway_method_response.method_response_post_200.status_code}"
      selection_pattern = "200"
    
      response_templates = {
        "application/json" = ""
      }
    
      response_parameters = {
        "method.response.header.Access-Control-Allow-Origin" = "'*'"
      }
    }
    
    resource "aws_api_gateway_integration_response" "integration_response_post_400" {
      depends_on  = ["aws_api_gateway_method_response.method_response_post_400"]
      rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}"
      resource_id = "${aws_api_gateway_resource.resource.id}"
      http_method = "${aws_api_gateway_method.method_post.http_method}"
      status_code = "${aws_api_gateway_method_response.method_response_post_400.status_code}"
      selection_pattern = "4\\d{2}"
    
      response_templates = {
        "application/json" = ""
      }
    
      response_parameters = {
        "method.response.header.Access-Control-Allow-Origin" = "'*'"
      }
    }

API Gateway では、Mock 統合タイプで OPTIONS メソッドを設定して CORS を有効にし、 前述のレスポンスヘッダーをメソッドのレスポンスヘッダーとして返す。 200 レスポンスで Access-Control-Allow-Origin:'*request-originating server addresses*' ヘッダーを返すようにすること。 特定の request-originating server addresses の静的な値を * に置き換えることもできるけど 広範なサポートを有効にすることはよく考えること。任意のサーバーを指定可能なのでできるならその方がよいので。

今回では * をつけています。 今回はMock統合タイプでbackendがGoogle Formを例にしている。

Access-Control-Allow-Originを設定すれば CORS は有効化できます。

  • ハマりポイント
    • OPTIONS メソッドを使用するのを忘れない
    • selection_pattern が必要
    • aws_api_gateway_integration_responseaws_api_gateway_method_responseに依存する。
    # options
    resource "aws_api_gateway_method" "method_options" {
      depends_on       = ["aws_api_gateway_resource.resource"]
      rest_api_id      = "${aws_api_gateway_rest_api.rest_api.id}"
      resource_id      = "${aws_api_gateway_resource.resource.id}"
      http_method      = "OPTIONS"
      authorization    = "NONE"
      api_key_required = false
    
      request_parameters = {
        "method.request.querystring.key" = true
      }
    }
    
    resource "aws_api_gateway_method_response" "method_response_options_200" {
      depends_on  = ["aws_api_gateway_method.method_options"]
      rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}"
      resource_id = "${aws_api_gateway_resource.resource.id}"
      http_method = "${aws_api_gateway_method.method_options.http_method}"
      status_code = "200"
    
      response_models = {
        "application/json" = "Empty"
      }
    
      response_parameters {
        "method.response.header.Access-Control-Allow-Origin" = true
      }
    }
    
    resource "aws_api_gateway_method_response" "method_response_options_400" {
      depends_on  = ["aws_api_gateway_method.method_options"]
      rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}"
      resource_id = "${aws_api_gateway_resource.resource.id}"
      http_method = "${aws_api_gateway_method.method_options.http_method}"
      status_code = "400"
    
      response_models = {
        "application/json" = "Empty"
      }
    
      response_parameters {
        "method.response.header.Access-Control-Allow-Origin" = true
      }
    }
    
    resource "aws_api_gateway_integration" "integration_options" {
      depends_on              = ["aws_api_gateway_method.method_options"]
      rest_api_id             = "${aws_api_gateway_rest_api.rest_api.id}"
      resource_id             = "${aws_api_gateway_resource.resource.id}"
      http_method             = "${aws_api_gateway_method.method_options.http_method}"
      integration_http_method = "OPTIONS"
      type                    = "MOCK"
    }
    
    resource "aws_api_gateway_integration_response" "integration_response_options_200" {
      depends_on  = ["aws_api_gateway_method_response.method_response_options_200"]
      rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}"
      resource_id = "${aws_api_gateway_resource.resource.id}"
      http_method = "${aws_api_gateway_method.method_options.http_method}"
      status_code = "${aws_api_gateway_method_response.method_response_options_200.status_code}"
      selection_pattern = "200"
    
      response_templates = {
        "application/json" = ""
      }
    
      response_parameters = {
        "method.response.header.Access-Control-Allow-Origin" = "'*'"
      }
    }
    
    resource "aws_api_gateway_integration_response" "integration_response_options_400" {
      depends_on  = ["aws_api_gateway_method_response.method_response_options_400"]
      rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}"
      resource_id = "${aws_api_gateway_resource.resource.id}"
      http_method = "${aws_api_gateway_method.method_options.http_method}"
      status_code = "${aws_api_gateway_method_response.method_response_options_400.status_code}"
      selection_pattern = "4\\d{2}"
    
      response_templates = {
        "application/json" = ""
      }
    
      response_parameters = {
        "method.response.header.Access-Control-Allow-Origin" = "'*'"
      }
    }
  • ハマりポイント
    • aws_api_gateway_deploymenaws_api_gateway_stage は両方必要ない (stage-nameがconflict する)。こちら参照)
    ## API Gateway - Deployment
    resource "aws_api_gateway_deployment" "deployment_200" {
      depends_on = [
        "aws_api_gateway_integration.integration_post",
        "aws_api_gateway_integration_response.integration_response_post_200",
      ]
    
      rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}"
      stage_name  = "${var.env}"
    }
    
    resource "aws_api_gateway_method_settings" "method_settings_200" {
      depends_on  = ["aws_api_gateway_deployment.deployment_200"]
      rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}"
      stage_name  = "${var.env}"
      method_path = "*/*"
    
      settings {
        metrics_enabled = true
        logging_level   = "INFO"
      }
    }
    
    resource "aws_api_gateway_deployment" "deployment_400" {
      depends_on = [
        "aws_api_gateway_integration.integration_post",
        "aws_api_gateway_integration_response.integration_response_post_400",
      ]
    
      rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}"
      stage_name  = "${var.env}"
    }
    
    resource "aws_api_gateway_method_settings" "method_settings_400" {
      depends_on  = ["aws_api_gateway_deployment.deployment_400"]
      rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}"
      stage_name  = "${var.env}"
      method_path = "*/*"
    
      settings {
        metrics_enabled = true
        logging_level   = "INFO"
      }
    }

WAFのRuleはIP制限。

同一IPアドレスから5分間に2000(最低値)を超えたリクエストをトリガー。

WAFとAPI Gatewayを繋げたいので、

WAF ResourcesではなくWAF Regional Resoucesを使う必要がある。

  • ハマりポイント
    • metric_name は英数字のみなので -などは使わない。こちら参照)
    ## WAF RULE
    resource "aws_wafregional_rate_based_rule" "wafrule" {
      name        = "${var.env}-bov-form-protect-dos-rule"
      metric_name = "${var.env}bovrule"
    
      rate_key   = "IP"
      rate_limit = 2000
    }

WAF RULEに該当したらBLOCKさせる設定をする。

aws_wafregional_web_acl_association) ではまだ ALBしか公式サポートしていないようだけど、

API Gatewayでも繋ぐことができた。

WAFとAPI Gatewayの繋ぎこみの設定は web_acl_idresource_arn を書くことで可能。

resource_arn の書き方は AWSドキュメンテーションなど参照)

もしくはAssociateWebACLのドキュメンテーション)

公式サポートされてほしい)

  • ハマりポイント
    • wafregional_ipset はALBで使うproviderで今回はIP指定もないので使わない。こちら参照)
    • aws_wafregional_ipset の変わりに aws_wafregional_rate_based_rule を使用する
    ## WAF_ACL rate_based block over 2000 access
    
    resource "aws_wafregional_web_acl" "wafacl" {
      name        = "${var.env}-bov-form-protect-dos-acl"
      metric_name = "${var.env}BovFormAccess"
    
      default_action {
        type = "ALLOW"
      }
    
      rule {
        action {
          type = "BLOCK"
        }
    
        priority = 1
        rule_id  = "${aws_wafregional_rate_based_rule.wafrule.id}"
        type     = "RATE_BASED"
      }
    }
    
    resource "aws_wafregional_web_acl_association" "wafassosiation" {
      depends_on   = ["aws_wafregional_web_acl.wafacl"]
      resource_arn = "arn:aws:apigateway:ap-northeast-1::/restapis/${aws_api_gateway_rest_api.rest_api.id}/stages/${var.env}"
      web_acl_id   = "${aws_wafregional_web_acl.wafacl.id}"
    }