Foreverly

メモ帳

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}"
    }

AWS構築[Cloudfront+ELB+WEB+DB+S3]

AWSなれるために2018年ごろに手でポチポチ構築したときのメモ なので今は設定画面とか変わっているものがありそう [Cloudfront+ELB+WEB+DB+S3] 構成。 ほとんどの場合、Terraformで構築すると思うけど、 その前のAWS入門として一応残しておきます。

仕様と構成

サブネット構成

名前タグ VPC AZ IPv4 CIDR ブロック
subnet-hogehoge-training-ssh hog ap-northeast-b 172.31.hoge.0/24
subnet-hogehoge-training-web01 training ap-northeast-b 172.31.hoge.0/24
subnet-hogehoge-training-web02 training ap-northeast-c 172.31.hoge.0/24
subnet-hogehoge-training-db01 training ap-northeast-b 172.31.hoge.0/24
subnet-hogehoge-training-db02 training ap-northeast-c 172.31.hoge.0/24

IAMロール

  • IAMロール名
  • 権限
    • EBSスナップショット
    • S3ファイルアップロード権限

S3

サーバ構成

踏み台

  • OS
    • CentOS7(AWS Marketplaceから)
  • インスタンス対応
    • t2.micro
  • サブネット
  • 自動割当パブリックIP
    • 有効化
  • プライベートIPアドレス
  • ディスク
    • 初期値を利用
    • ボリュームタイプは汎用SSD(GP2)
  • タグ
  • セキュリティグループ
    • セキュリティグループ名:hoge-training-ssh
    • 設定:自分のNWからSSHのみ許可
  • キーペア
    • 新規作成
  • EIP
    • 必要
  • ホスト名

web

  • OS
    • CentOS7(AWS Marketplaceから)
  • インスタンス対応
    • t2.micro
  • サブネット
    • subnet-hoge-training-web01
    • subnet-hoge-training-web02
  • 自動割当パブリックIP
    • 有効化
  • プライベートIPアドレス
  • ディスク
    • 初期値を利用
    • ボリュームタイプは汎用SSD(GP2)
  • タグ
    • Name:hoge-training-web01
    • Name:hoge-training-web02
  • セキュリティグループ
    • セキュリティグループ名:hoge-training-web
    • 設定:踏み台のからSSHのみ許可,ELBからのHTTP(80)のみ許可
  • キーペア
    • 新規作成
  • EIP
    • 不要
  • ホスト名
    • hoge-training-web01
    • hoge-training-web02
  • ミドルウェア
    • Apache(yumにてインストールされる最新版)

ELB(ALB)

  • 名前
  • セキュリティグループ
    • セキュリティグループ名:hoge-training-elb
  • ルーティングの設定
    • ターゲットグループ名:hoge-training-elb
  • ターゲットの登録
    • webサーバ2台を登録

RDS(MySQL)

  • サブネットグループの作成
  • 開発/テストを洗濯
  • DBエンジンのバージョン
    • 最新版を選択
  • DBインスタンスの選択
    • db.t2.micro
  • マルチAZ配置
    • はい
  • DBインスタンス識別子
  • マスターユーザの名前
    • root
  • パスワード

  • VPC
    • training
  • サブネットグループ
    • subnet-hoge-training-db01
    • subnet-hoge-training-db02
  • パブリックアクセス可能
    • いいえ
  • VPCセキュリティグループ
    • タグ名: hoge-training-db
    • インバウンドルールの編集: webサーバのsubnetからのみ3306のアクセス許可
  • データベース名
  • バックアップウィンドウ
    • 開始時間20:00UTF
  • マイナーバージョン自動アップグレード
    • いいえ
  • メンテナンスウィンドウ
    • 開始日時:日曜日の21:00UTF

CloudFront

S3参照

Route53

hoge-trainigをサブドメインとして<ドメイン>に登録

流れ

  1. hoge-training.<ドメイン> にアクセスするとサイト表示閲覧可能にする。
    • ELB から 2 台に振り分けられている状態
    • index.html を設置
  2. RDS 作成
    • web サーバから mysql コマンドで接続可能
  3. CloudFront の設定
  4. プログラムの作成

手順

IAMロールの作成

ロールは永続的な権限(アクセスキー、シークレットアクセスキー)を保持するユーザーとは異なり、 一時的にAWSリソースへアクセス権限を付与する場合に使用します。

  • AWSリソースへの権限付与
  • クロスアカウントアクセス
    • 複数のAWSアカウント間のリソースを1つのIAMユーザアカウントで操作したい
  • IDフェデレーション
    • 社内のADやLDAPサーバに登録されているアカウントを使用して、AWSリソースにアクセスしたい場合
  • WEB IDフェデレーション
    • FBやGoogle等のアカウントを使用してAWSリソースにアクセスしたい場合

