情報畑でつかまえてロゴ
本サイトは NTTテクノクロスが旬の IT をキーワードに
IT 部門が今知っておきたい最新テクノロジーに関する情報をお届けするサイトです

ハイブリッドクラウド環境でKubernetes Cluster Federationを使ってみた

自社のプライベートなクラウド環境(=OpenStack)をベースに、パブリックなクラウド環境(=AWS)を掛け合わせたハイブリッドクラウド環境を利用し、Kubernetes Cluster Federationといったツールを使用しマルチクラスタを一括管理およびGitLabと連携させたCI/CDパイプラインを検証していきます。

1.はじめに

今回は現在のトレンドである「コンテナ」「ハイブリッドクラウド」「CI/CD」といったキーワードより、ハイブリッドクラウド上にKubernetesを使用したCI/CDパイプラインについてのモデル検討および検証した内容について紹介します。

自社のプライベートなクラウド環境(=OpenStack)をベースに、パブリックなクラウド環境(=AWS)を掛け合わせたハイブリッドクラウド環境を利用し、Kubernetes Cluster Federationといったツールを使用しマルチクラスタを一括管理およびGitLabと連携させたCI/CDパイプラインを検証していきます。

どうぞよろしくお願いいたします。

2.背景と目的

ハイブリッドクラウドにおけるコンテナ環境についてユースケース調査を行ったところ、CI/CDパイプラインの市場的ニーズが多いということがわかりました。
また、コンテナ市場については「開発リリースを加速させるためのプラットフォーム」という点を期待している背景もあり、コンテナ・Kubernetesの市場は今後も拡大が期待されています。

しかし、ハイブリットクラウド環境でKubernetesクラスタを構築するには、ノード間の通信やPodのネットワーク、マネージドサービス利用に伴うクラウド事業者ロックインなどの課題を解消する必要があります。
今回は、Kubernetes Cluster Federationを使うことでマスターノードなどのリソースが重複するような構成になりますが、複数のクラウド環境上のKubernetesクラスタをシームレスに運用できるようにしてみました。
また、ハイブリッドクラウド環境におけるCI/CDパイプラインにおいて、ステージングの一部でKubernetes Cluster FederationによるPod制御を行うことで、多様な用途に応じて複数のKubernetesクラスタを使い分けることができ、Workerノードにラベルを付与し、テナント毎に異なるラベルを指定することで1つのKubernetesクラスタを複数のテナントで論理的に分離したマルチテナントでの利用も可能になると考えています。
Kubernetes Cluster Federationについては、2018年12月にβ販がリリースされていましたが公開されている情報や記事等の情報が非常に少なかったため、今回検証した内容を記事にしました。

3.実際に実施した内容

構成


手順

【前提条件】

・kubeadmという構築ツールを使用して、Kubernetesを構築(AWS環境2セット、OpenStack環境1セット)していること。
・Federationをインストールするマスターノードにhelmをインストールしていること。
・GitLabおよびGitLab Runnerをインストールしていること。
・ECRをサンプルアプリのコンテナイメージを登録するレジストリとして作成していること。

今回、下記のリンクを参考に構築を行っています。
https://qiita.com/FY0323/items/c84b01e7976d45801d48
https://qiita.com/C910/items/772329aa2d59a7f9236d
https://docs.gitlab.com/runner/install/linux-manually.html

Kubernetes Federation V2のインストール

Kubernetes Federation V2は、ホスティングクラスタ内のAPIの単一セットから複数のKubernetesクラスタの構成を調整できる。様々なクラウド、リージョン、オンプレ環境にまたがる複数のクラスタを、1 つの論理的なコンピューティング フェデレーションとして連携させることができる。

Kubernetes Federation V2構成に必要なもの

1. kubefed
2. Control Plane

以下の手順、フェデレーション環境を構築する想定
ユーザーガイド(英語):https://github.com/kubernetes-sigs/kubefed

※以降はルートユーザで実行すること

1. kubefedのインストール

kubefed最新版zipファイルを取得・解凍し、権限を変更して、PATHの下に置く

curl -LO https://github.com/kubernetes-sigs/kubefed/releases/download/v0.1.0-rc6/kubefedctl-0.1.0-rc6-linux-amd64.tgz
tar -zxvf kubefedctl-*.tgz
chmod u+x kubefedctl
mv kubefedctl /bin/

kubefedのバージョンを確認する

kubefedctl version

2. kubefed chart(コントロールプレーン)
kubefed chart最新版のzipファイルを取得する

