AWS Service Brokerを使ってKubernetesでマネージドサービスを管理する

EKSのおかげでEC2・ASG・ELBなどが一式揃ったKubernetes環境を楽に構築できるようになりました。

一方で、上記以外のAWSマネージドサービスに依存したアプリケーションの場合、それらマネージドサービスもKubernetes (つまりマニフェストファイル) の管理下に置いて、アプリケーションと共にプロビジョニングしたいケースがしばしばあります。(かくいう私もkubectl applyでS3バケットとかSQSとか作りたいなーと思っていた次第でした。)

KubernetesのService CatalogとAWSで提供されるService Brokerを組み合わせることでこのユースケースを実現できます。例としてSQSを作成しながら、それらの利用方法をまとめていきます。

Service CatalogとService Broker

(僕にとって) 新しい概念として現れたこの2者について簡単に触れておきます。

Service CatalogおよびService Brokerは、Kubernetesと各社のクラウドインフラが提供するリソース (マネージドサービス) を糊付けするための仕組みで、下図で示す関係となっています。

f:id:ohke:20200430113431p:plain
[1] 抜粋

Service Catalogは、Kubernetesの機能で、Kubernetes APIと複数あるService Brokerを仲介する役割を持ちます
利用したいService BrokerをService Catalogに登録できるので、任意のリソースとKubernetes APIを連携できるようになります。

f:id:ohke:20200430141506p:plain
[2] 抜粋

Service Brokerは、各クラウドインフラベンダが提供するアプリケーションで、Service Catalogからのリクエストを受けて実際にリソースをプロビジョニングします。Open Service Broker API準拠のインタフェースであるため、Kubernetes以外の他のオーケストレーションシステムとも連携しやすいようになっています。

AWS Service Brokerの実装は↓のレポジトリで管理されています。

  • プロビジョニングではCloudFormationが使われています
  • サポートしているマネージドサービスの一覧はここで確認できます
    • AWSのすべてのサービスについて実装されているわけではないので注意してください

github.com

実践: EKSからSQSをプロビジョニング

それではEKSでSQSを生成します。[こちらのポスト]を参考にしながら、次の順番で進めます。

  1. Service Catalogのインストール
  2. AWS Service Brokerのインストール
  3. SQSインスタンスの作成
  4. バインディング
  5. アプリケーションからの参照

aws.amazon.com

前提

  • EKSでクラスタ・ノードグループの作成されていること
  • 作業環境にkubectlがインストールされ、クラスタと接続できていること
  • クラスタにHelmがインストールされていること

Service Catalogのインストール

helmでService Catalogをnamespace: catalogにインストールします。

$ helm repo add svc-catalog https://svc-catalog-charts.storage.googleapis.com
"svc-catalog" has been added to your repositories

$ kubectl create namespace catalog
namespace/catalog created

$ helm install catalog svc-catalog/catalog --namespace catalog --wait
NAME: catalog
LAST DEPLOYED: Thu Apr 30 18:33:36 2020
NAMESPACE: catalog
STATUS: deployed
REVISION: 1
TEST SUITE: None

AWS Service Brokerのインストール

次にService Brokerのインストールです。

その前にDynamoDBとIAMの準備が必要で、prerequisites.yamlをダウンロードして、CloudFormationで適用します。このときaws-service-broker-prerequisites-BrokerUser-*という名前でユーザが作成されますが、この後作成するService Brokerの認証情報として用います。

$ REGION=ap-northeast-1

$ wget https://raw.githubusercontent.com/awslabs/aws-servicebroker/master/setup/prerequisites.yaml

$ BUSERNAME=$(aws cloudformation create-stack \
  --capabilities CAPABILITY_IAM \
  --template-body file://prerequisites.yaml \
  --stack-name  aws-service-broker-prerequisites \
  --output text --query "StackId" \
  --region ${REGION})

$ aws iam create-access-key --user-name ${BUSERNAME} --output json --query 'AccessKey.{KEY_ID:AccessKeyId,SECRET_ACCESS_KEY:SecretAccessKey}'
{
    "KEY_ID": "AAAA",
    "SECRET_ACCESS_KEY": "BBBB"
}

上のCloudFormationスタックが完了したら、namespace: aws-servicebrokerにHelmでインストールします。

$ kubectl create namespace aws-servicebroker
namespace/aws-servicebroker created

$  helm repo add aws-servicebroker https://awsservicebroker.s3.amazonaws.com/charts
"aws-servicebroker" has been added to your repositories

$ helm show chart aws-servicebroker/aws-servicebroker
apiVersion: v1
description: Deploys the AWS Service Broker
name: aws-servicebroker
version: 1.0.1

$ helm install awssb aws-servicebroker/aws-servicebroker --wait \
  --namespace aws-servicebroker --version 1.0.1 --set aws.region=ap-northeast-1 \
  --set aws.accesskeyid=AAAA --set aws.secretkey=BBBB
NAME: awssb
LAST DEPLOYED: Thu Apr 30 22:28:56 2020
NAMESPACE: aws-servicebroker
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
For more information on usage, see https://github.com/awslabs/aws-servicebroker/docs/

