AWS
kubernetes
kops

AWSハイブリッド構成にてKubernetesクラスターを構築する(kopsを利用)

More than 1 year has passed since last update.


はじめに

AWSハイブリッド構成とは、Amazon VPCとオンプレミス環境をAWS Direct ConnectやインターネットVPNを利用して接続し、運用する形態のことです。この場合、Amazon VPCは企業内であれば社内ネットワークの一部として扱われる構成となります。また、特に企業内での利用においては、セキュリティ上の理由からVPCにはインターネットゲートウェイはアタッチされておらず、インターネットアクセスはオンプレミス側に用意されているゲートウェイ/プロキシサーバを通るような構成にされることが多いかと思います。

このような構成の場合、Amazon VPC上にKubernetesを自前で構築するのは一苦労です。そもそもKubernetesのマニュアル構築自体も面倒ですし、その上でインターネットに直接出れないといったネットワーク制約もあり、なかなか大変です。

そこで本記事では、このような環境でKubernetesクラスターをkopsを利用してVPC上にお手軽に構築する方法について説明します。


kopsとは

Kubernetesクラスターを構築するためのデプロイツールのひとつです。Kubernetesオフィシャルなツールのひとつとして開発されています。利用環境としては現時点ではAWSのみが公式サポート環境であり、実質AWS向けのKubernetesクラスター構築ツールです。

本記事では、AWSハイブリッド構成においてkopsでKubernetesクラスターを構築する上でのポイントを中心に説明します。kopsそのものについてはQiitaでも既にいくつか記事がありますので、そちらを参照されるとよいでしょう。


AWSハイブリッド構成における課題

AWSハイブリッド構成でプライベートなVPCの構成の場合、下記点の考慮が必要です。


HTTP(S)プロキシの設定が必要

kopsで構築・運用するにはインターネット経由でリソースの取得が必要になってきます(kubeletのインストールなど)。また、AWSの各種サービスを利用する上でもプロキシ設定が必要です(例えば、DockerイメージのレジストリにAmazon ECRを使うなど)。


kopsのデフォルトネットワーク設定であるkubenetはプライベートサブネットで使えない

VPCはインターネットから接続されていないプライベートサブネットとなる場合、デフォルトのkubenetを使うことができないという制約があります。以下、kopsのネットワークのドキュメントより引用です。


Users running --topology private will not be able to choose kubenet networking because kubenet requires a single routing table. These advanced users are usually running in multiple availability zones and NAT gateways are single AZ, multiple route tables are needed to use each NAT gateway.


kubenetはルートテーブルが1つの環境でしか動かず、AZ単位にNATゲートウェイを立てて冗長化する一般的なプライベートネットワークを構成する場合は、複数のルートテーブルが必要になってくるので対応できない、とのことです(実際に試しにkubenetで構築してみたところ、AZをまたいでPOD間通信ができないという現象になりました)。

この話は下記のとおり、kopsのネットワークトポロジーのドキュメントの方にも書かれており、kubenet以外のネットワーク設定を利用する必要があります


In the case of a private cluster you must also set a networking option other than kubenet.



ロードバランサは内部ELBを使う必要あり

AWSを使う場合、KubernetesのServiceやIngressでELBを使うことができますが、プライベートネットワークのためパブリックなELBは使えず、内部ELBを使う必要があります。


kopsによるクラスター構築のポイント

上記課題に対応方法について具体的に説明していきます。なお、kopsではCUIでクラスター構築時にオプションで設定項目を指定できますが、kopsのバージョンによっては対応していない設定項目があるため、本記事ではkopsのYAMLファイルによるクラスター定義での設定例を記載しています。とりあえず適当なオプションでkops create cluster $CLUSTER_NAME--yesなしでクラスター定義だけをまず作成し、kops edit cluster $CLUSTER_NAMEにてクラスター定義を編集しつつ、kops update cluster $CLUSTER_NAME --yesにて詳細設定した内容でクラスターを構築できます。


HTTP(S)プロキシの設定

私がちょうどkopsを触り始めた時期(2017年の秋頃)に、kopsのHTTPプロキシ対応がコミットされました。当時は未リリースのため自分でビルドして使っていましたが、kops 1.8以降であれば簡単に設定できるようになっています!