wget https://github.com/kubernetes-sigs/kubefed/releases/download/v0.1.0-rc6/kubefed-0.1.0-rc6.tgz

ファイルを解凍する

tar -zxvf kubefed-0.1.0-rc6.tgz

kubefed chartをhelmでインストールする

helm install ./kubefed --name kubefed --version=0.1.0-rc6 --namespace kube-federation-system

3. 子クラスタのconfig情報を親クラスタに追加する

 複数のクラスタを結合する方法は以下3つに分けられる。
 今回は追加する子クラスタが2つのみのため、 a.パターン を実施

  a. 親クラスタのconfigファイルに手動で子クラスタの情報を追加する(少数の子クラスタを追加したい場合おすすめ)
  b. load-configで子クラスタのconfigファイルを読み込む(大量の子クラスタを追加したい場合おすすめ)
  c. kubectlコマンドで子クラスタのconfig情報を追加する(認証や鍵データのファイルを所有している場合おすすめ)

手順については、下記のリンクを参照
https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/

 今回はAWS環境上のKubenetesクラスタの名前をaws、OpenStack環境上のKubenetesクラスタの名前をopenstackと指定し、構築します。

4. config情報を確認する

kubectl config get-contexts

出力結果例

[root@k8s-master-host .kube]# kubectl config get-contexts
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
* host host kubernetes-admin1
aws aws kubernetes-admin2       ※上記の手順で追加されたことを確認
openstack openstack kubernetes-admin3     ※上記の手順で追加されたことを確認

5. kubefedctlコマンドで子クラスタ情報を登録する

kubefedctl join <子クラスタのcluster名> --cluster-context <子クラスタのcontext名> --host-cluster-context <親クラスタのcontext名> --v=2

 出力結果例

[root@k8s-master-host .kube]# kubefedctl join aws --cluster-context aws --host-cluster-context host --v=2
I1112 08:58:40.386306 5171 join.go:159] Args and flags: name aws, host: host, host-system-namespace: kube-federation-system, kubeconfig: , cluster-context: aws, secret-name: , dry-run: false
I1112 08:58:40.457042 5171 join.go:238] Performing preflight checks.
I1112 08:58:40.467263 5171 join.go:244] Creating kube-federation-system namespace in joining cluster
I1112 08:58:40.469519 5171 join.go:377] Already existing kube-federation-system namespace
I1112 08:58:40.469541 5171 join.go:252] Created kube-federation-system namespace in joining cluster
I1112 08:58:40.469551 5171 join.go:398] Creating service account in joining cluster: aws
I1112 08:58:40.476832 5171 join.go:408] Created service account: aws-host in joining cluster: aws
I1112 08:58:40.476851 5171 join.go:436] Creating cluster role and binding for service account: aws-host in joining cluster: aws
I1112 08:58:40.491755 5171 join.go:445] Created cluster role and binding for service account: aws-host in joining cluster: aws
I1112 08:58:40.491846 5171 join.go:804] Creating cluster credentials secret in host cluster
I1112 08:58:40.495641 5171 join.go:830] Using secret named: aws-host-token-knwz5
I1112 08:58:40.499789 5171 join.go:873] Created secret in host cluster named: aws-wtlvd
I1112 08:58:40.519647 5171 join.go:280] Created federated cluster resource

6. 子クラスタ情報を確認する

kubectl --context=<親クラスタのcontext名> get kubefedclusters -n kube-federation-system

出力結果例

[root@k8s-master-host ec2-user]# kubectl --context=host get kubefedclusters -n kube-federation-system
NAME READY AGE
host True 2m17s
aws True 15h
openstack True 15h

7. CI/CDパイプラインの検証
7-1. 下記のマニフェストファイルをGitLab上に作成する

①federation用デプロイメントのマニフェスト

各クラスタのアプリケーションのスケーリングやバージョンを管理するリソースを定義する

apiVersion: types.kubefed.io/v1beta1
kind: FederatedDeployment
metadata:
name: test-deployment
namespace: test-namespace
spec:
template:
metadata:
labels:
app: testuser-sample
spec:
replicas: 3 #通常のDeploymentマニフェストと同じく、ReplicaSetを設定することが可能
selector:
matchLabels:
app: testuser-sample
template:
metadata:
labels:
app: testuser-sample
spec:
containers:
- name: testuser-sample
image: <コンテナイメージ>
ports:
- containerPort: 80
placement: #Kubernetes Federation V2の機能であり、登録したcluster nameを指定することで特定のクラスタのみにデプロイすることが可能
clusters:
- name: aws