AWSコンソールのIAM→ロール→ロールの作成

EC2インスタンスだけがそのロールを取得することができるポリシーを選択 次のステップ:アクセス権限 をクリック ポリシーの作成 をクリック 後述の IAMポリシーの付与 に飛びます。

IAMユーザとグループ

今回は使わないがロールではなくユーザとグループを作成してポリシーを付与するのは以下手順

AWSコンソールのIAM→ユーザ→ユーザを追加 IAMユーザにアクセス権限を設定する場合、ユーザーに直接ポリシー付与する方法と、ポリシー付与したグループを作成して、ユーザーをグループに所属させる方法がある。 基本的にはIAMグループに対して、アクセス権限を設定するようにする。 グループの名前も管理者であることが想像できるものにするのがよい。

  • ユーザ名
  • AWSアクセスの種類を選択
    • アクセスの種類:プログラムによるアクセス、AWSコンソールへのアクセス
    • カスタムパスワード:*********
    • パスワードのリセットが必要
  • ユーザをグループに追加で グループの作成 をクリック
  • グループ名 hoge-training 入力して グループの作成 をクリック
  • ポリシーの付与はのちほど行うので 次のステップ:確認 をクリック
  • ユーザーの作成 をクリック

これでIAMユーザーが作成されました。 作成結果の画面にアクセスキーIDとシークレットアクセスキーが表示されていますので、 忘れずに控えておきましょう。 .csvのダウンロード をクリックすると、アクセスキー情報が記載されたCSVファイルをダウンロードできます、 後からアクセスキーの作成は可能だが、同じアクセスキーは二度とダウンロードできないので注意。 アクセスキーを不特定多数が閲覧可能な場所には置かないこと。Githubとか... アクセスキーを入手したら 閉じる をクリックしてIAMのユーザー画面に戻る。

IAMポリシーの付与

作成したIAMユーザにアクセス権限を設定 アクセス権限はIAMポリシーで管理されます。 ポリシーには管理ポリシーとインラインポリシーの2種類があります。

  • 管理ポリシー
    • AWS管理ポリシー
      • AWS側で作成。管理されるポリシー
      • 管理者権限、全サービスの閲覧のみの権限、EC2フルアクセス権限などが用意されている
      • 細かい設定をする必要がない場合は、このポリシーを付与するとラク
    • カスタマー管理ポリシー
      • ユーザが作成して、自由に設定できるポリシー
      • 特定のIPをからのみ操作を受け付けるといった細かい設定はこのポリシーを使う
  • インラインポリシー
    • 特定のIAMユーザ、IAMグループ、IAMロールに直接付与されるポリシー
    • 基本的には管理ポリシーを使うので、インラインポリシーが付与されることはあまりない。
    • 管理ポリシーはそのポリシーを適用している全ユーザ、グループ、ロールに影響があるので、特定のユーザのみに付与したい場合などはインラインポリシーを使う

ポリシーはJSON形式で記述します。

権限を以下のみにしたいので参考記事を参照しつつ設定してください。

  • EBSスナップショット
  • S3ファイルアップロード権限

参考記事

Review policy をクリックし、ポリシー名と説明を記入し、 Create policy をクリック 次にアクセス権限ポリシーをロールにアタッチさせます。 対象のポリシーを選択し、 次のステップ:確認 をクリック ロール名とロールの説明を入力し、 ロールの作成 をクリック

EC2で対象のインスタンスを選択し、 アクション→インスタンスの設定→IAMロールの割り当て/置換 をクリックし,アタッチしたいポリシーを選択。

サブネットの作成

サブネットは大きなネットワークを複数の小さなネットワークに分割して管理する歳の、管理単位となるネットワーク ELBを作成する予定があるサブネットは余裕を持って、「/24」(256IPアドレス以上)のネットワーク範囲で作成することが推奨 なぜなら、ELB作成時でELBを作成するサブネットに20IPアドレス以上の空きが必要になるため、「/28」(16IPアドレス)で作成したサブネットにはELBを作成することができないため。

AWSコンソールで VPC→サブネット→サブネットの作成 から以下の条件でサブネットを作成する

