💪

AWS EKSを活用した高可用性MQTTブローカークラスタの構築

2024/09/30に公開

はじめに

IoTチームの高原です。

みなさんは「AWS IoT Coreを使わずにスケーラブルなMQTTブローカーを使いてぇ〜」って思ったことありません?ありますよね?
例えば、デバイスがMQTT over TLSに対応しておらず、閉域網経由で平文のMQTTメッセージが飛んでくる場合です。

調べてみたところEMQXなるMQTTブローカーソフトウェアがあるようなのですが、これをk8s上で管理するためにEMQX Operatorというツールが提供されています。
今回はそれを使わせてもらって、AWS EKS上にいい感じのMQTTブローカークラスターを構築します。
VerneMQもMQTTブローカーのHAクラスターは組めるっぽいのですが、ノードをIPで手動追加する形式だったので今回は採用を見送りました。

k8sは素人なので何か微妙な点があっても手加減してください。

完成物

AWSリソース

上記のような感じでAWS EKSを構築します。
で、EKSにEMQXをカスタムリソースとしてデプロイします。

できること

上記のAWSリソース図を見てもらうとわかるのですが、NLBが2つ作成されます。
1つはMQTTブローカーのエンドポイントとして利用できます。
デフォルトだと4種のポートが使えます。

  • 1883: 素のMQTT (TCP)
  • 8083: MQTT over WebSocket
  • 8084: MQTT over WebSocket over TLS (SSL)
  • 8883: MQTT over TLS (SSL)

もう1つはEMQXの管理画面にアクセスできるエンドポイントです。
おしゃれなダッシュボードにアクセスできます。
EMQX管理画面

作成手順

ここからはMQTTブローカークラスターの構築を目指して、CLIを叩くだけです。

準備

以下のツールをインストールしておきます。

  • kubectl
    • k8sクラスターを管理するためのCLI
  • eksctl
    • EKSクラスターを管理するためのCLI
  • helm
    • k8sのパッケージマネージャー

VPC, Subnetの作成

aws cloudformation create-stack \
  --region ap-northeast-1 \
  --stack-name my-eks-vpc-stack \
  --template-url https://s3.us-west-2.amazonaws.com/amazon-eks/cloudformation/2020-10-29/amazon-eks-vpc-private-subnets.yaml

上記CloudFormationで以下のネットワークリソースが作成されます。
AWSリソース1

コンソールを開いてpublic, private subnetのIDを控えておきましょう。

EKSクラスターの作成

まず下記ファイルを用意して、信頼ポリシーの準備をします。

eks-cluster-trust-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "eks.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

そしてEKSクラスター用IAM Roleを作成

aws iam create-role \
  --role-name my-eks-cluster-role \
  --assume-role-policy-document file://eks-cluster-trust-policy.json

aws iam attach-role-policy \
  --policy-arn arn:aws:iam::aws:policy/AmazonEKSClusterPolicy \
  --role-name my-eks-cluster-role

そしてEKSクラスターを作成します。
作成したsubnet 4個とsg 1個を指定します。
その後、kubectl用に設定更新。

aws eks create-cluster \
  --name my-eks-cluster \
  --region ap-northeast-1 \
  --role-arn arn:aws:iam::XXXXXXXXXXXX:role/my-eks-cluster-role \
  --resources-vpc-config subnetIds=subnet-0XXXXXXXXXXXXXXX1,subnet-0XXXXXXXXXXXXXXX2,subnet-0XXXXXXXXXXXXXXX3,subnet-0XXXXXXXXXXXXXXX4,securityGroupIds=sg-0XXXXXXXXXXXXXXX0

aws eks update-kubeconfig \
  --region ap-northeast-1 \
  --name my-eks-cluster

ノードグループ(ワーカーノード)の作成

同様に下記ファイルを用意して、信頼ポリシーの準備をします。

node-group-trust-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

もう1つ、別に下記ファイルも用意しておきましょう。
EMQXはデプロイ時にEBSへの書き込みが走るので、それ関連のIAMが必要です。
(ここ詰まりポイントです)

ebs-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:AttachVolume",
        "ec2:CreateSnapshot",
        "ec2:CreateTags",
        "ec2:CreateVolume",
        "ec2:DeleteSnapshot",
        "ec2:DeleteTags",
        "ec2:DeleteVolume",
        "ec2:DescribeAvailabilityZones",
        "ec2:DescribeInstances",
        "ec2:DescribeSnapshots",
        "ec2:DescribeTags",
        "ec2:DescribeVolumes",
        "ec2:DescribeVolumesModifications",
        "ec2:DetachVolume",
        "ec2:ModifyVolume"
      ],
      "Resource": "*"
    }
  ]
}

ファイルが準備できたら、ノードグループ用IAMを作成します。

aws iam create-role \
  --role-name my-eks-node-role \
  --assume-role-policy-document file://node-group-trust-policy.json

aws iam attach-role-policy \
  --policy-arn arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy \
  --role-name my-eks-node-role

aws iam attach-role-policy \
  --policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly \
  --role-name my-eks-node-role

aws iam attach-role-policy \
  --policy-arn arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy \
  --role-name my-eks-node-role

aws iam put-role-policy \
    --role-name my-eks-node-role \
    --policy-name EBSFullAccess \
    --policy-document file://ebs-policy.json

続いてノードグループを作成します。