②federation用NodePortサービスのマニフェスト

各クラスタのネットワークを定義する

apiVersion: types.kubefed.io/v1beta1
kind: FederatedService
metadata:
name: test-service
namespace: test-namespace
spec:
template:
spec:
selector:
app: testuser-sample
type: NodePort
ports:
- name: http
port: 80
nodePort: 30080
placement:
clusters:
- name: openstack
- name: aws

③federation用名前空間のマニフェスト

各クラスタの名前空間を定義する

apiVersion: types.kubefed.io/v1beta1
kind: FederatedNamespace
metadata:
name: test-namespace
namespace: test-namespace
spec:
placement:
clusters:
- name: openstack
- name: aws

7-2. CI/CDの設定ファイル「.gitlab-ci.yml」を作成する

image: gitlab/dind:latest
before_script:
# SSH接続に関する設定
- echo "invoke ssh agent..."
- eval $(ssh-agent -s)
- mkdir -p ~/.ssh
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa  
- chmod 600 ~/.ssh/id_rsa
- '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
stages:
- build
- test
- deploy
build:
stage: build
tags:
- 
script:
# 事前準備
- curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py"
- python3 get-pip.py
## awscliをインストール
- pip install awscli --ignore-installed six
# ECRのログイン
## AWS Configure
- export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID
- export AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY
- export AWS_DEFAULT_REGION=$AWS_ECR_REGION
- export AWS_DEFAULT_OUTPUT=$AWS_DEFAULT_OUTPUT
## ECRログインのコマンドを環境変数として定義する
- login_string=`/usr/local/bin/aws ecr get-login --region ap-northeast-1 --no-include-email | sed 's|https://||'`
## コマンドを実行する
- eval $login_string;
# ビルド
## サンプルアプリのコンテナイメージをビルドする
- docker build -t <サンプルアプリのコンテナイメージ> .
## イメージにタグを付ける
- docker tag <タグ> <AWSアカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/<サンプルアプリのコンテナイメージ>
## ECRのレジストリにプッシュする
- docker push <AWSアカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/<サンプルアプリのコンテナイメージ>
test:
stage: test
tags:
- 
script:
# 事前準備
- curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py"
- python3 get-pip.py
## awscliをインストールする
- pip install awscli --ignore-installed six
# ECRログインパスワードを取得する
## AWS Configure
- export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID
- export AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY
- export AWS_DEFAULT_REGION=$AWS_ECR_REGION
- export AWS_DEFAULT_OUTPUT=$AWS_DEFAULT_OUTPUT
## ECRログインパスワードを環境変数として定義する
- TOKEN=`aws ecr get-login --region ap-northeast-1 --no-include-email | cut -d' ' -f6`
# AWSのMasterノードにデプロイする
## リポジトリにあるマニフェストを配布する 
- scp  .<federation用マニフェストファイル> ec2-user@<federationをインストールしたMasterノード>:/tmp
# test-namespaceを作成する
- ssh ec2-user@<federationをインストールしたMasterノード> sudo kubectl delete ns test-namespace --ignore-not-found
- ssh ec2-user@<federationをインストールしたMasterノード> sudo kubectl delete svc testuser-sample --ignore-not-found
- ssh ec2-user@<federationをインストールしたMasterノード> sudo kubectl apply -f <federation用名前空間のマニフェストファイル>
- sleep 60
## test-secretを作成する
- ssh ec2-user@<federationをインストールしたMasterノード> sudo kubectl create secret docker-registry testuser-secret --docker-server=https://<AWSアカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com --docker-username=AWS --docker-password=$TOKEN --docker-email=none --context=aws -n test-namespace
- ssh ec2-user@<federationをインストールしたMasterノード> sudo kubectl create secret docker-registry testuser-secret --docker-server=https://<AWSアカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com --docker-username=AWS --docker-password=$TOKEN --docker-email=none --context=openstack -n test-namespace
## デプロイ
- ssh ec2-user@federation-host-master sudo kubectl apply -f /tmp/<federation用デプロイメントのマニフェストファイル> -f /tmp/<federation用NodePortサービスのマニフェスト>
## ステータス確認 
- sleep 60
- ssh ec2-user@<federationをインストールしたMasterノード> sudo kubectl get kubefedclusters -n kube-federation-system
- ssh ec2-user@<federationをインストールしたMasterノード> sudo kubectl get deploy -n test-namespace --context aws
- ssh ec2-user@<federationをインストールしたMasterノード> sudo kubectl get pods -n test-namespace --context aws
- ssh ec2-user@<federationをインストールしたMasterノード> sudo kubectl get svc -n test-namespace --context aws
- ssh ec2-user@<federationをインストールしたMasterノード> sudo kubectl describe pods -n test-namespace --context aws
## 後片付け 
- ssh ec2-user@<federationをインストールしたMasterノード> sudo rm -f /tmp/<federation用全てのマニフェストファイル>
## AWSのワーカーノードにあるサンプルアプリの疎通確認
- curl <AWSのワーカーノードのEIP>
deploy:
stage: deploy
tags:
- 
script:
# OpenStackにデプロイする
## OpenStackのcluster nameをJSON型で定義する 
- placement='{\"spec\":{\"placement\":{\"clusters\":[{\"name\":\"aws\"},{\"name\":\"openstack\"}]}}}'
## patchコマンドのシェルスクリプトを作成する
- DEPLOY_PATCH_SHELL="sudo kubectl -n test-namespace patch federateddeployment.types.kubefed.io/test-deployment --type=merge -p '{\"spec\":{\"placement\":{\"clusters\":[{\"name\":\"aws\"},{\"name\":\"openstack\"}]}}}'"
- echo "$DEPLOY_PATCH_SHELL" > /root/deploy_patch.sh
- scp /root/deploy_patch.sh ec2-user@<federationをインストールしたMasterノード>:/tmp
- ssh ec2-user@<federationをインストールしたMasterノード> sudo cat /tmp/deploy_patch.sh
- ssh ec2-user@<federationをインストールしたMasterノード> sudo chmod 777 /tmp/deploy_patch.sh
## シェルスクリプトを実行する
- ssh ec2-user@<federationをインストールしたMasterノード> sudo /tmp/deploy_patch.sh