kopsのHTTPフォワードプロキシのドキュメントに書かれているように、下記のようにkopsのクラスター定義のYAMLにegressProxyを追加してクラスターを構築するだけで、必要な箇所にHTTPプロキシ設定がまとめて行われます。


spec:
egressProxy:
httpProxy:
host: proxy.corp.local
port: 3128
excludes: corp.local,internal.corp.com


ネットワーク設定はkubenet以外を使う

kubenetは使えないため、それ以外のものを利用します。kopsのクラスター定義のnetworkingに設定します。kopsのネットワークのドキュメントによるといくつか利用可能なタイプがあります。例えばflannel-vxlanを使う場合は以下のように設定します。

spec:

networking:
flannel:
backend: vxlan

また、プライベートネットワークのため、kopsのクラスター定義のtopology設定を以下のようにします。

spec:

topology:
dns:
type: Private
masters: private
nodes: private

なお、既存のVPC/サブネットに対してKubernetesクラスターを構築する場合は、下記のようにnetworkCIDRにVPCのネットワークアドレスを、networkIDにVPCのIDを、subnetsにサブネット情報を(既存のサブネットを流用する場合は、idでサブネットIDを)を設定します。既存のVPC/サブネットを利用する方法についての詳細は、kopsにドキュメントに記載があります。

  networkCIDR: 172.16.0.0/24

networkID: vpc-54baa4eb
subnets:
- cidr: 172.16.0.0/25
id: subnet-11e5ee67
name: ap-northeast-1a
type: Private
zone: ap-northeast-1a
- cidr: 172.16.0.128/25
id: subnet-a6113be0
name: ap-northeast-1c
type: Private
zone: ap-northeast-1c


ロードバランサーに内部ELBを利用する


Kubernetes APIサーバのELB

kopsのクラスター定義にて、下記のように設定することで、KubernetesのAPIサーバのフロントに内部ELBを利用することができます。

spec:

api:
loadBalancer:
type: Internal


Service/IngressのELB

こちらはクラスター構築後の話ですが、Kubernetesのドキュメントに記載のとおり、AWSの場合はannotationsservice.beta.kubernetes.io/aws-load-balancer-internalを追加することで、Service/Ingressのロードバランサーに内部ELBを利用することができます。

kind: Service

apiVersion: v1
metadata:
name: ingress-nginx
namespace: ingress-nginx
labels:
app: ingress-nginx
annotations:
service.beta.kubernetes.io/aws-load-balancer-internal: 0.0.0.0/0
# Enable PROXY protocol
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: '*'
# Increase the ELB idle timeout to avoid issues with WebSockets or Server-Sent Events.
service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: '3600'
spec:
type: LoadBalancer
selector:
app: ingress-nginx
ports:
- name: http
port: 80
targetPort: http
- name: https
port: 443
targetPort: https


その他、AWSならではの設定


DockerレジストリにECRを利用する

AWSを使うのであれば、Dockerイメージの管理にはECRを使いたくなります。しかし、kopsで構築したノードに対してデフォルトで設定されるIAMロールは、ECRへのアクセス権をもちません。別途アクセス権を付与する必要があります。これは、kopsのIAMロールに関するドキュメントに記載のとおり、クラスター定義にて下記のようにallowContainerRegistryを設定すればよいです。

spec:

iam:
allowContainerRegistry: true
legacy: false

上記設定でクラスターを構築すると、自動的に作成されるIAMロール(nodes.クラスター名)に下記のようにECRへの参照権限が追加されます。このIAMロールがKubernetesのノード(=EC2インスタンス)のIAMロールとして付与されることで、Kubernetesクラスター内からECRを利用できるようになります。

        {

"Sid": "kopsK8sECR",
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:GetRepositoryPolicy",
"ecr:DescribeRepositories",
"ecr:ListImages",
"ecr:BatchGetImage"
],
"Resource": [
"*"
]
}

なお、注意点として、IAMロールで下回りのEC2インスタンスにECRの参照権限付与してしまうため、セキュリティ上考慮が必要な場合があります。例えば、Kubernetesクラスターを共用サービスとして不特定多数の方に提供した場合は、ECRはだれからも参照できる状態になってしまいます(更新は不可です)。ユースケースによってはこの点注意が必要かと思います。