$ kubectl get ClusterServiceBrokers
awssb   https://awssb-aws-servicebroker.aws-servicebroker.svc.cluster.local   Ready    3m39s

SQSインスタンスの作成

yamlファイルのフォーマットはサービスごとに異なります。aws-servicebrokerのレポジトリで確認でき、SQSであればこちらに記載があります。

apiVersion: servicecatalog.k8s.io/v1beta1
kind: ServiceInstance
metadata:
  name: sqs-standard-minimal-example
spec:
  clusterServiceClassExternalName: sqs
  clusterServicePlanExternalName: standard
  parameters:

上で定義したsqs_instance.yamlファイルをkubectl applyすることで、CloudFormationのスタックが走り、SQSが生成されます。

$ kubectl apply -f sqs_instance.yaml
serviceinstance.servicecatalog.k8s.io/sqs-standard-minimal-example created

$ kubectl describe ServiceInstance sqs-standard-minimal-example
Name:         sqs-standard-minimal-example
Namespace:    default
Labels:       <none>
Annotations:  API Version:  servicecatalog.k8s.io/v1beta1
Kind:         ServiceInstance
Metadata:
  Creation Timestamp:  2020-04-30T13:38:13Z
  Finalizers:
    kubernetes-incubator/service-catalog
  Generation:        1
  Resource Version:  185
  Self Link:         /apis/servicecatalog.k8s.io/v1beta1/namespaces/default/serviceinstances/sqs-standard-minimal-example
  UID:               d6aad2c5-8ae7-11ea-abb5-9e806396566e
Spec:
  ...
Events:                           <none>

SQSの名前やタグを見ると、どのクラスタのService Brokerで生成されたものかなどがわかります。

f:id:ohke:20200501112859p:plain

バインディング

最後に生成されたSQSをバインディングします。sqs-standard-minimal-exampleを指定したsqs_binding.yamlを作成します。

apiVersion: servicecatalog.k8s.io/v1beta1
kind: ServiceBinding
metadata:
  name: sqs-binding
spec:
  instanceRef:
    name: sqs-standard-minimal-example

同じくkubectl applyで適用すると、QUEUE_NAMEやQUEUE_URLというsecretsが作成されます。

$ kubectl apply -f sqs_binding.yaml
servicebinding.servicecatalog.k8s.io/sqs-binding created

$ kubectl describe ServiceBinding sqs-binding
Name:         sqs-binding
Namespace:    default
Labels:       <none>
Annotations:  API Version:  servicecatalog.k8s.io/v1beta1
Kind:         ServiceBinding
Metadata:
  Creation Timestamp:  2020-05-01T01:55:40Z
  Finalizers:
    kubernetes-incubator/service-catalog
  Generation:        1
  Resource Version:  337
  Self Link:         /apis/servicecatalog.k8s.io/v1beta1/namespaces/default/servicebindings/sqs-binding
  UID:               dc32e4cc-8b4e-11ea-abb5-9e806396566e
Spec:
  ...
Events:
  Type    Reason              Age   From                                Message
  ----    ------              ----  ----                                -------
  Normal  InjectedBindResult  14s   service-catalog-controller-manager  Injected bind result

$ kubectl describe secrets/sqs-binding
Name:         sqs-binding
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  Opaque

Data
====
QUEUE_NAME:                 80 bytes
QUEUE_URL:                  134 bytes
SQS_AWS_ACCESS_KEY_ID:      20 bytes
SQS_AWS_SECRET_ACCESS_KEY:  40 bytes
DEAD_LETTER_QUEUE_ARN:      0 bytes
DEAD_LETTER_QUEUE_NAME:     0 bytes
DEAD_LETTER_QUEUE_URL:      0 bytes
QUEUE_ARN:                  120 bytes

アプリケーションからの参照

Podでは上のsecretを環境変数にセットすることで、アプリケーションから参照できるようになります。

apiVersion: v1
kind: Pod
metadata:
  name: example-app
  labels:
    name: example-app
spec:
  containers:
  - name: example-app
    image: busybox
    command: ["sh", "-c", "printenv"]
    env:
    - name: EXAMPLE_SQS_URL
      valueFrom: { secretKeyRef: { name: sqs-binding, key: QUEUE_URL } }

試しにprintenvコマンドを実行してログを見ると、EXAMPLE_SQS_URLに作成したSQSのURLが入ってることが確認できます。

...
EXAMPLE_SQS_URL=https://sqs.ap-northeast-1.amazonaws.com/123456789012/aws-service-broker-sqs-d6aXXXX
...

まとめ

今回はService CatalogとService Brokerを使ってKubernetesからAWSマネージドサービスをプロビジョニングする方法について整理しました。

マニフェストファイルとkubectlコマンドで環境構築を完結できるのが利点ですが、一方でプロビジョニングされるリソースの名前などを自由に設定できないので、Kubernetes環境 "外" のシステムからはどのリソースがどの環境と紐付いているのかがわかりにくい欠点があります。
なのでユースケースとしては、Kubernetes環境内で閉じて利用されるマネージドサービスのみに限定したほうが簡潔かと思いました。