はじめに

前回はAzure Kubernetes Service(AKS)を使ってKubernetesクラスター上でコンテナ化アプリケーションを実行する方法について説明しました。今回はマイクロサービスアーキテクチャのアプリケーション構築を支援する「Dapr」を使ってAKS上にアプリケーションを構築していく方法について説明していきます。

Azure Kubernates Serviceでマイクロサービスアプリケーションを動かそう

AKS上にマイクロサービスアーキテクチャのアプリケーションを構築する方法には様々なものがあります。今回はDaprというツールを使ったマイクロサービスの開発と運用方法について説明します。

マイクロサービスアーキテクチャとは

まずはマイクロサービスアーキテクチャについて説明します。マイクロサービスアーキテクチャとは複数の小規模なサービス(アプリケーション)を組み合わせて一つのアプリケーションを構築していくソフトウェアの開発手法です。これまでは一つの大きなアプリケーションを構築する手法(モノリシック(一枚岩)アーキテクチャ)が一般的でしたが、コンテナ技術の発展によりアプリケーションをサービス単位で分割して開発・実行することが容易になったことからマイクロサービスアーキテクチャが注目されるようになりました。
マイクロサービスアーキテクチャはサービス単位での開発が可能のため、各サービスは個別に言語やフレームワークを選択して開発を行い、個別にデプロイできます。
Kubernetesを使用する場合、各マイクロサービスはそれぞれが独立したコンテナーとしてクラスター上にデプロイされ、お互いに連携を取って一つのアプリケーションとしての機能を提供していきます。

Daprとは

Daprという名称は「Distributed Application Runtime(分散アプリケーションランタイム)」を略したものです。分散アプリケーションとはマイクロサービスを指しており、マイクロサービスアプリケーションの開発や実行を簡略化・サポートするAPI群を提供する実行基盤(ランタイム)という位置づけとなっています。Kubernetes上では各マイクロサービスアプリケーションはポッド単位で分割されますが、Daprは各マイクロサービス用のポッド内にデプロイされます。このようにアプリケーションのコンテナに隣接してデプロイされるコンテナは、サイドカーコンテナと呼ばれます。サイドカーコンテナは各マイクロサービスのポッドが連携する際の仲介役として機能します。
AKSでは、クラスター拡張機能と呼ばれる機能でKubernetesクラスター内にDaprを容易に追加することができるようになっています。

Daprを使ったマイクロサービスアプリケーションをAKS上に構築しよう

ここからは実際にDaprを使ったマイクロサービスアプリケーションをAKS上に構築していきます。今回はHTTPリクエストでJSON形式のメッセージを受け取り、それをRedisに保存するアプリケーションをDaprを使って開発していきます。

AKSにDaprを導入する

まずはAKSでDaprが利用できるようにするために、AKSのクラスター拡張機能を利用してDaprをKubernetesクラスターに追加していきます。なおAKSのクラスターを未作成の場合は、前回の内容を参考にクラスターの作成を行って下さい。

Azure CLIからAKSにDapr拡張機能を追加する

az k8s-extension create --cluster-type managedClusters \
--resource-group zerokara \
--cluster-name zerokara-dapr \
--name myDaprExtension \
--extension-type Microsoft.Dapr

「az k8s-extension create」コマンドでAKSに拡張機能を追加できます。「extension-type」オプションで「Microsoft.Dapr」を指定することでDaprが追加されます。「cluster-type」は「managedClusters」固定で、「resource-group」、「cluster-name」にはそれぞれAKSクラスターの所属するリソースグループ名とクラスター名を入力します。「name」には作成する拡張機能の名称を設定します。
コマンドを実行してしばらく待つと拡張機能の追加が完了し、Daprを利用できるようになります。

Redisの構築

次にアプリケーションのデータを保存するRedisを用意します。今回はAzureのマネージドサービスであるAzure Cache for Redisを使ってRedisインスタンスを作成します。AzureポータルからAzure Cache for Redisのサービスを選択し、「作成」ボタンからインスタンスを新規作成していきます。

  • Azure Cache for RedisでRedisインスタンスを作成する