aws eks create-nodegroup \
  --region ap-northeast-1 \
  --cluster-name my-eks-cluster \
  --nodegroup-name my-eks-cluster-node \
  --subnets subnet-0XXXXXXXXXXXXXXX1 subnet-0XXXXXXXXXXXXXXX2 \
  --node-role arn:aws:iam::XXXXXXXXXXXX:role/my-eks-node-role \
  --scaling-config minSize=2,maxSize=2,desiredSize=2 \
  --ami-type AL2_x86_64 \
  --instance-types t3.medium

ここまでで、下記が完成しました。
AWSリソース2

k8sサービスアカウントとIAMロールのマッピング

IAMロールをk8sサービスアカウントに関連づけるために、EKSクラスターにOIDCプロバイダーをリンクさせます。

eksctl utils associate-iam-oidc-provider --cluster my-eks-cluster --approve

NLBデプロイ準備

NLB用IAM policyをダウンロードして、IAM Roleを作成します。

curl -O https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.7.2/docs/install/iam_policy.json

aws iam create-policy \
    --policy-name my-eks-lb-controller-policy \
    --policy-document file://iam_policy.json

NLBのIAM Roleをリンクさせた k8sサービスアカウントを作成します。

eksctl create iamserviceaccount \
  --cluster=my-eks-cluster \
  --namespace=kube-system \
  --name=aws-load-balancer-controller \
  --role-name my-eks-lb-controller-policy \
  --attach-policy-arn=arn:aws:iam::XXXXXXXXXXXX:policy/my-eks-lb-controller-policy \
  --approve \
  --override-existing-serviceaccounts

クラスターにaws-load-balancer-controllerをインストール。

helm repo add eks https://aws.github.io/eks-charts
helm repo update
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
  -n kube-system \
  --set clusterName=my-eks-cluster \
  --set serviceAccount.create=false \
  --set serviceAccount.name=aws-load-balancer-controller

EMQXデプロイ

クラスターにcert-manageremqxをインストール。(参考)

helm repo add jetstack https://charts.jetstack.io
helm repo update
helm upgrade --install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --set installCRDs=true

helm repo add emqx https://repos.emqx.io/charts
helm repo update
helm upgrade --install emqx-operator emqx/emqx-operator \
  --namespace emqx-operator-system \
  --create-namespace

さらに下記コマンドでAmazon EBS CSI Driverをクラスターにインストール。

kubectl apply -k "github.com/kubernetes-sigs/aws-ebs-csi-driver/deploy/kubernetes/overlays/stable/ecr/?ref=release-1.9"

そしてEBS書き込み用のk8sサービスアカウントを準備。

aws iam create-policy \
  --policy-name my-eks-ebs-csi-driver-policy \
  --policy-document file://ebs-policy.json

eksctl create iamserviceaccount \
  --name ebs-csi-controller-sa \
  --namespace kube-system \
  --cluster my-eks-cluster \
  --attach-policy-arn arn:aws:iam::XXXXXXXXXXXX:policy/my-eks-ebs-csi-driver-policy \
  --approve \
  --region ap-northeast-1

下記yamlファイルを用いて、カスタムリソースとしてemqxをapply。(参考)
aws-load-balancer-subnetsにはprivate subnetを指定。

emqx.yaml
apiVersion: apps.emqx.io/v2beta1
kind: EMQX
metadata:
  name: emqx
spec:
  image: emqx:5
  coreTemplate:
    spec:
      ## EMQX custom resources do not support updating this field at runtime
      volumeClaimTemplates:
        ## More content: https://docs.aws.amazon.com/eks/latest/userguide/storage-classes.html
        ## Please manage the Amazon EBS CSI driver as an Amazon EKS add-on.
        ## For more documentation please refer to: https://docs.aws.amazon.com/zh_cn/eks/latest/userguide/managing-ebs-csi.html
        storageClassName: gp2
        resources:
          requests:
            storage: 10Gi
        accessModes:
          - ReadWriteOnce
  dashboardServiceTemplate:
    metadata:
      ## More content: https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.4/guide/service/annotations/
      annotations:
        ## Specifies whether the NLB is Internet-facing or internal. If not specified, defaults to internal.
        service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
        ## Specify the availability zone to which the NLB will route traffic. Specify at least one subnet, either subnetID or subnetName (subnet name label) can be used.
        service.beta.kubernetes.io/aws-load-balancer-subnets: subnet-0XXXXXXXXXXXXXXX3,subnet-0XXXXXXXXXXXXXXX4
    spec:
      type: LoadBalancer
      ## More content: https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.4/guide/service/nlb/
      loadBalancerClass: service.k8s.aws/nlb
  listenersServiceTemplate:
    metadata:
      ## More content: https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.4/guide/service/annotations/
      annotations:
        ## Specifies whether the NLB is Internet-facing or internal. If not specified, defaults to internal.
        service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
        ## Specify the availability zone to which the NLB will route traffic. Specify at least one subnet, either subnetID or subnetName (subnet name label) can be used.
        service.beta.kubernetes.io/aws-load-balancer-subnets: subnet-0XXXXXXXXXXXXXXX3,subnet-0XXXXXXXXXXXXXXX4
    spec:
      type: LoadBalancer
      ## More content: https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.4/guide/service/nlb/
      loadBalancerClass: service.k8s.aws/nlb

下記コマンドでデプロイ!

kubectl apply -f ./emqx.yaml

listenersServiceTemplateの方に作成されたエンドポイントをMQTTブローカーとして使用できます。
dashboardServiceTemplateの方に作成されたエンドポイントには18083でダッシュボードがホスティングされています。

終わり

これにてEMQXをAWS EKSにデプロイできました。
以上で手順は終了です。お疲れ様でした。

Luup Developers Blog

Discussion