谷本 心 in せろ部屋

はてなダイアリーから引っ越してきました

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でも特に問題なく運用できています。

f:id:cero-t:20211219103727p:plain
今回動かすアプリケーション

環境設定

事前準備

まず前提として、次のことは完了済みとします。

  • AWSアカウントの作成
  • AWS CLIのインストール
  • aws configureが済んでいて、awsコマンドが実行できる(~/.aws/credentialsに必要な情報を記載済み)

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

オプションの -kk8sを対象とします。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 を呼ぶ処理を行うアプリケーションです。

baseurlapplication.yamlapplication-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にリポジトリを作成します。リポジトリはイメージごとに作成する必要があるため helloinvoke という名前のリポジトリをそれぞれ作成します。

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/(リポジトリ名) となっています。

そして、Mavenspring-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アプリケーション用のマニフェストファイルを作成します。

eks/invoke-app.yaml

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.activedapr を指定して、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.typeLoadBalancer である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上で動いているのは確かなのです。意外と簡単でしょ。

f:id:cero-t:20211219103748p:plain
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 GatewayAPIを管理する場合には、対象としてCLBを指定できないため、ALBが必要となります。実際に僕が運用しているシステムでは、CLBではなくALBを利用しています。

ALBを使う場合には、アプリケーションのServiceの type として LoadBalancer ではなく NodePort を指定し、それとは別に Ingress を作成してALBと接続する設定を行います。その辺りの使い方については、クラスメソッドのブログなどが詳しいのでそちらを参考にしてください。

dev.classmethod.jp

いずれにせよ、目的に合わせて柔軟に環境を構築できることは間違いありません。

まとめ

  • eksctlコマンドを使って、Amazon EKSのクラスタを作成しました
  • kubectlコマンドを使って、EKS上にアプリケーションのデプロイを行いました
  • typeLoadBalancer のServiceを作成すると、CLBが自動的に作成され、インターネット経由でアクセスすることができます
  • ALBを利用する場合は typeNodePort のServiceを作成し、Ingress を利用してALBと接続します
  • EKSを試す場合には、使い終わったら削除することを忘れないようにしてください

ところで今回もソースコードには全く手を入れることがありませんでした。このAdvent Calendarを書き始めたときに、最初からk8sの環境で動かすことを想定してサンプルのソースコードを作成したわけではなく、あくまでDaprを使うことだけを想定していたのですが、k8sでも問題なく動いています。

Daprを使うことで環境から切り離され、ローカルPCで直接動かしても、minikubeで動かしても、あるいはAmazon EKSで運用しても、それぞれの設定ファイルを作成するだけで、ソースコードは変更しなくて済んだのです。

これは間違いなく、Daprのメリットだと言えるでしょうね。

それでは、また明日!