サブスクリプションとリソースグループは任意のものを選択します。DNS名と場所には任意の名称と場所を入力します。キャッシュの種類でRedisキャッシュのスペックを決定します。今回は開発用途のため最小構成である「Basic C0」のプランを選択します。基本情報の入力が完了したら「レビューと作成」ボタンを選択し、「作成」ボタンを選択してRedisインスタンスの作成を開始します。しばらく待つとインスタンスの作成が完了します。
Redisインスタンスの作成が完了したら以下のコマンドをAzure CLIから実行します。

RedisインスタンスのアクセスキーをKubernatesシークレットとして保存する

$ az aks get-credentials --resource-group zerokara --name zerokara-dapr

$ kubectl create secret generic redis --from-literal=redis-password=<Redisのプライ    マリアクセスキー>

まず「az aks get-credentials」コマンドでAzure CLIが接続するAKSのクラスターを指定し、Kubernatesのインスタンスを操作するためのkubectlコマンドが利用できるようにします。次に「kubectl create secret」コマンドでパスワードなどの機密情報を保持するKubernatesの「シークレット」という種類のリソースを作成していきます。先程作成したRedisインスタンスをAzureポータルで表示し、「アクセスキー」のメニューにある「プライマリ」に表示されているアクセスキーをコピーしてコマンドに入力して実行します。

シークレットが作成できたら、RedisインスタンスにアクセスするためのDaprコンポーネントである状態ストアをAKS上に作成していきます。状態ストアはKubernetesのマニュフェストファイルの形式で定義していきます。deployフォルダを作成して「redis.yaml」ファイルを記述します。

Redisインスタンスにアクセスするための状態ストアのマニュフェスト(deploy/redis.yaml)

apiVersion: dapr.io/v1alpha1 # DaprのAPIリソースを使用する・・・①
kind: Component
metadata:
  name: redisstore
spec:
  type: state.redis
  version: v1
  metadata:
  - name: redisHost
    value: zerokara-dapr.redis.cache.windows.net:6380 # Azure Cache for Redisのホスト名を設定・・・②
  - name: redisPassword
    secretKeyRef: # Redisのパスワードを保持するシークレット名とキー名を設定・・・③
      name: redis
      key: redis-password
  - name: enableTLS
    value: true
auth:
  secretStore: kubernetes

状態ストアはDaprが提供するComponentというリソースで実装されているため、先頭の「apiVersion」はDaprのAPIグループを指定します(①)。「metadata.name」はリソースを識別できる任意の名称を入力します。ここでは「redisstore」という名称にします。「spec.type」ではDaprのComponentの種類を指定します。Redisを使った状態ストアの場合は「state.redis」と指定します。「redisHost」というメタデータにはRedisのホスト名を指定します(②)。Azure Cache for Redisの場合、「.redis.cache.windows.net:6380」とホスト名を設定します。「redisPassword」メタデータではRedisのパスワードを設定します。「secretKeyRef」というフィールドを使用すると、Kubernetesのシークレットに保存したRedisパスワードを参照できるようになります(③)。ここには先程「kubectl create secret」で作成した際に指定したシークレットの名前とキーを設定します。
マニュフェストファイルを作成したら、AKSにマニュフェストを適用します。

Redis用状態ストアのマニュフェストをAKSに適用する

$ kubectl apply -f ./deploy/redis.yaml

「component.dapr.io/redisstore created」といったメッセージが表示されれば、状態ストアの作成は成功です。

AKSにデプロイするアプリケーションの作成

Redisインスタンスの作成およびその状態ストアとなるDaprコンポーネントまで作成できたら、Redisにデータを保存したりデータを取得するアプリケーションを実装していきます。今回もFlaskを使ったWebアプリケーションをPythonコードで実装していきます。

(app/message/app.py)

# Flaskのインポート
from flask import Flask
from flask import request

# Requestsモジュールのインポート・・・①
import requests