将来的には、Amazon EKSが正式リリースされればIAMロールの適用をもっと細かく制御できるようになるのではないか、と期待しています。


DNSサーバにRoute 53 Private Hosted Zoneを利用する

AWSを使うのであれば、DNSの管理にはRoute 53を使いたくなります。Kubernetes上にアプリケーションをデプロイした際に、自動的にアプリケーションのDNS登録もできるとユースケースによっては便利です。KubernetesのIncubatorプロジェクトのひとつであるExternalDNSがこれを実現してくれるものですが、kopsを使うとこれを簡単に組み込むことができるようになっています。kopsのドキュメントのとおり、クラスター定義にexternalDnsを下記のように設定してクラスターを構築すると有効化されます。

spec:

externalDns:
watchIngress: true

これでIngressの設定にあるhostを自動的にDNSに登録してくれるようになります。例えば、下記のようなIngress設定をデプロイすると、myapp.example.orgが自動的にRoute 53に登録されます。

apiVersion: extensions/v1beta1

kind: Ingress
metadata:
name: myapp-ingress
namespace: myapp
annotations:
kubernetes.io/ingress.class: nginx
spec:
rules:
- host: myapp.example.org
http:
paths:
- backend:
serviceName: myapp-svc
servicePort: 80

なお、Route 53 Private Hosted ZoneをAWSハイブリッド環境で利用するには、オンプレミス側のDNS環境によっては色々やり方があります。クラスメソッドさんの記事、「AWSハイブリッド構成のDNS設計レシピ」が参考になりますが、例えば、この記事の「設計レシピ4 : Route 53 Private Hosted Zone + Amazon DNSを利用する」のような構成を取る必要があります。

もし、オンプレミス環境に既にDNS権威サーバがあり、Route 53 Private Hosted Zoneにはサブドメインを管理させたい場合は、サブドメインの委任が必要ですが残念ながらRoute 53 Private Hosted Zoneは対応していません。その場合、最近私が書いた「AWSハイブリッド構成にてRoute 53 Private Hosted Zoneでサブドメインを管理する」のような方式をとることで、Route 53を活用することができます。


ログ収集にAmazon CloudWatch Logsを利用する

AWSを使うのであれば、Kubernetesクラスター上で動くアプリケーションのログ管理に、CloudWatch Logsを使いたくなります。アプリケーションはDockerコンテナとして動作しますが、DockerがそもそもCloudWatch Logsに対応しているので、Dockerの設定変更のみで対応することができます。kopsを利用する場合は、クラスター定義にて下記のようにdocker配下でログ周りの設定ができます。

spec:

docker:
version: 17.09.0
logDriver: awslogs
logOpt:
- awslogs-region=ap-northeast-1
- awslogs-group=/k8s-cluster
- awslogs-create-group=false
- tag={{.Name}}/{{.ID}}

また、CloudWatch Logsへの登録のための権限をIAMロールに付与する必要があります。kopsのクラスター定義にて、下記のようにadditionalPolicies設定しておくことで、クラスター構築時にIAMロールを追加設定することができます。

spec:

additionalPolicies:
master: |
[
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": ["*"]
}
]
node: |
[
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": ["*"]
}
]

注意点として、logDriverawslogsにするとkubectl logsでのログ参照はできなくなってしまいます。両立させたいのであれば、logDriverでCloudWatch Logsに転送するのではなく、Fluentdなどを使用してCloudWatch Logsに転送する必要があります。


その他、問題と対策


インターネットゲートウェイ問題

プライベートネットワークのためVPCにインターネットゲートウェイのアタッチは不要なのですが、この状態でkopsで既存のVPCに対してクラスターを構築すると、インターネットゲートウェイがアタッチされておらず正常終了しません。一応、#3031 Skip the creation of a IGW にてプルリクエストが出ていますがマージされていません。現状、クラスター構築時は残念ながら一時的にインターネットゲートウェイをVPCにアタッチさせる必要があります(アタッチされていればよく、ルートテーブル設定でインターネットゲートウェイに通信を流す必要はありません)。


まとめ

(あまりニーズはなさそうですが)AWSハイブリッド環境にてKubernetesクラスターを構築する際のノウハウをいくつか紹介しました。何かの助けになれば幸いです。