名前タグ VPC AZ IPv4 CIDR ブロック
subnet-hoge-training-ssh training ap-northeast-b 172.31.hoge.0/24
subnet-hoge-training-web01 training ap-northeast-b 172.31.hoge.0/24
subnet-hoge-training-web02 training ap-northeast-c 172.31.hoge.0/24
subnet-hoge-training-db01 training ap-northeast-b 172.31.hoge.0/24
subnet-hoge-training-db02 training ap-northeast-c 172.31.hoge.0/24

踏み台とWEBは- 自動割当パブリックIPを有効化にしたいので、 サブネットのアクション タブの自動割り当てIP設定の変更から有効化します。

EC2作成

AWS操作用の公開鍵・秘密鍵の作成

EC2では公開鍵暗号方式でろログインをします。

EC2→キーペア→キーペアの作成でキーペアを作成します。 作成したキーペアは作成したタイミングでしかダウンロードできないので注意。

  1. AWSコンソールからEC2を選択
  2. EC2ダッシュボードからキーペアを選択
  3. キーペアの作成をクリック
  4. キーペア名に作成するキーペアの名前を入力
  5. 作成をクリック
  6. pem形式のファイルをダウンロードします。

権限は600にします。

$ chmod 600 hoge.pem

セキュリティグループを作成

セキュリティグループはホワイトリスト方式なので「何を許可するのか」のみ指定可能 また、セキュリティグループはVPCごとの設定になります。

AWSコンソールのEC2→セキュリティグループ→セキュリティグループの作成からインバウンド(内部への通信)で以下の条件で作成

  • 踏み台
    • セキュリティグループ名:hoge-training-ssh
    • 設定:オフィスIPからSSHのみ許可
    • SSH,TCP,22, 自分のネットワーク
  • WEB
    • セキュリティグループ名:hoge-training-web
    • 設定:踏み台のからSSHのみ許可,ELBからのHTTP(80)のみ許可
    • HTTP,TCP,80,172.31.0.0/16
    • SSH,TCP,22,172.31.hoge.10/32
  • ELB
    • セキュリティグループ名:hoge-training-elb
    • HTTP,TCP,80,0.0.0.0/0,::/0
  • DB
    • タグ名: hoge-training-db
    • インバウンドルールの編集: webサーバのsubnetからのみ3306のアクセス許可
    • カスタム TCP ルール,TCP,3306,172.31.hoge.0/24
    • カスタム TCP ルール,TCP,3306,172.31.hoge.0/24

EC2作成・起動

AWSコンソールのEC2→インスタンス→インスタンスの作成から作成していく

  1. AMIの選択

AWS Marketplace からCentOS7を選択

  1. インスタンスタイプの選択

t2.micro

  1. 詳細設定

ネットワークで対象のVPCを選択 サブネットはそれぞれのものを選択 自動割り当てパブリック IPは有効化 ネットワークインターフェイスのプライマリIPで以下のを指定 - ssh - 172.31.hoge.10 - web01 - 172.31.hoge.10 - web02 - 172.31.hoge.10

  1. ストレージの追加

  2. 初期値を利用

  3. ボリュームタイプは汎用SSD(GP2)

  4. タグの追加

Name:hoge-training-ssh Name:hoge-training-web01 Name:hoge-training-web02

  1. セキュリティグループの設定

既存のセキュリティグループを選択

EIP

AWSコンソールのEC2→Elastic IP→新しいアドレスの割り当てでElasticIPを作成 次にアクション→アドレスの関連付けで踏み台のインスタンスとプライベートIPを選択を選択し関連付けをおす。

ELBの作成

AWSコンソールのEC2→ロードバランサー→ロードバランサーの作成をクリック。 ALBのHTTP(S) で作成。

名前:hoge-training-elb リソースはHTTPのみ アベイラビリティーゾーン:VPCと登録するEC2インスタンスを含むAZを選択 タグ:Name,hoge-training-elb

次の手順:セキュリティ設定の構成を選択 以下のエラーが出るが気にせず、進む。

ロードバランサーのセキュリティを向上させましょう。ロードバランサーは、いずれのセキュアリスナーも使用していません。
ロードバランサーへのトラフィックを保護する必要がある場合は、フロントエンド接続に HTTPS プロトコルをお使いください。 最初のステップに戻り、基本的な設定 セクションでセキュアなリスナーを追加 / 設定することができます。または現在の設定のまま続行することもできます。

ステップ 3: セキュリティグループの設定では既存のセキュリティグループを選択するで既に作成したセキュリティグループを選択

ステップ 4: ルーティングの設定ターゲットグループでは名前だけ入力

名前:ELBTARGET

ステップ 5: ターゲットの登録 で追加したいEC2インスタンスを選択して、登録済みに追加をクリック 登録済みに追加されたら次の手順:確認を選択