# Flaskの使用
app = Flask(__name__)

# Daprの状態ストアのURL・・・②
state_url = 'http://localhost:3500/v1.0/state/redisstore'

# メッセージ登録・取得用のメソッド・・・③
@app.route('/message', methods=['POST', 'GET'])
def message():

    # メッセージ登録処理(POSTメソッド)・・・③-1
    if request.method == 'POST':
        message = [{'key':'message', 'value': request.json['data']}]
        response = requests.post(state_url, json=message, headers={'Content-Type': 'application/json'})
        return 'message posted.', 200

    # メッセージ取得処理(GETメソッド)・・・③-2
    else:
        headers = {"content-type": "application/json"}
        response = requests.get(state_url + '/message', headers=headers)
        return response.text

if __name__ == "__main__":
    app.run(host="0.0.0.0", port="5000", debug=True)

このWebアプリケーションではリクエストされたメッセージ(JSON)をRedisに保存するためのエンドポイントと、現在Redisに保存されている情報を返却するためのエンドポイントを実装しています。
まずはFlaskとRequestsモジュールのインポートを行います(①)。RequestsモジュールはこのアプリケーションからRedisの状態ストアに対してHTTPリクエストを送信する際に使用します。状態ストアへのアクセスはDaprのサイドカーコンポーネントを経由して行われるため、URLにはサイドカーの宛先である「localhost:3500」を指定します(②)。Webアプリケーションからのリクエストを受け取ったサイドカーはURL内の状態ストア名(redisstore)からアクセスすべき状態ストアを特定してリクエストを送信します。
「message」メソッドに実際の処理を実装します(③)。JSON形式のメッセージを受け取り、Redisに対してそのメッセージの登録リクエストを行っているのがPOSTメソッドの処理です(③-1)。先述の通りリクエストはDaprのサイドカーに対して行っており、状態ストアを経由してRedisに到達します。メッセージ取得処理では反対にRedisから現在登録されているメッセージを取得しています(③-2)。こちらも処理の流れは登録処理と同様となっています。

Webアプリケーションが実装できたらコンテナイメージを作成していきます。Webアプリケーション用のDockerfileを「app/message」フォルダに作成します。

Webアプリケーション用のDockerfile(app/message/Dockerfile)

FROM python:3.7-alpine
WORKDIR /app
COPY . .
RUN pip install requests flask
ENTRYPOINT [ "python" ]
CMD [ "app.py" ]

今回はベースイメージとしてpythonのコンテナイメージを使い、必要なモジュールをpythonのパッケージ管理ツールである「pip」コマンドを使ってインストールしています。
Dockerfileを作成したら、dockerコマンドでコンテナイメージの作成とACRへのイメージのプッシュを行います。

コンテナイメージの作成とACRへのプッシュ

$ docker build -t zerokara-message:latest ./app/message
$ docker tag zerokara-message zerokara.azurecr.io/zerokara-message:latest
$ docker push zerokara.azurecr.io/zerokara-message:latest

WebアプリケーションがACRにプッシュできたら、AKSにデプロイするためのマニフェストファイルを作成します。

Webアプリケーション用のマニフェストファイル(deploy/message.yaml)

apiVersion: v1 # Webアプリケーション用のサービス定義・・・①
kind: Service
metadata:
  name: messageapp
  labels:
    app: message
spec:
  selector:
    app: message
  ports:
  - protocol: TCP
    port: 80
    targetPort: 5000
  type: LoadBalancer

---
apiVersion: apps/v1 # Webアプリケーション用のデプロイ定義・・・②
kind: Deployment
metadata:
  name: messageapp
  labels:
    app: message
spec:
  replicas: 1
  selector:
    matchLabels:
      app: message
  template:
    metadata:
      labels:
        app: message
      annotations: # Daprサイドカーを挿入するための定義・・・②-1
        dapr.io/enabled: "true"
        dapr.io/app-id: "messageapp"
        dapr.io/app-port: "5000"
    spec:
      containers:
      - name: message
        image: zerokara.azurecr.io/zerokara-message:latest
        ports:
        - containerPort: 5000