7-3. パイプラインを実行する

7-4. 「Build」、「Test」、「Deploy」が正常に終わるまで待つ

全体的なパイプライン構成は以下の通り

「Test」ステージで「curl」コマンドが正常に実行したことを確認

7-5. パイプラインで作成されたPodをMaster側で確認する

[root@fed-master ec2-user]# CLUSTER_CONTEXTS="aws openstack"
[root@fed-master ec2-user]# for c in ${CLUSTER_CONTEXTS}; do     echo ----- ${c} -----;     kubectl --context=${c} get pods -n test-namespace; done
----- aws -----
NAME                               READY   STATUS    RESTARTS   AGE
test-deployment-5d4b4bf5d4-79wf5   1/1     Running   0          6m30s
test-deployment-5d4b4bf5d4-fwjhm   1/1     Running   0          6m30s
test-deployment-5d4b4bf5d4-llkr6   1/1     Running   0          6m30s
test-job-h7j6n                     1/1     Running   0          6m28s
----- openstack -----
NAME                               READY   STATUS    RESTARTS   AGE
test-deployment-5d4b4bf5d4-7bsfn   1/1     Running   0          6m27s
test-deployment-5d4b4bf5d4-bhppm   1/1     Running   0          6m27s
test-job-gj6wv                     1/1     Running   0          6m29s
test-job-kxx2s                     1/1     Running   0          6m29s

おわりに

Kubernetes Cluster Federationを利用しマルチクラスタを構築することで、ステージングの種類によって異なるKubernetesクラスタにアプリをデプロイすることができ、マルチテナントでシームレスにCI/CDパイプラインを実行することが出来ました。

今回は実装していませんが実運用を想定した場合、以下のような点を考える必要があります。
・事前に必要なパッケージをインストールしたコンテナイメージを使用する、GitLabでクラスタを追加してKubernetes APIを使用するなどで、パイプライン実行時の処理を最小化する。
・KubernetesのPodに関するセキュリティ設定として適切なPodSecurityPolicyの定義を行い、ホストに影響を与える可能性がある機能を制限し Pod に脆弱性があった場合にクラスタを守れるようにする。

ご覧いただきありがとうございました。

連載シリーズ
テクノロジーコラム
著者プロフィール
浅沼 伸洋
浅沼 伸洋

移動機のアプリ評価、ディープラーニング向けの画像処理基盤の機能追加などの業務を経て、現在はAWS関連の業務に従事。月2回は横浜スタジアムへ野球観戦に行く横浜ファン。