Dapr Advent Calendar 19日目 - DaprをAmazon EKSで使う
こんにちは Dapr Advent Calendar 19日目です。ついに最後の1週間に突入しました。3日目くらいの時に「ちょっとこれ無理かな」とか思ってたんですけど、人間、やってみれば意外といけるもんですね!
DaprをAmazon EKSで運用する
さて、これまでk8s + Daprのアプリケーションをローカルのminikube上で動かしてきましたが、今回はAmazon EKS上で動かしてみます。Daprはマイクロソフトが中心に開発しているOSSなので、Azureのほうがサポートされているミドルウェアが多かったりドキュメントが多かったりするのですが、AWSでも特に問題なく運用できています。
環境設定
事前準備
まず前提として、次のことは完了済みとします。
AWSを日常的に利用している人は、この辺りは既に終わっていると思いますので、説明は割愛します。
また、これまでのAdvent Calendarで利用してきた次のツールもインストール済みとします。
- Dapr CLI
- Docker
- kubectl
もしまだインストールしていなければ、インストールしておいてください。
eksctlのインストール
上に記載したツール群に加えて、eksctlというCLIツールをインストールしてください。これはAmazon EKSをコマンドラインで操作するもので、ちょうどminikubeのCLIに近いものです。
https://docs.aws.amazon.com/eks/latest/userguide/eksctl.html
これで準備は完了です。
EKS環境の構築
EKSクラスタの作成
まず、eksctlコマンドを使ってEKSクラスタを作成します。
eksctl create cluster \ --name=dapr-test \ --version 1.21 \ --region ap-northeast-1 \ --zones ap-northeast-1a,ap-northeast-1c
僕のアカウントでは --zones
オプションをつけずに実行したら「ap-northeast-1bにキャパシティがないよ」というエラーが起きたので、AZとして -1a
と -1c
を指定するようにしました。
なお、クラスタの作成に失敗すると基本的にはロールバックされるのですが、状況次第ではCloudFormationが削除されないことがあるので、残っているようなら手で削除してください。
ちなみに作成したEKSクラスタを放置すると(m5.largeのEC2インスタンス2つも合わせて)月2万円くらい掛かるので、不要になればすぐにEKSクラスタを削除して想定外の課金にならないよう気をつけてくださいね。
kubectlからEKSクラスタにアクセスする
eksctlでk8sのクラスタを作成すると、kubectlの向き先が作成したEKSクラスタになっているはずです。念のため確認しておきましょう。
kubectl config current-context
次のように表示されるはずです。
(AWSアカウント名)@dapr-test.ap-northeast-1.eksctl.io
もしminikubeなど別のクラスタを参照している場合は、kubectl config get-contexts
でEKSのクラスタ名を確認したうえで
kubectl config use-context (AWSアカウント名)@dapr-test.ap-northeast-1.eksctl.io
などでkubectlの向き先がEKSにするようにしてください。
EKSクラスタを他の人(アカウント)が作成した場合など、手元のコンテキスト一覧にEKSクラスタがない場合は、次のコマンドでコンテキストを取得してください。
aws eks --region ap-northeast-1 update-kubeconfig --name dapr-test
これでkubectlからEKSにアクセスできるようになるはずです。
クラスタ情報を確認しておきましょう。
kubectl cluster-info
次のように表示されます。
Kubernetes control plane is running at https://(アカウントID).gr7.ap-northeast-1.eks.amazonaws.com CoreDNS is running at https://(アカウントID).gr7.ap-northeast-1.eks.amazonaws.com/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
これでkubectlからEKSにアクセスしていることを確認できました。
Dapr環境の構築
それではEKS上にDapr環境を構築しましょう。
dapr init -k
オプションの -k
でk8sを対象とします。kubectlが向いているk8s上にDaprの環境が構築されます。
初期化が終わったら、podの一覧を見てみましょう。
kubectl get pods -A
次のように表示されるはずです。
NAMESPACE NAME READY STATUS RESTARTS AGE dapr-system dapr-dashboard-57b4db56fc-vxmcs 1/1 Running 0 89s dapr-system dapr-operator-5b4b68b5c5-cgh28 1/1 Running 0 89s dapr-system dapr-placement-server-0 1/1 Running 0 89s dapr-system dapr-sentry-c6b746cdf-pxgdx 1/1 Running 0 89s dapr-system dapr-sidecar-injector-6f749dbf87-qcg7r 1/1 Running 0 89s kube-system aws-node-fp8m2 1/1 Running 0 15m kube-system aws-node-rp5qq 1/1 Running 0 15m kube-system coredns-76f4967988-7psgm 1/1 Running 0 24m kube-system coredns-76f4967988-fjlrb 1/1 Running 0 24m kube-system kube-proxy-xk8wm 1/1 Running 0 15m kube-system kube-proxy-ztkr8 1/1 Running 0 15m
Daprやk8s関係のpodが起動していることが分かります。
これでEKS上でDaprを動かす準備ができました。
アプリケーションのデプロイ
続いて、Hello Worldアプリケーションと、それを呼び出すInvokeアプリケーションをEKS上で動かしてみましょう。ソースコードはGitHubにあります。
https://github.com/cero-t/dapr-advent-2021
この中にある「hello」と「invoke」を利用します。
作成したアプリケーションのおさらい
helloアプリケーション
helloアプリケーションの HelloController
をおさらいしておきましょう。
(hello) HelloController.java
@RestController public class HelloController { @GetMapping("/hello") public Map<String, String> hello() { return Map.of("message", "Hello, world!"); } }
/hello
というエンドポイントでメッセージを返すだけのアプリケーションです。
invokeアプリケーション
invokeアプリケーションの InvokeController
もおさらいしておきましょう。
(invoke) InvokeController.java
@RestController public class InvokeController { private RestTemplate restTemplate; @Value("${baseUrl}") private String baseUrl; public InvokeController(RestTemplate restTemplate) { this.restTemplate = restTemplate; } @GetMapping("/invokeHello") public Map<String, ?> invokeHello() { Map<?, ?> result = restTemplate.getForObject(baseUrl + "/hello", Map.class); return Map.of("baseUrl", baseUrl, "remoteMessage", result); } }
/invokeHello
というエンドポイントで、上の /hello
を呼ぶ処理を行うアプリケーションです。
baseurl
は application.yaml
と application-dapr.yaml
に定義されており、Springのプロファイルが dapr
の時は次の設定が利用されます。
(invoke) application-dapr.yaml
baseUrl=http://localhost:${DAPR_HTTP_PORT}/v1.0/invoke/hello-app/method
DaprのInvoke APIを利用したURLです。これを使ってhelloアプリケーションを呼び出します。
アプリケーションのイメージ作成
イメージの置き場所となるECRの作成
続いて、アプリケーションのイメージを作成したいのですが、もちろんEKSからローカルPCにあるイメージレジストリを参照できるわけではありません。EKSを使う場合には、Docker HubやAmazon ECRなどを使うことになります。ここではEKSと距離の近いECRにイメージを置くことにしましょう。
次のコマンドでECRにリポジトリを作成します。リポジトリはイメージごとに作成する必要があるため hello
と invoke
という名前のリポジトリをそれぞれ作成します。
export AWS_REGION=ap-northeast-1 aws ecr create-repository --repository-name hello --region ap-northeast-1 aws ecr create-repository --repository-name invoke --region ap-northeast-1
これでリポジトリの作成は完了です。
イメージの作成
続いてイメージを作成します。上で作ったECRのリポジトリにpushできるよう、イメージ名をECRリポジトリのURIに合わせる必要があります。
まずはhelloアプリケーションのイメージ作成です。次のようなコマンドになります。
cd (GitHubのディレクトリパス)/dapr-advent-2021 export HELLO_IMAGE=$(aws ecr describe-repositories --repository-names hello --query 'repositories[0].repositoryUri' --output text --region ap-northeast-1) ./mvnw spring-boot:build-image -pl hello -Dspring-boot.build-image.imageName=${HELLO_IMAGE}:1.0.0
(オプションが多くて長くなるため、改行を挟んでいます)
awsコマンドを用いてECRリポジトリのURLを取得し、それを環境変数 HELLO_IMAGE
として保持します。リポジトリのURLは (アカウントID).dkr.ecr.ap-northeast-1.amazonaws.com/(リポジトリ名)
となっています。
そして、Mavenの spring-boot:build-image
コマンドでイメージを作成するのですが、ここで -Dspring-boot.build-image.imageName
オプションを使い、上で取得したECRリポジトリのURLをイメージ名として指定しています。
イメージの作成に成功したら、invokeアプリケーションも同様にイメージを作成します。
export INVOKE_IMAGE=$(aws ecr describe-repositories --repository-names invoke --query 'repositories[0].repositoryUri' --output text --region ap-northeast-1) ./mvnw spring-boot:build-image -pl invoke -Dspring-boot.build-image.imageName=${INVOKE_IMAGE}:1.0.0
作成が完了したら、念のため確認しておきましょう。
docker images | grep hello
(アカウントID).dkr.ecr.ap-northeast-1.amazonaws.com/hello 1.0.0 d62af7e6faa8 41 years ago 261MB hello 1.0.0 73b3e3b9795a 41 years ago 261MB
docker images | grep invoke
(アカウントID).dkr.ecr.ap-northeast-1.amazonaws.com/invoke 1.0.0 d38a74e45a8f 41 years ago 261MB invoke 1.0.0 bf464e49d0e6 41 years ago 261MB
ECRのURIがついたイメージができているはずです。もし以前に作ったhelloやinvokeのイメージがあれば、一緒に表示されます。
イメージのプッシュ
それでは作成したイメージをECRにプッシュしましょう。ECRにログインしてからでないとプッシュできないので、次のコマンドでログインをします。
export ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account) aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin https://${ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com
アカウントIDやログインパスワードをawsコマンドで取得して、それを使ってログインしています。
これでログインに成功したら、イメージをプッシュします。
docker push ${HELLO_IMAGE}:1.0.0 docker push ${INVOKE_IMAGE}:1.0.0
イメージを作成した際に使用した環境変数をここでも使っています。
これでECRにイメージがプッシュされました。
アプリケーションをEKSにデプロイ
それでは、ECRにプッシュしたイメージを使って、EKS上にアプリケーションをデプロイしてみましょう。
helloアプリケーションのマニフェストファイルを作成
helloアプリケーションをデプロイするためのマニフェストファイルを次のように作成します。
eks/hello-app.yaml
apiVersion: apps/v1 kind: Deployment metadata: name: hello-app labels: app: hello spec: replicas: 1 selector: matchLabels: app: hello template: metadata: labels: app: hello annotations: dapr.io/enabled: "true" dapr.io/app-id: "hello-app" dapr.io/app-port: "8080" spec: containers: - name: hello image: ***.dkr.ecr.ap-northeast-1.amazonaws.com/hello:1.0.0 ports: - containerPort: 8080 imagePullPolicy: IfNotPresent
以前に作ったものと同様です。イメージ名にECRリポジトリのURIを指定する必要があるため ***
の部分を自分のアカウントIDに置き換えてください。また、helloアプリケーションには外部からアクセスしないため、Serviceは作成しません。
invokeアプリケーションのマニフェストファイルを作成
続いて、invokeアプリケーション用のマニフェストファイルを作成します。
apiVersion: apps/v1 kind: Deployment metadata: name: invoke-app labels: app: invoke spec: replicas: 1 selector: matchLabels: app: invoke template: metadata: labels: app: invoke annotations: dapr.io/enabled: "true" dapr.io/app-id: "invoke-app" dapr.io/app-port: "8081" spec: containers: - name: invoke image: ***.dkr.ecr.ap-northeast-1.amazonaws.com/invoke:1.0.0 ports: - containerPort: 8081 imagePullPolicy: IfNotPresent env: - name: spring.profiles.active value: dapr --- kind: Service apiVersion: v1 metadata: name: invoke-svc labels: app: invoke spec: selector: app: invoke ports: - protocol: TCP port: 8081 targetPort: 8081 type: LoadBalancer
これも以前に作ったものと同様です。イメージ名の ***
の部分は自分のアカウントIDに置き換えてください。
環境変数の spring.profiles.active
に dapr
を指定して、application-dapr.yaml
の設定が有効になるようにしています。
また、こちらは外部からアクセスするため、Serviceを作成しています。
アプリケーションのデプロイ
それではアプリケーションをデプロイしましょう。デプロイ方法はminikubeでもEKSでも変わりません。
cd (GitHubのディレクトリパス)/dapr-advent-2021 kubectl apply -f eks/hello-app.yaml kubectl apply -f eks/invoke-app.yaml
コマンドに成功したらしばらく待って、podの一覧を見てみましょう。
kubectl get pods
NAME READY STATUS RESTARTS AGE hello-app-6f97c6ff57-dqqhd 2/2 Running 0 30s invoke-app-674c8bfb75-zpdrc 2/2 Running 0 33s
hello-appとinvoke-appが起動していればOKです。
アプリケーションへのアクセス
さて、続いてアプリケーションへのアクセスを行います。
Amazon EKSではk8s上に spec.type
が LoadBalancer
であるServiceを作成すると、自動的にCLB(Classic Load Balancer)が作成され、ポートが外部に公開されます。作成された時点で世界中からアクセスできてしまうため、注意してください。
AWSのコンソールから確認するなり、次のコマンドで確認するなりして、CLBのアドレスを確認します。
aws elb describe-load-balancers --region ap-northeast-1
アドレスの確認ができたら、curlコマンドでアクセスします。
curl (CLB名).ap-northeast-1.elb.amazonaws.com:8081/invokeHello
次の結果が表示できるはずです。
{ "baseUrl": "http://localhost:3500/v1.0/invoke/hello-app/method", "remoteMessage": { "message": "Hello, world!" } }
このAdvent Calendarをずっと読んでる人には見慣れたメッセージが表示されました。invokeアプリケーションからDaprを経由してhelloアプリケーションのメッセージを取得することができています。
k8sやeksctlコマンドを使っていると、どこでクラスタ動いているのかをあまり強く意識せず使えてしまえるため忘れがちになりますが、CLBのアドレスでアクセスできている以上、Amazon EKS上で動いているのは確かなのです。意外と簡単でしょ。
後片付け
無事にEKS上で動作確認ができたので、忘れないうちに環境を削除しておきましょう。そのまま残していてはセキュリティ的にも課金的にも危険です。
まずはデプロイしたアプリケーションを削除します。Serviceを削除する際に、一緒にCLBも削除されます。
kubectl delete -f eks
CLBの削除に時間が掛かるため、いつもより応答が遅くなる可能性があります。
続いて、ECRの削除を行います。
aws ecr delete-repository --repository-name hello --region ap-northeast-1 --force aws ecr delete-repository --repository-name invoke --region ap-northeast-1 --force
最後に、EKSクラスタ自体を削除します。
eksctl delete cluster --name dapr-test --region ap-northeast-1
これで削除は完了です。
AWS上でどのように運用するか
今回はAmazon EKS上にhelloアプリケーションとinvokeアプリケーションのみをデプロイしましたが、もちろん他のアプリケーションをデプロイすることも可能です。
また、これまで使ってきたRabbitMQやPostgreSQLなどは、k8s上にデプロイするのではなく、マネージドサービスのAmazon MQ for RabbitMQやAmazon RDS for PostgreSQL、あるいはAmazon Aurora PostgreSQLなどを利用することも可能です。
それぞれのマネージドサービスでインスタンスを作成し、Daprの接続先がそれぞれのインスタンスになるよう指定し、適切にセキュリティグループを設定すればアクセスができます。その辺りはAWSの説明になりすぎますし、説明すると長くなるため今回は割愛しました。
ところで、今回はk8sのLoadBalancerを利用し、AWSのCLBが自動的に作成されることを確認しました。ただ、たとえばAPI GatewayでAPIを管理する場合には、対象としてCLBを指定できないため、ALBが必要となります。実際に僕が運用しているシステムでは、CLBではなくALBを利用しています。
ALBを使う場合には、アプリケーションのServiceの type
として LoadBalancer
ではなく NodePort
を指定し、それとは別に Ingress
を作成してALBと接続する設定を行います。その辺りの使い方については、クラスメソッドのブログなどが詳しいのでそちらを参考にしてください。
いずれにせよ、目的に合わせて柔軟に環境を構築できることは間違いありません。
まとめ
- eksctlコマンドを使って、Amazon EKSのクラスタを作成しました
- kubectlコマンドを使って、EKS上にアプリケーションのデプロイを行いました
type
がLoadBalancer
のServiceを作成すると、CLBが自動的に作成され、インターネット経由でアクセスすることができます- ALBを利用する場合は
type
がNodePort
のServiceを作成し、Ingress
を利用してALBと接続します - EKSを試す場合には、使い終わったら削除することを忘れないようにしてください
ところで今回もソースコードには全く手を入れることがありませんでした。このAdvent Calendarを書き始めたときに、最初からk8sの環境で動かすことを想定してサンプルのソースコードを作成したわけではなく、あくまでDaprを使うことだけを想定していたのですが、k8sでも問題なく動いています。
Daprを使うことで環境から切り離され、ローカルPCで直接動かしても、minikubeで動かしても、あるいはAmazon EKSで運用しても、それぞれの設定ファイルを作成するだけで、ソースコードは変更しなくて済んだのです。
これは間違いなく、Daprのメリットだと言えるでしょうね。
それでは、また明日!