ステップ 6: 確認で問題なければ作成

ELBの動作確認

webサーバにcentosをインストール

# yum -y install httpd
# systemctl list-unit-files -t service |grep httpd
httpd.service                                 disabled
# systemctl enable httpd.service
Created symlink from /etc/systemd/system/multi-user.target.wants/httpd.service to /usr/lib/systemd/system/httpd.service.
# systemctl list-unit-files -t service |grep httpd
httpd.service                                 enabled
# systemctl start httpd.service

ELBのDNS名をブラウザに入力してindex.htmlの中身が表示されることを確認 ターゲットグループのターゲットから両インスタンスhealthyになることを確認

Route53の設定

ホストゾーンドメインは既にあるものなどを使い、 hoge-trainigをサブドメインとして ドメイン に登録する。 ホストゾーンはそのドメインのリソースレコードセットの集合です。

Route53→Hosted zonesからドメインを選択 Create Record Setを選択  Name:hoge-training Type:Aレコード(IPv4) Value:Alias:Yes Alias Target:ELBのDNS名 Routing Policy:Simple

のちにAliasをCloudFrontに変更します。

RDS 作成

VPC上にRDSインスタンスを起動する場合、DBサブネットグループを作成する必要があるので まずはサブネットグループを作成します。

RDS→サブネットグループ→DBサブネットグループの作成を選択 サブネットグループの詳細 名前:hoge-training 説明:middle engineer training ozawa subnet VPC:trainingのVPCを選択 サブネットの追加でアベイラビリティーゾーンサブネット

RDS→インスタンス→DBインスタンスの起動→MySQLを選択

DB詳細の指定

  • 開発/テストを選択
  • DBエンジンのバージョン
    • 最新のバージョン
  • DBインスタンスの選択
    • db.t2.micro
  • マルチAZ配置
    • 別のゾーンにレプリカを作成します
  • DBインスタンス識別子
  • マスターユーザの名前
    • root
  • パスワード

[詳細設定]の設定 ネットワーク&セキュリティ

データベースの設定

  • データベースの名前
  • データベースのポート
    • 3306
  • DBパラメータグループ
    • デフォルト
  • オプショングループ
    • デフォルト
  • IAM DB 認証
    • 無効化

暗号化

  • 無効化(無料利用枠ではデフォルトで無効)

バックアップ

  • バックアップの保存期間
    • 1日
  • バックアップウィンドウ
    • 開始時間20:00UTF期間0.5時間
  • タグをスナップショットへコピー  - チェック

モニタリング

  • 拡張モニタリング
    • 有効
    • モニタリングロール:デフォルト
    • 60秒

ログのエクスポート

  • 監査ログ
    • 有効
  • エラーログ
    • 無効
  • 全般ログ
    • 有効
  • スロークエリログ
    • 有効

メンテナンス

  • マイナーバージョン自動アップグレード
    • 無効化
  • メンテナンスウィンドウ
    • 開始日時:日曜日の21:00UTF期間0.5時間

DBインスタンスの作成 で作成完了

webサーバにmysqlクライアントをインスタンス RDSインスタンスからエンドポイントを確認

# yum install mysql -y
# mysql -u root -h エンドポイント名 -p

S3

hoge-training.<ドメイン>バケットを作成します。

