GKE で Spinnaker を構築する

# はじめに

最近話題の Spinnaker を Google Kubernetes Engine（GKE）上に構築していきます。
簡単に試すのであれば、[Google Cloud Launcher](https://cloud.google.com/launcher/solution/click-to-deploy-images/spinnaker?q=spinnaker) から試せます。
今回は、Spinnaker のデプロイ管理ツールである Halyard を用いて Spinnaker をデプロイしていきます。

本記事は、以下のドキュメントを参考に Spinnaker の構築を試したものです。

- [Try out Halyard on GKE - Spinnaker](https://www.spinnaker.io/setup/quickstart/halyard-gke/)
- [Deploy to Google Kubernetes Engine with RBAC - Spinnaker](https://www.spinnaker.io/setup/quickstart/halyard-gke-deploy-rbac/)

以下の環境を構築することを目指します。
![Halyard インスタンスを含むデプロイ環境](https://www.spinnaker.io/setup/quickstart/halyard-gke/deployment.png)

# 環境

- macOS High Sierra Version 10.13.6
- Google Cloud SDK 240.0.0
- Spinnaker 1.13.0

# Spinnaker とは

Spinnaker とは、マルチクラウドに対応した継続デリバリのプラットフォームです。
パイプライン管理の機能を備え、Red/Black デプロイ（Blue/Green デプロイ）やカナリアリリースにも対応しています。
Netflix によって OSS 化され、Google を始め、様々な企業やコミュニティが開発に参加しています。

# Google Cloud SDK をインストールする

以下のドキュメントを参考に Google Cloud SDK をインストールし、プロジェクトの設定をしてください。
[Google Cloud SDK documentation](https://cloud.google.com/sdk/docs/)

# API を有効にする

以下の API を有効にします。

- Identity and Access Management (IAM) API
- Cloud Resource Manager API
- Kubernetes Engine API

以下のコマンドで API を有効にします。

```bash
$ gcloud services enable \
    iam.googleapis.com \
    cloudresourcemanager.googleapis.com \
    container.googleapis.com
```

# サービスアカウントの作成

Halyard をインストールするインスタンスで使用するサービスアカウントを作成します。

```bash
$ GCP_PROJECT=$(gcloud info --format='value(config.project)')
$ HALYARD_SA=halyard-service-account

$ gcloud iam service-accounts create $HALYARD_SA \
    --project=$GCP_PROJECT \
    --display-name $HALYARD_SA
```

作成した Halyard のサービスアカウントに以下のロールを付与します。

- `roles/iam.serviceAccountKeyAdmin`
- `roles/container.admin`

```bash
$ HALYARD_SA_EMAIL=$(gcloud iam service-accounts list \
    --project=$GCP_PROJECT \
    --filter="displayName:$HALYARD_SA" \
    --format='value(email)')

$ gcloud projects add-iam-policy-binding $GCP_PROJECT \
    --role roles/iam.serviceAccountKeyAdmin \
    --member serviceAccount:$HALYARD_SA_EMAIL

$ gcloud projects add-iam-policy-binding $GCP_PROJECT \
    --role roles/container.admin \
    --member serviceAccount:$HALYARD_SA_EMAIL
```

Spinnaker で利用する Google Cloud Storage（GCS）と Google Container Registry（GCR）のサービスアカウントを作成します。

```bash
$ GCS_SA=gcs-service-account

$ gcloud iam service-accounts create $GCS_SA \
    --project=$GCP_PROJECT \
    --display-name $GCS_SA
```

作成した GCS と GCR のサービスアカウントに以下のロールを付与します。

- `roles/storage.admin`
- `roles/browser`

```bash
$ GCS_SA_EMAIL=$(gcloud iam service-accounts list \
    --project=$GCP_PROJECT \
    --filter="displayName:$GCS_SA" \
    --format='value(email)')

$ gcloud projects add-iam-policy-binding $GCP_PROJECT \
    --role roles/storage.admin \
    --member serviceAccount:$GCS_SA_EMAIL

$ gcloud projects add-iam-policy-binding $GCP_PROJECT \
    --role roles/browser \
    --member serviceAccount:$GCS_SA_EMAIL
```

# Spinnaker の永続データを保存する GCS のバケットを作成する

バケット名は、GCS 全体で一意である必要があることに注意してください。
サンプルとして `my-spin-bucket` という名前で作成しています。

```bash
$ GCS_CLASS=multi_regional
$ GCS_BUCKET=my-spin-bucket

$ gsutil mb -p $GCP_PROJECT -c $GCS_CLASS -l asia gs://$GCS_BUCKET/
```

# GKE クラスタの構築

Spinnaker のデプロイには、以下のいずれかに対応する必要があります。

- レガシー認証を有効にする
- RBAC を設定する

今回は RBAC を設定することで進めます。

まずは GKE を作成しましょう。
デフォルトで作成されるクラスタの設定に一部オプションを加えて作成します。
GKE のバージョンは、執筆時点（2019/03）の最新 `1.12.6-gke.7` を使用します。
サンプルとして `spinnaker-cluster` という名前で作成しています。

```bash
$ gcloud config set container/new_scopes_behavior true

$ GKE_CLUSTER_NAME=spinnaker-cluster
$ GKE_CLUSTER_ZONE=asia-northeast1-a
$ GKE_CLUSTER_VERSION=1.12.6-gke.7

$ gcloud container --project $GCP_PROJECT clusters create $GKE_CLUSTER_NAME \
    --zone $GKE_CLUSTER_ZONE \
    --no-enable-basic-auth \
    --no-issue-client-certificate \
    --cluster-version $GKE_CLUSTER_VERSION \
    --machine-type "n1-standard-1" \
    --image-type "COS" \
    --num-nodes "3" \
    --disk-type "pd-standard" \
    --disk-size "100" \
    --metadata disable-legacy-endpoints=true \
    --scopes gke-default \
    --enable-cloud-logging \
    --enable-cloud-monitoring \
    --enable-ip-alias \
    --addons HorizontalPodAutoscaling,HttpLoadBalancing \
    --enable-autorepair
```

# Halyard 実行用のインスタンス作成

作成した Halyard 用のサービスアカウントでインスタンスを作成します。
サンプルとして `halyard-host` という名前で作成しています。

```bash
$ HALYARD_HOST=halyard-host

$ gcloud compute instances create $HALYARD_HOST \
    --project=$GCP_PROJECT \
    --zone=asia-northeast1-a \
    --scopes=cloud-platform \
    --service-account=$HALYARD_SA_EMAIL \
    --image-project=ubuntu-os-cloud \
    --image-family=ubuntu-1804-lts \
    --machine-type=n1-standard-4
```

作成したインスタンスに SSH で接続します。

```bash
$ gcloud compute ssh $HALYARD_HOST \
    --project=$GCP_PROJECT \
    --zone=asia-northeast1-a \
    --ssh-flag="-L 9000:localhost:9000" \
    --ssh-flag="-L 8084:localhost:8084"
```

以降 SSH 内でコマンドを実行します。

# kubectl をインストール

kubectl をインストールします。

```bash
$ KUBECTL_LATEST=$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)

$ curl -LO https://storage.googleapis.com/kubernetes-release/release/$KUBECTL_LATEST/bin/linux/amd64/kubectl
$ chmod +x kubectl
$ sudo mv kubectl /usr/local/bin/kubectl
$ kubectl version
```

# Halyard をインストール

Halyard をインストールします。

```bash
$ curl -O https://raw.githubusercontent.com/spinnaker/halyard/master/install/debian/InstallHalyard.sh
$ sudo bash InstallHalyard.sh
$ hal -v
$ . ~/.bashrc
```

# 認証情報を設定

## Kubernetes クラスタの認証情報を取得する

作成した Kubernetes クラスタの認証情報を取得します。

```bash
$ GCP_PROJECT=$(gcloud info --format='value(config.project)')
$ GKE_CLUSTER_NAME=spinnaker-cluster
$ GKE_CLUSTER_ZONE=asia-northeast1-a

$ gcloud container clusters get-credentials $GKE_CLUSTER_NAME \
    --zone $GKE_CLUSTER_ZONE \
    --project $GCP_PROJECT
```

## GCS のサービスアカウントのファイルを作成する

GCS のサービスアカウントの json ファイルを作成します。

```bash
$ GCS_SA=gcs-service-account
$ GCS_SA_DEST=~/.gcp/gcp.json

$ mkdir -p $(dirname $GCS_SA_DEST)

$ GCS_SA_EMAIL=$(gcloud iam service-accounts list \
    --filter="displayName:$GCS_SA" \
    --format='value(email)')

$ gcloud iam service-accounts keys create $GCS_SA_DEST \
    --iam-account $GCS_SA_EMAIL
```

# Kubernetes クラスタにサービスアカウントを作成する

作成した Kubernetes クラスタに対してサービスアカウントを作成します。
そして作成したサービスアカウントからトークンを取得し、新しいトークンユーザーに付与します。

```bash
$ CONTEXT=$(kubectl config current-context)

$ kubectl apply --context $CONTEXT \
    -f https://spinnaker.io/downloads/kubernetes/service-account.yml

$ TOKEN=$(kubectl get secret --context $CONTEXT \
   $(kubectl get serviceaccount spinnaker-service-account \
       --context $CONTEXT \
       -n spinnaker \
       -o jsonpath='{.secrets[0].name}') \
   -n spinnaker \
   -o jsonpath='{.data.token}' | base64 --decode)

$ kubectl config set-credentials ${CONTEXT}-token-user --token $TOKEN
$ kubectl config set-context $CONTEXT --user ${CONTEXT}-token-user
```

RBAC を設定するに当たって以下の `spinnaker-rbac.yml` を作成します。

```yaml:spinnaker-rbac.yml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: spinnaker-role
rules:
  - apiGroups: ['']
    resources:
      [
        'namespaces',
        'configmaps',
        'events',
        'replicationcontrollers',
        'serviceaccounts',
        'pods/log',
      ]
    verbs: ['get', 'list']
  - apiGroups: ['']
    resources: ['pods', 'services', 'secrets']
    verbs:
      [
        'create',
        'delete',
        'deletecollection',
        'get',
        'list',
        'patch',
        'update',
        'watch',
      ]
  - apiGroups: ['autoscaling']
    resources: ['horizontalpodautoscalers']
    verbs: ['list', 'get']
  - apiGroups: ['apps']
    resources: ['controllerrevisions', 'statefulsets']
    verbs: ['list']
  - apiGroups: ['extensions', 'apps']
    resources: ['deployments', 'replicasets', 'ingresses']
    verbs:
      [
        'create',
        'delete',
        'deletecollection',
        'get',
        'list',
        'patch',
        'update',
        'watch',
      ]
  - apiGroups: ['']
    resources: ['services/proxy', 'pods/portforward']
    verbs:
      [
        'create',
        'delete',
        'deletecollection',
        'get',
        'list',
        'patch',
        'update',
        'watch',
      ]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: spinnaker-role-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: spinnaker-role
subjects:
  - namespace: spinnaker
    kind: ServiceAccount
    name: spinnaker-service-account
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: spinnaker-service-account
  namespace: spinnaker
```

作成した `spinnaker-rbac.yml` を適用します。

```bash
$ kubectl apply --context $CONTEXT -f spinnaker-rbac.yml
```

# Spinnaker の設定

Halyard が最新の Spinnaker のバージョンを使用するように設定します。

```bash
$ hal config version edit --version $(hal version latest -q)
```

Spinnaker の設定やパイプライン情報の保存先として GCS を設定します。
`--bucket` でバケット名を指定しなければ、`spin-[UUID]` のバケットが自動で作成されます。

```bash
$ BUCKET_LOCATION=asia-northeast1
$ BUCKET=my-spin-bucket

$ hal config storage gcs edit \
    --bucket $BUCKET \
    --project $(gcloud info --format='value(config.project)') \
    --bucket-location $BUCKET_LOCATION \
    --json-path ~/.gcp/gcp.json

$ hal config storage edit --type gcs
```

GCR から pull するように Docker Registry プロバイダの設定をします。

```bash
$ hal config provider docker-registry enable

$ hal config provider docker-registry account add my-gcr-account \
    --address gcr.io \
    --password-file ~/.gcp/gcp.json \
    --username _json_key
```

Kubernetes プロバイダの設定をします。
今回は Kubernetes V2 プロバイダを使用して進めます。

```bash
$ hal config provider kubernetes enable

$ hal config provider kubernetes account add my-k8s-account \
    --docker-registries my-gcr-account \
    --provider-version v2 \
    --context $(kubectl config current-context)
```

# Spinnaker をデプロイ

設定を終えたところで Spinnaker をデプロイしていきます。

```bash
$ hal config deploy edit \
    --account-name my-k8s-account \
    --type distributed

$ hal deploy apply
```

Spinnaker をデプロイしたら作成された Pod が `1/1 Running` になるまで待機します。

```bash
$ kubectl get po -n spinnaker
NAME                                READY   STATUS    RESTARTS   AGE
spin-clouddriver-5fc8d78c9f-rqwn4   1/1     Running   0          9m31s
spin-deck-78df87c4cb-ws5j2          1/1     Running   0          9m30s
spin-echo-75f8d9f955-swnzj          1/1     Running   0          9m31s
spin-front50-69bf559776-bfz5h       1/1     Running   0          9m29s
spin-gate-677dd7889f-vfzm6          1/1     Running   0          9m30s
spin-igor-58b98c69cb-7tq9h          1/1     Running   0          9m30s
spin-orca-5fd4f77c84-f4769          1/1     Running   0          9m29s
spin-redis-55ccd77cbd-5k6dp         1/1     Running   0          9m31s
spin-rosco-b84f686c8-7hbnk          1/1     Running   0          9m28s
```

デプロイが完了したら Spinnaker に接続します。

```bash
$ hal deploy connect
```

ブラウザで以下のアドレスに接続すると Spinnaker が表示されます。

```bash
http://localhost:9000/
```

アクセスすると以下のようなローディング画面が表示されます。

![Spinnaker のローディング画面](https://qiita-image-store.s3.amazonaws.com/0/129299/a5afd3b0-64cb-534f-f807-71a2170569f4.png "Spinnaker のローディング画面")

ローディングが終わると検索画面が表示されます。

![Spinnaker の検索画面](https://qiita-image-store.s3.amazonaws.com/0/129299/a3c28635-55c7-72fe-cebb-8dcda0afc809.png "Spinnaker の検索画面")

# 作成したリソースの削除

作成したリソースを削除します。

GKE クラスタを削除します。
`y` で削除します。

```bash
$ gcloud container clusters delete $GKE_CLUSTER_NAME
```

GCS バケットを削除します。

```bash
$ gsutil rm -r gs://$GCS_BUCKET/
```

Halyard 実行用のインスタンスを削除します。
`y` で削除します。

```bash
$ gcloud compute instances delete $HALYARD_HOST
```

サービスアカウントに紐付けたロールを削除します。

```bash
$ gcloud projects remove-iam-policy-binding $GCP_PROJECT \
    --role roles/iam.serviceAccountKeyAdmin \
    --member serviceAccount:$HALYARD_SA_EMAIL

$ gcloud projects remove-iam-policy-binding $GCP_PROJECT \
    --role roles/container.admin \
    --member serviceAccount:$HALYARD_SA_EMAIL

$ gcloud projects remove-iam-policy-binding $GCP_PROJECT \
    --role roles/storage.admin \
    --member serviceAccount:$GCS_SA_EMAIL

$ gcloud projects remove-iam-policy-binding $GCP_PROJECT \
    --role roles/browser \
    --member serviceAccount:$GCS_SA_EMAIL
```

サービスアカウントを削除します。
`y` で削除します。

```bash
$ gcloud iam service-accounts delete $HALYARD_SA_EMAIL
$ gcloud iam service-accounts delete $GCS_SA_EMAIL
```

# さいごに

ここまで Spinnaker の構築を試しました。
Halyard を用いて RBAC を設定した Kubernetes 上に構築すると手順が多くなるので、気軽に試すのであれば [Google Cloud Launcher](https://cloud.google.com/launcher/solution/click-to-deploy-images/spinnaker?q=spinnaker) からの構築がスムーズだと感じました。
次回は Spinnaker のパイプラインの構築を試します。

# 参考

[Try out Halyard on GKE - Spinnaker](https://www.spinnaker.io/setup/quickstart/halyard-gke/)
[Deploy to Google Kubernetes Engine with RBAC - Spinnaker](https://www.spinnaker.io/setup/quickstart/halyard-gke-deploy-rbac/)
[次世代 Continuous Delivery プラットフォームである Spinnaker を体験してみよう！ - Mercari Engineering Blog](https://tech.mercari.com/entry/2017/12/17/205719)
[Spinnaker を導入した - kawabatas 技術ブログ](https://kawabatas.hatenablog.com/entry/2018/06/24/135312)