はじめに
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でkubernetes環境構築〜kopsでクラスタ作成〜
- 【メモ】 AWSにkopsを使ってkubernetesのクラスタを構築する
- 既存のAWS環境にkopsでsubnetを指定してkubernetesを構築する(master冗長化)
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の場合はannotations
でservice.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": ["*"]
}
]
注意点として、logDriver
をawslogs
にするとkubectl logs
でのログ参照はできなくなってしまいます。両立させたいのであれば、logDriver
でCloudWatch Logsに転送するのではなく、Fluentdなどを使用してCloudWatch Logsに転送する必要があります。
その他、問題と対策
インターネットゲートウェイ問題
プライベートネットワークのためVPCにインターネットゲートウェイのアタッチは不要なのですが、この状態でkopsで既存のVPCに対してクラスターを構築すると、インターネットゲートウェイがアタッチされておらず正常終了しません。一応、#3031 Skip the creation of a IGW にてプルリクエストが出ていますがマージされていません。現状、クラスター構築時は残念ながら一時的にインターネットゲートウェイをVPCにアタッチさせる必要があります(アタッチされていればよく、ルートテーブル設定でインターネットゲートウェイに通信を流す必要はありません)。
まとめ
(あまりニーズはなさそうですが)AWSハイブリッド環境にてKubernetesクラスターを構築する際のノウハウをいくつか紹介しました。何かの助けになれば幸いです。