S3→バケットを作成する で作成します。

  • バケット名を入力
  • リージョンを選択
  • 次へをクリック
  • フォルダの作成image フォルダを作成
  • アクセス権限 → バケットポリシーを以下にしてパブリックにする
{
    "Version": "2012-10-17",
    "Id": "PublicRead",
    "Statement": [
        {
            "Sid": "ReadAccess",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::hoge-training.<ドメイン/*"
        }
    ]
}

プロパティの設定およびアクセス許可の設定はデフォルトのまま進めます。

CloudFrontの設定

CloudFrontはAWSCDNサービスです。 CDNとはコンテンツを配信するために最適化されたネットワークのことです。 分散して配置されたサーバからコンテンツを配布することで効率的にコンテンツを配信する仕組み。

CloudFrontはAWSが世界中に配置したエッジサーバを利用して効率的にコンテンツを配信します。 エッジサーバはCloudFrontのコンテンツの配布ポイントでユーザーのアクセスを最も近くにあるエッジサーバに誘導します。 「xxx.cloudfront.net」というドメインにアクセスすると、最も近いエッジサーバのIPアドレスが返されます。 CloudFrontではコンテンツの配信元のサーバをオリジンサーバと呼びます。 CloudFrontを利用していない場合は、全てのアクセスがオリジンサーバにいくが、 利用するとオリジンサーバに到達する前に、エッジサーバがコンテンツを返す。 そのため、オリジンサーバの負荷を下げることができ負荷分散としても大きな意味があります。 なのでEC2やS3等コンテンツを配信するサーバの前には、なるべくCloudFrontを配置するべき。

エッジロケーションはエッジサーバが存在する地域で、日本では東京と大阪にある。

CloudFront→Create Distributionをクリック

  • コンテンツの配信方法の選択

HTTP[S]で表示するWEBコンテンツの配信なのでWEBを選択します。

  • Distributionの設定

OriginSettingでオリジンサーバの設定をします。 今回CloudFrontはS3とALBの前に設置するので、 Origin Domain Name にS3とALBを選択して入力することになります。

  • Origin Domain Name
  • Behaviors
    • /image/* 300 秒キャッシュ
  • Origin Domain Name
    • hoge-training-elb-1353485913.ap-northeast-1.elb.amazonaws.com
  • Behaviors

    • /js/* 600 秒キャッシュ
    • /css/* 600 秒キャッシュ
    • Default (*)
  • Distributionの設定(Origin Settings)

オリジンサーバの設定をする

設定項目 説明
Origin Domain Name S3バケットのエンドポイント オリジンサーバのドメインを入力
Origin Path 値なし CloudFrontへのリクエストをオリジンサーバの特定のパスにルーティングしたい場合に設定。
Origin ID 任意の名前 このDistributionを区別するための名前を設定
Restrict Bucket Access No オリジンがS3の場合のみ有効。S3バケットのコンテンるへのアクセスをCloudFrontからのみに制限する場合はYes。直接エンドポイントにアクセスできるようにするならNo
Origin Custom Headers 値なし リクエストをオリジンサーバに転送する際のヘッダーを指定
  • Default Cache Behavior Setting

キャッシュの動作の設定をする

設定項目 説明
Path Pattern Default(*) キャッシュを有効にするパスのパターンを指定。
Viewer Priticol Policy HTTP and HTTPS コンテンツにアクセスする際にどのプロトコルを使用するか選択
Allowed HTTP Methods GET,HEAD エンドユーザーに許可するHTTPメソッドを選択
Field-level Encryption Config 値なし 特定のデータフィールドにフィードレベル暗号化を適用する場合に指定
Cached HTTP Methods GET,HEAD CloudFrontでのキャッシュが有効になるHTTPメソッドを選択
Cached Based on Selected Request Headers None CloudFrontがオリジンサーバに転送するリクエストヘッダーの指定とヘッダー値に基づいてオブジェクトをキャッシュするかの設定を行う。
Object Caching Use Origin Cache Headers CloudFrontがキャッシュを保持する時間の設定。オリジンサーバで追加したCache-COntorolの時間に応じてキャッシュを保持する場合は、今回の値を設定する。
Minimum TTL 0 キャッシュの最小保持期間
Maximum TTL 31536000 キャッシュの最大保持期間
Default TTL 86400 キャッシュのデフォルトの保持期間
Foward Cookied None CloudFrontからオリジンサーバに転送するCookieを指定。オリジンがS3のときは無効。
Query String Fowarding and Caching None クエリ文字列に基づいてキャッシュを行う
Smooth Streaming No Microsoftスムーズストリーミング形式のメディアファイルを配信する場合はYES
Restrict Viewer Access No 署名付きURLを利用するかの選択
Compress Objects Automatically No ファイルを自動的に圧縮する
Lambda Function Assopciations 値なし トリガーを追加するLambda関数のARNを指定
  • Distribution Setting

Distributionの詳細を設定

設定項目 説明
Price Class Use ALL Edge Locations 価格クラスを選択。
AWS WAF Web ACL None AWS WAFを使用する場合にウェブACLを選択する・
Alternate Domain Names 値なし 独自ドメインを利用する場合に設定
SSL Certificate Default CloudFrint Cerificate DefaultではCloudFrontにデフォルトで用意されている証明書を利用する。
Supported HTTP Versions HTTP/2,HTTP/1.1,HTTP/1.0 CloudFrontとの通信に使用するHTTPバージョンを指定
Default Root Object index.html デフォルトのルートオブジェクトを設定
Logging OFF ログを取得するか設定
Bucket for Logs 値なし ログを配置するS3のバケットを選択
Log Prefix 値なし ログファイル名の戦闘につける文字列を指定
Cookie Logging off ログにCookieも記録するか選択
Enable IPv6 有効 IPv6を有効にするか選択
Comment 値なし 入力は任意。
Distribution State Enabled Distributionの使用準備が整ったあと、自動的にこのDistributionを有効にするか選択

Deployedになったら、Domain Nameに表示されている「****.cloudfront.net」にアクセスすればS3のコンテンツが表示されます。

  • Route53との連系

Route53にCLoudFrontのエンドポイントを設定することで、独自ドメインでCloudFrontを利用することができます。 作成したDistributionを選択し、Distribution Settingをクリック。 GeneralタブのEditで編集画面に行き、Alternate Domain Namesに設定したい独自ドメインを入力する。

次にRoute5→Hotsted Zonesから使用する独自ドメインのホストゾーンを選択し、Aliasレコードを追加する。

確認項目

セキュリティ

  • SSH 事務所からのみ接続できることを確認
  • HTTP 事務所からのみ閲覧できることを確認
  • MySQL web サーバからのみ接続できることを確認

RDS

  • 手動フェイルオーバを行なう
    • AZ が切り替わっていることを確認
    • サービスが閲覧できることを確認

S3

  • web サーバから aws コマンドを利用してS3 のバケットにテストファイルをアップロードする
  • S3 のバケットから aws コマンドを利用してテストファイルをダウンロードする
  • 踏み台からは S3 操作が不可であることを確認する

EBS

  • web サーバから aws コマンドを利用して、EBS スナップショットを取得する
  • 踏み台からは EBS スナップショット取得が出来ないことを確認

サイト表示

  • yourname-training.<ドメイン> 画面表示が正常であることを確認
    • web01, web02 に分散されていることを確認
    • web01 を ELB から切り離しても正常に閲覧できることを確認
  • image 配下へのアクセス
    • S3 へアクセスが行っていることを確認
  • js, css 配下へのアクセス ELB を経由していることを確認
    • キャッシュ時間に応じてキャッシュされていることを確認

上記以外
- ELB を経由していることを確認
- キャッシュされていないことを確認

podのCrashLoopBackOff原因およびNodeのNot Readyフラッピング問題調査

これは何?

とあるPodがCrashLoopBackOffのステータスになりあがってくれず、 それにあわせてNodeのstatusがNot Readyでフラッピングしている。 調査しつつ周辺技術(kubelet, CRI,CNIとか)についても調べて自信ニキになりたい。

事象

fluentdのpodがCrashLoopBackOffのステータスになり起動しない、 それに伴いNodeのstatusもNot Readyにフラッピングしている。

  • 各nodeの状態
# kubectl get pods |grep fluentd
node1                             0/1     CrashLoopBackOff   2029       28d
node2                            0/1     CrashLoopBackOff   1075       28d
node3                            0/1     CrashLoopBackOff   1052       28d
node4                            0/1     CrashLoopBackOff   1036       28d
node5                            0/1     CrashLoopBackOff   2994       28d

どんなとき

fluentdにログが流れてなかった時期

原因

CNIのbug fluentd PodのPodSandboxのコンテナがないためkubeletでPodの起動に失敗している。

根本対応

bugfixされたlibcniをpullする。

暫定対応

PodSandboxのコンテナ起動もしくはfluentd Podの再作成(DeamonSetなので消せば再起動するため)もしくはkubelet再起動

調査した流れとログ

pod起動していないのでlogsは不可。 kubeletかdescribeでまずは調べる。 台数あるのでまずは fluentd を中心に調べていく。 その次にNode NotreadyについてPLEG周りを調べていく。

  • node server

kubeletが fluentd- podへの動機に失敗している模様

# journalctl -u kubelet
-- Logs begin at 月 2018-11-19 12:14:46 JST, end at 月 2019-02-04 16:29:22 JST. --
 1月 24 01:49:36 node kubelet[10796]: E0124 01:49:36.768314   10796 pod_workers.go:190] Error syncing pod a094d5a4-125b-11e9-8374-fa163e76fc98 ("fluentd(a094d5a4-125b-11e9-8374-fa163e76fc98)"), skipping: rpc err
 1月 24 01:49:42 node kubelet[10796]: I0124 01:49:42.507809   10796 setters.go:72] Using node IP: "10.64.152.1"
 1月 24 01:49:51 node kubelet[10796]: E0124 01:49:51.768283   10796 pod_workers.go:190] Error syncing pod a094d5a4-125b-11e9-8374-fa163e76fc98 ("fluentd(a094d5a4-125b-11e9-8374-fa163e76fc98)"), skipping: rpc err
 1月 24 01:49:52 node kubelet[10796]: I0124 01:49:52.523387   10796 setters.go:72] Using node IP: "10.64.152.1"
  • Master側

CrashLoopBackOffはどうやら以下の内容

コンテナ内のプロセスの終了を検知してコンテナの再起動を繰り返している。

fluentdDaemonset だった。なので自動起動を何度も試みていると考えられる。

# kubectl get pods fluentd -o wide
NAME    READY   STATUS             RESTARTS   AGE   IP            NODE    NOMINATED NODE   READINESS GATES
fluentd   0/1     CrashLoopBackOff   1075       28d   172.16.69.8    node   <none>           <none>
  • master側

OCIruntimeがコンテナ起動作成失敗している模様 Eventsに status: rpc error とあるので、kubeletからCRIへのgrpcプロトコルの命令が処理されずエラーになっていると考えられる。

# kubectl describe pods fluentd
(略)
    State:          Waiting
      Reason:       CrashLoopBackOff
    Last State:     Terminated
      Reason:       ContainerCannotRun
      Message:      OCI runtime create failed: container_linux.go:348: starting container process caused "process_linux.go:402: container init caused \"\"": unknown
      Exit Code:    128
      Started:      Fri, 11 Jan 2019 16:26:40 +0900
      Finished:     Fri, 11 Jan 2019 16:26:40 +0900
(中略)
Events:
  Type     Reason      Age                    From                                     Message
  ----     ------      ----                   ----                                     -------
  Warning  FailedSync  60m (x85 over 19d)     kubelet, node  error determining status: rpc error: code = Unknown desc = operation timeout: context deadline exceeded
  Warning  FailedSync  8s (x123314 over 23d)  kubelet, node  error determining status: rpc error: code = DeadlineExceeded desc = context deadline exceeded

PodSandboxStatus of sandbox for pod "fluentd" error: rpc error: code = Unknown desc = Error: No such container: とあるので fluentd podのための PodSandbox がないメッセージが出力されている。

  • node
# less /var/log/messages-20190113
(略)
Jan  7 09:07:16 node004 kubelet: E0107 18:07:16.254903   10796 kuberuntime_manager.go:857] PodSandboxStatus of sandbox "5983c305299bada0906df670eba18f8be1e5192d75b825f1a01075dd5595c5f3" for pod "fluentd(a094d5a4-125b-11e9-8374-fa163e76fc98)" error: rpc error: code = Unknown desc = Error: No such container: 5983c305299bada0906df670eba18f8be1e5192d75b825f1a01075dd5595c5f3
Jan  7 09:07:16 node004 kubelet: E0107 18:07:16.254959   10796 generic.go:247] PLEG: Ignoring events for pod fluentd/default: rpc error: code = Unknown desc = Error: No such container: 5983c305299bada0906df670eba18f8be1e5192d75b825f1a01075dd5595c5f3
  • node

fluentd コンテナは起動しきってない。 gcr.io/google_containers/pause-amd64k8s_POD_fluentd_default_a094d5a4-125b-11e9-8374-fa163e76fc98_1 をみにいっている。 google_containers/pause-amd64 pause-amdd64はimage周りで使うコンテナの模様 image_gc_manager_test.goでもpauseコンテナについて書いてある。 pauseコンテナの説明はここがわかりやすい

# docker ps -a                                                                                                                                                                                                 [85/1657]
CONTAINER ID        IMAGE                                                        COMMAND                  CREATED             STATUS                PORTS               NAMES
000016b6000        asia.gcr.io/hoge/fluentd                            "/bin/sh -c '/run.sh…"   3 weeks ago         Created                                   k8s_fluentd_fluentd_default_a094d5a4-125b-11e9-8374-fa163e76fc98_1076
0004301c000       asia.gcr.io/hoge/fluentd                           "/bin/sh -c '/run.sh…"   3 weeks ago         Removal In Progress                       k8s_fluentd_fluentd_default_a094d5a4-125b-11e9-8374-fa163e76fc98_1075
0002e7bc000        gcr.io/google_containers/pause-amd64:3.1                         "/pause"                 3 weeks ago         Up 3 weeks                                k8s_POD_fluentd_default_a094d5a4-125b-11e9-8374-fa163e76fc98_1

以上をまとめると fluentd コンテナは作成され起動しようとしているけど、OCIruntimeがコンテナ起動作成失敗していると考えられる。 fluentd_default podのための PodSandbox がないメッセージが出力されているので、 PodSandbox に調べてみる。 PodSandbox は以下。

流れとして kubelet→CRI(docker)→podsandbox→pod となる。

Before starting a pod, kubelet calls RuntimeService.RunPodSandbox to create the environment. This includes setting up networking for a pod (e.g., allocating an IP). Once the PodSandbox is active, individual containers can be created/started/stopped/removed independently. To delete the pod, kubelet will stop and remove containers before stopping and removing the PodSandbox.

Kubelet is responsible for managing the lifecycles of the containers through the RPCs, exercising the container lifecycles hooks and liveness/readiness checks, while adhering to the restart policy of the pod.

つまりkubeletはポッドを開始する前に、RuntimeServiceを呼ぶ。そしてRunPodSandboxを呼び出して環境を作成する。(ポッド用のネットワーク設定(IP割当て)などもする。) PodSandboxがアクティブになると、個々のコンテナを個別に作成/開始/停止/削除することができる。 ポッドを削除するには、kubeletはPodSandboxを停止して削除する前にコンテナを停止して削除する。 Kubeletは、ポッドの再起動ポリシーを守りながら、RPCを通じてコン​​テナのライフサイクルを管理して、 コンテナーのライフサイクルフックを実行し、liveness/readiness のチェックを行う。

とあるのでkubeletがCRIにgRPCでコンテナ起動させようとしても PodSandboxのコンテナがないため、コンテナの起動に失敗している。 そしてDeamonSetが起動させようとして、Woeker NodeのStatusもフラッピングしていると考えられる。

Not ReadyのNodeでは PLEG でFalseしている。 PLEGはここ参照。 PLEGはkubeletの内部モジュールで、 Pod Lifecycle Event Generatorという名前の通り、kubeletがPodの管理およびコンテナの状態を管理するためのもの。

# kubectl describe nodes node
Conditions:
  Type             Status  LastHeartbeatTime                 LastTransitionTime                Reason                       Message
  ----             ------  -----------------                 ------------------                ------                       -------
  MemoryPressure   False   Tue, 05 Feb 2019 14:36:43 +0900   Fri, 21 Dec 2018 20:19:20 +0900   KubeletHasSufficientMemory   kubelet has sufficient memory available
  DiskPressure     False   Tue, 05 Feb 2019 14:36:43 +0900   Fri, 21 Dec 2018 20:19:20 +0900   KubeletHasNoDiskPressure     kubelet has no disk pressure
  PIDPressure      False   Tue, 05 Feb 2019 14:36:43 +0900   Fri, 21 Dec 2018 20:19:20 +0900   KubeletHasSufficientPID      kubelet has sufficient PID available
  Ready            False   Tue, 05 Feb 2019 14:36:43 +0900   Tue, 05 Feb 2019 14:36:33 +0900   KubeletNotReady              ≈is not healthy: pleg was last seen active 3m11.707539232s ago; threshold is 3m0s
(中略)
Events:
  Type    Reason        Age                     From                    Message
  ----    ------        ----                    ----                                     -------
  Normal  NodeReady     19m (x8929 over 45d)    kubelet, node004  Node node004 status is now: NodeReady
  Normal  NodeNotReady  4m15s (x8932 over 24d)  kubelet, node004  Node node004 status is now: NodeNotReady

PLEGのhealth checkは3分毎。docker ps でコンテナの状態変化を検出し、docker psinspect でこれらのコンテナの詳細を取得する。 これらが終了した後にタイムスタンプを更新するのだけれども、タイムスタンプが更新されていない場合(3分)、ヘルスチェックは失敗する。 https://github.com/kubernetes/kubernetes/blob/a1539d8e52c7ca5f6a590eebc66a5b33acc9c036/pkg/kubelet/pleg/generic.go#L134-L146

Master側はNodeのkubeletのPLEGからpodが落ちていると受け取って、Not Readyとしている。 そしてPLEGがpodが落ちていると判断しているのはPodSandboxがおらずコンテナが起動していないから。 そしてCNIのbugでpodsandboxの起動時失敗でtimeoutのままlockを要求して、podsandboxが再起動されず起動していない模様。 https://github.com/containernetworking/cni/issues/567

# kubectl describe pods fluentd
(略)
Events:
  Type     Reason      Age                    From                                     Message
  ----     ------      ----                   ----                                     -------
  Warning  FailedSync  60m (x85 over 19d)     kubelet, node004  error determining status: rpc error: code = Unknown desc = operation timeout: context deadline exceeded
  Warning  FailedSync  8s (x123314 over 23d)  kubelet, node004  error determining status: rpc error: code = DeadlineExceeded desc = context deadline exceeded

コンテナがどうやって起動しているのか、コンテナランタイムの実装などをみて、 理解が必要だと思ったので、理解を深めてからまた、kubernatesのpod起動周りについて調べ直したい。