前回同様、Webアプリケーションのマニフェストファイルではアプリを外部公開するためのサービス定義(①)とデプロイに関する定義(②)を記述します。
デプロイ定義の中に「spec.template.metadata.annotations」という項目がありますが、ここにDaprに関する内容を記述します(②-1)。するとマニフェストファイルをAKSに適用した際に、Daprがマニフェストファイルの内容を読み取ってDaprのサイドカーをWebアプリケーション用のポッドに自動的に挿入するようになります。「dapr.io/enabled」ではDaprサイドカーを挿入するかどうかを決定します。「true」の場合は挿入されます。「dapr.io/app-id」にはDaprサイドカーを挿入するポッドのIDを指定します。これはデプロイ定義の「metadata.name」の値と同じものを指定します。「dapr.io/app-port」にはアプリが公開しているポート番号を指定します。Daprサイドカーとアプリのコンテナーがやり取りをするために必要となります。 このマニュフェストファイルをAKSに適用していきます。

Webアプリケーション用のマニュフェストをAKSに適用する

$ kubectl apply -f ./deploy/message.yaml

これでWebアプリケーションおよびDaprサイドカーがAKS上にデプロイされました。

アプリケーションの動作確認

Webアプリケーションのデプロイが完了し、サービスが正しく追加されるとパブリックIPアドレスが割り当てられるので、その値を変数に代入しておきます。

WebアプリケーションのIPアドレスの設定

$ kubectl get svc messageapp

# EXTERNAL-IPに表示されているIPアドレスを変数に代入
$ EXTERNAL_IP=<IPアドレス>

このIPアドレスに対してHTTPリクエストを送信し、Webアプリケーションが正しく動作しているかを確認します。

# メッセージの登録リクエストの送信・・・①
$ curl -X POST -H "Content-Type:application/json" -d '{"data":{"message":"hello, aks x dapr!"}}' $EXTERNAL_IP/message
message posted.

# メッセージの取得リクエストの送信・・・②
$ curl -X GET $EXTERNAL_IP/message
{"message":"hello, aks x dapr!"}

curlコマンドを使ってHTTPリクエストを送信します。まずはwebアプリケーションに対してPOSTメソッドでリクエストを送信します(①)。正しくリクエストが処理されると「message posted.」とレスポンスが出力されます。これでWebアプリケーションからDaprを経由してRedisにメッセージを登録することができました。次にメッセージを取得するためにGETメソッドでリクエストを送信します(②)。すると先程登録したメッセージがレスポンスとして出力されます。これでDaprを経由してRedisからデータを取得できることも確認できました。

使用したリソースの削除

AKSではKubernetesクラスターの実行中はノードとして稼働している仮想マシンに対して課金が行われています。今回のように開発やテスト用途の場合は使用しない時間帯はクラスターを停止するか、削除することで意図しない課金が発生しないように注意して下さい。クラスターの停止および削除は、Azureポータル上で作成したKubernetesクラスターの画面上部から行うことができます。
またAzure Cache for Redisのインスタンスも稼働中は課金されるため、使用しない場合はインスタンスを削除してください。こちらもAzureポータルから作成したRedisインスタンスの画面上部から削除できます。

まとめ

今回はマイクロサービスアーキテクチャのアプリケーションの開発と運用を支援するDaprを使って、AKS上でマイクロサービスを実行する方法について説明しました。Daprのサイドカーコンテナがマイクロサービス間の連携を仲介することで、マイクロサービスアプリケーションの関心事を軽減できることが分かったかと思います。

WINGSプロジェクト 秋葉龍一著/山田祥寛監修
<WINGSプロジェクトについて>テクニカル執筆プロジェクト(代表山田祥寛)。海外記事の翻訳から、主にWeb開発分野の書籍・雑誌/Web記事の執筆、講演等を幅広く手がける。一緒に執筆をできる有志を募集中