OSSデータカタログ「Datahub」をできる限りAWSマネージドサービスを利用した構成でデプロイしてみた

2022.01.25

どーもsutoです。

今回は以前に記事で予告していたとおり、LinkedIn製のOSSデータカタログ「DataHub」をフロントエンド部分はAWS EKSに、それ以外のコンポーネントをAWSマネージドサービスに変更した構成で構築してみたいと思います。

今回のゴール

以前の記事では、全てのコンポーネントをAWS EKSクラスター上に構成した手順で紹介しました。

今回は以下の下側の図のように、Datahubのストレージにあたる部分を各AWSサービスに変更した構成で構築します。

つまり、Frontend部分(WebUIとMetadata Ingection)→EKSに残し、Metadata Store→RDS、Metadata Crawler→Glue schema registry、Elasticsearch→Amazon Opensearch、MAE/MCE機能→Amazon Managed Kafka(MSK)、CLB→ALBに変えます。

前提条件と作業環境の設定について

※作業環境はMacです。

事前に以下のツールをインストールしておく必要があります。Python3については省略させていただきますが、その他はリンク先を参考に実施できると思います。

brew install kubectl
kubectl version --client
brew tap weaveworks/tap
brew install weaveworks/tap/eksctl
eksctl version
brew install helm

EKSクラスター作成

eksctlコマンドを使用してクラスターを作成します。今回は以下のパラメータで作成します。

  • VPC:CIDRを指定して新規作成(10.1.0.0/16)
  • クラスター名:datahub-test
  • リージョン:ap-northeast-1
  • インスタンスタイプ:t3.medium
  • ノード数:3
eksctl create cluster \
--vpc-cidr 10.1.0.0/16 \
--name datahub-test \
--region ap-northeast-1 \
--with-oidc \
--node-type t3.medium \
--node-private-networking \
--nodes 3

実行するとCloudformationスタックが実行され、完了後にkubectl get nodesを実行することで以下のように3ノードが表示されます

% kubectl get nodes
NAME                                              STATUS   ROLES    AGE     VERSION
ip-10-1-107-176.ap-northeast-1.compute.internal   Ready    <none>   3m6s    v1.21.5-eks-bc4871b
ip-10-1-152-244.ap-northeast-1.compute.internal   Ready    <none>   3m10s   v1.21.5-eks-bc4871b
ip-10-1-184-243.ap-northeast-1.compute.internal   Ready    <none>   3m7s    v1.21.5-eks-bc4871b

ストレージ部分のリソースを構築

次に以下のCloudformationテンプレートを使って、RDS、OpenSearchクラスター、Glue schema registry、MSKクラスター、関連するSGやIAMロールを作成します。

Parameters:
  ProjectName:
    Description: ProjectName
    Type: String
    Default: 'datahub-test'
  DBInstanceClass:
    Type: String
    Default: 'db.t3.small'
  DbPassword:
    Type: String
    Description: Password string for master user of DB Instance
    Default: ''
    NoEcho: true
  KafkaInstanceType:
    Type: String
    Default: 'kafka.t3.small'
  OSSInstanceType:
    Type: String
    Default: 't3.medium.search'
  VPCId:
    Type: String
    Description: id for VPC.
    Default: 'vpc-xxxxxx'
  PrimarySubnetId:
    Type: String
    Description: Private Subnet id 1.
    Default: 'subnet-xxxxxx'
  SecondarySubnetId:
    Type: String
    Description: Private Subnet id 2.
    Default: 'subnet-xxxxxx'
  ThirdSubnetId:
    Type: String
    Description: Private Subnet id 3.
    Default: 'subnet-xxxxxx'
  EKSNodeRole:
    Type: String
    Description: Name of EKS nodegroup IAM Role.
    Default: 'eksctl-datahub-nodegroup-ng-NodeInstanceRole-xxxxxxxx'
  NodeInstanceSG:
    Type: String
    Description: EKS nodegroup instances SG id.
    Default: 'sg-xxxxxx'


Resources:
  DatahubSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub ${ProjectName}-strage-sg
      GroupDescription: Enable datahub service instances access
      VpcId: !Ref VPCId
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: '443'
        ToPort: '443'
        SourceSecurityGroupId: !Ref NodeInstanceSG
      - IpProtocol: tcp
        FromPort: '3306'
        ToPort: '3306'
        SourceSecurityGroupId: !Ref NodeInstanceSG
      - IpProtocol: tcp
        FromPort: '9092'
        ToPort: '9092'
        SourceSecurityGroupId: !Ref NodeInstanceSG
      - IpProtocol: tcp
        FromPort: '2181'
        ToPort: '2181'
        SourceSecurityGroupId: !Ref NodeInstanceSG
      - IpProtocol: tcp
        FromPort: '9091'
        ToPort: '9091'
        SourceSecurityGroupId: !Ref NodeInstanceSG
      Tags:
        - Key: Name
          Value: !Sub ${ProjectName}-strage-sg

  MyDB:
    Type: AWS::RDS::DBInstance
    Properties:
      AllocatedStorage: '10'
      DBInstanceClass: !Ref DBInstanceClass
      DBInstanceIdentifier: !Sub ${ProjectName}-mysql
      DBSubnetGroupName: !Ref DBSubnetGroup
      Engine: MySQL
      MasterUsername: 'root'
      MasterUserPassword: !Ref DbPassword
      StorageType: gp2
      VPCSecurityGroups:
        - !GetAtt 'DatahubSecurityGroup.GroupId'
    #DeletionPolicy: Snapshot
  DBSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: custom subnet group
      SubnetIds:
        - !Ref PrimarySubnetId
        - !Ref SecondarySubnetId
        - !Ref ThirdSubnetId

  MSKCluster:
    Type: 'AWS::MSK::Cluster'
    Properties:
      ClusterName: !Sub ${ProjectName}-kafka
      KafkaVersion: 2.6.2
      NumberOfBrokerNodes: 3
      EncryptionInfo:
        EncryptionInTransit:
          ClientBroker: TLS_PLAINTEXT
      BrokerNodeGroupInfo:
        BrokerAZDistribution: DEFAULT
        InstanceType: !Ref KafkaInstanceType
        SecurityGroups:
          - Ref: DatahubSecurityGroup
        StorageInfo:
          EBSStorageInfo:
            VolumeSize: 100
        ClientSubnets:
          - !Ref PrimarySubnetId
          - !Ref SecondarySubnetId
          - !Ref ThirdSubnetId

  GlueSchemaRegistry:
    Type: AWS::Glue::Registry
    Properties:
      Description: 'Datahub schema registry'
      Name: !Sub ${ProjectName}-schema-registry

  GlueIAMPolicy:
    Type: 'AWS::IAM::Policy'
    Properties:
      PolicyName: !Sub ${ProjectName}-schema-registry-policy
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action:
              - 'glue:GetRegistry'
              - 'glue:ListRegistries'
              - 'glue:CreateSchema'
              - 'glue:UpdateSchema'
              - 'glue:GetSchema'
              - 'glue:ListSchemas'
              - 'glue:RegisterSchemaVersion'
              - 'glue:GetSchemaByDefinition'
              - 'glue:GetSchemaVersion'
              - 'glue:GetSchemaVersionsDiff'
              - 'glue:ListSchemaVersions'
              - 'glue:CheckSchemaVersionValidity'
              - 'glue:PutSchemaVersionMetadata'
              - 'glue:QuerySchemaVersionMetadata'
            Resource: 
              - !Sub arn:aws:glue:*:${AWS::AccountId}:schema/*
              - !GetAtt GlueSchemaRegistry.Arn
          - Effect: Allow
            Action: 'glue:GetSchemaVersion'
            Resource: '*'
      Roles:
        - !Ref EKSNodeRole

  OpenSearchServiceDomain:
    Type: AWS::OpenSearchService::Domain
    Properties:
      DomainName: !Sub ${ProjectName}-oss
      EngineVersion: 'OpenSearch_1.1'
      ClusterConfig:
        DedicatedMasterEnabled: true
        InstanceCount: '2'
        ZoneAwarenessEnabled: true
        InstanceType: !Ref OSSInstanceType
        DedicatedMasterType: !Ref OSSInstanceType
        DedicatedMasterCount: '3'
      DomainEndpointOptions:
        EnforceHTTPS: true
      EBSOptions:
        EBSEnabled: true
        Iops: '0'
        VolumeSize: '30'
        VolumeType: 'gp2'
      AccessPolicies:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              AWS: '*'
            Action: 'es:*'
            Resource: '*'
      VPCOptions:
        SubnetIds:
          - !Ref PrimarySubnetId
          - !Ref SecondarySubnetId
        SecurityGroupIds:
          - Ref: DatahubSecurityGroup

※スタック内でOpensearch作成時にBefore you can proceed, you must enable a service-linked role~のエラーが出たら以下コマンドでサービルリンクロールを事前に作成しましょう。

aws iam create-service-linked-role --aws-service-name es.amazonaws.com

作成したリソースの情報を控えておきます。

  • RDSのマスターユーザー名とパスワード
  • RDSのエンドポイント(以下を参照)

  • MSKクラスターのブートストラップサーバのエンドポイント(テキストプレーン)
  • MSKクラスターのzookeeperサーバのエンドポイント(テキストプレーン)
  • ※どちらも3エンドポイントをまるごとコピーしてください

  • OpensearchのVPCエンドポイント
  • ※以下の赤枠の”https://”を除いた部分をコピー

EKSクラスターにALBコントローラーをセットアップ

  • コントローラがAWSAPIを呼び出すことができるようにするためのIAMポリシードキュメントをダウンロードします。
curl -o iam_policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.2.0/docs/install/iam_policy.json
  • 次のコマンドを実行して、ポリシードキュメントに基づいてIAMポリシーを作成します。
aws iam create-policy \
    --policy-name AWSLoadBalancerControllerIAMPolicy \
    --policy-document file://iam_policy.json
  • eksctlを使用して、上記のポリシーをkubernetesポッドにアタッチできるサービスアカウントを作成します。
eksctl create iamserviceaccount \
  --cluster=<<cluster-name>> \
  --namespace=kube-system \
  --name=aws-load-balancer-controller \
  --attach-policy-arn=arn:aws:iam::<<account-id>>:policy/AWSLoadBalancerControllerIAMPolicy \
  --override-existing-serviceaccounts \
  --approve
  • 次のコマンドを実行して、TargetGroupBindingカスタムリソース定義をインストールします。
kubectl apply -k "github.com/aws/eks-charts/stable/aws-load-balancer-controller//crds?ref=master"
  • 最新バージョンのALBコントローラーを含むヘルムチャートリポジトリを追加します。
helm repo add eks https://aws.github.io/eks-charts
helm repo update
  • 次のコマンドを実行して、コントローラーをkubernetesクラスターにインストールします。
helm upgrade -i aws-load-balancer-controller eks/aws-load-balancer-controller \
  --set clusterName=<<cluster-name>> \
  --set serviceAccount.create=false \
  --set serviceAccount.name=aws-load-balancer-controller \
  -n kube-system
  • インストールが完了すれば、以下のコマンドで次のような結果が返されるはずです。
% kubectl get deployment -n kube-system aws-load-balancer-controller
NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
aws-load-balancer-controller   2/2     2            2           142m

Helmを使用してEKSクラスターにDataHubをセットアップ

  • DatahubのリポジトリをHelmに追加します。
helm repo add datahub https://helm.datahubproject.io/
  • 作成したRDSのパスワードを格納したkubernetesシークレットを作成します。
kubectl create secret generic mysql-secrets --from-literal=mysql-root-password=<RDSのパスワード>
  • Helmチャートのvalues.yamlを作成します。各パラメータを以下のようにAWSリソースの情報に書き換えて保存してください。
  • 【参考】デフォルト構成はこちら
datahub-gms:
  enabled: true
  image:
    repository: linkedin/datahub-gms
    tag: "v0.8.23"
  ingress:
    enabled: true
    annotations:
      kubernetes.io/ingress.class: alb
      alb.ingress.kubernetes.io/scheme: internet-facing
      alb.ingress.kubernetes.io/target-type: instance
      alb.ingress.kubernetes.io/inbound-cidrs: 0.0.0.0/0
    hosts:
      - paths:
          - /*

datahub-frontend:
  enabled: true
  image:
    repository: linkedin/datahub-frontend-react
    tag: "v0.8.23"
  ingress:
    enabled: true
    annotations:
      kubernetes.io/ingress.class: alb
      alb.ingress.kubernetes.io/scheme: internet-facing
      alb.ingress.kubernetes.io/target-type: instance
      alb.ingress.kubernetes.io/inbound-cidrs: 0.0.0.0/0
    hosts:
      - paths:
          - /*

datahub-mae-consumer:
  image:
    repository: linkedin/datahub-mae-consumer
    tag: "v0.8.23"

datahub-mce-consumer:
  image:
    repository: linkedin/datahub-mce-consumer
    tag: "v0.8.23"

datahub-ingestion-cron:
  enabled: false
  image:
    repository: linkedin/datahub-ingestion
    tag: "v0.8.23.0"

elasticsearchSetupJob:
  enabled: true
  image:
    repository: linkedin/datahub-elasticsearch-setup
    tag: "v0.8.23"
  extraEnvs:
    - name: USE_AWS_ELASTICSEARCH
      value: "true"

kafkaSetupJob:
  enabled: true
  image:
    repository: linkedin/datahub-kafka-setup
    tag: "v0.8.23"

mysqlSetupJob:
  enabled: true
  image:
    repository: acryldata/datahub-mysql-setup
    tag: "v0.8.23.0"

datahubUpgrade:
  enabled: true
  image:
    repository: acryldata/datahub-upgrade
    tag: "v0.8.23.0"
  noCodeDataMigration:
    sqlDbType: "MYSQL"

global:
  graph_service_impl: elasticsearch
  datahub_analytics_enabled: true
  datahub_standalone_consumers_enabled: false

  elasticsearch:
    host: "<OpensearchクラスターのVPCエンドポイント>"
    port: "443"
    useSSL: "true"

  kafka:
    bootstrap:
      server: "<MSKクラスターのbootstrap-server endpoint>"
    zookeeper:
      server: "<MSKクラスターのzookeeper endpoint>"
    ## For AWS MSK set this to a number larger than 1
    partitions: 3 # MSKクラスタ数と同じにする
    replicationFactor: 3 # MSKクラスタ数と同じにする
    schemaregistry:
      type: AWS_GLUE
      glue:
        region: ap-northeast-1 # <<region>>
        registry: <Glue schema registry name>

  sql:
    datasource:
      host: "<RDSのエンドポイント>:3306"
      hostForMysqlClient: "<RDSのエンドポイント>"
      port: "3306"
      url: "jdbc:mysql://<RDSのエンドポイント>:3306/datahub?verifyServerCertificate=false&useSSL=true&useUnicode=yes&characterEncoding=UTF-8"
      driver: "com.mysql.jdbc.Driver"
      username: "<RDSのマスターユーザー>"
      password:
        secretRef: mysql-secrets
        secretKey: mysql-root-password

  datahub:
    gms:
      port: "8080"
    mae_consumer:
      port: "9091"
    appVersion: "1.0"
  • 以下のコマンドを実行してDatahubをデプロイします。
helm upgrade --install datahub datahub/datahub --values <yamlファイルを保存したフォルダパス>/values.yaml
  • デプロイ完了後、podの一覧が以下のようになるはずです。
% kubectl get pods
NAME                                               READY   STATUS      RESTARTS   AGE
datahub-datahub-frontend-6c47c77468-v2hsn          1/1     Running     0          4m23s
datahub-datahub-gms-59dfd97556-9vrsr               1/1     Running     0          4m23s
datahub-datahub-upgrade-job-8xbhc                  0/1     Error       0          4m23s
datahub-datahub-upgrade-job-g4qpn                  0/1     Completed   0          3m14s
datahub-elasticsearch-setup-job-kj95r              0/1     Completed   0          6m9s
datahub-kafka-setup-job-kwkqs                      0/1     Completed   0          6m
datahub-mysql-setup-job-64grv                      0/1     Completed   0          4m29s
  • デプロイ完了後、Frontendアクセス用とメタデータ取り込み(GMS)用2つのALBが作成されています。
  • ※Helmによるデプロイの影響で、同時にCLBも2つ作成されていますが、ALBがあるため不要なので削除して大丈夫です。

  • 以下コマンドで確認して「ADDESS」列のFrontend用ALBのエンドポイントを控える

% kubectl get ingress
NAME                       CLASS    HOSTS   ADDRESS                                                                       PORTS   AGE
datahub-datahub-frontend   <none>   *       k8s-default-datahubd-xxxxxxxx.ap-northeast-1.elb.amazonaws.com   80      32h
datahub-datahub-gms        <none>   *       k8s-default-datahubd-xxxxxxxx.ap-northeast-1.elb.amazonaws.com   80      32h
  • Frontend用ALBにブラウザでアクセスしてみると以下のようにログイン画面が表示されます。

  • デフォルトユーザとパスワード(datahub:datahub)でログインできるはずです。

【おまけ】ALBのアクセスポイントをhttpsにする場合

ALBのアクセスをhttpsで構成したい場合は、Route53やACMなどを利用してパブリックDNSと証明書を用意した上でvalues.yamlのingress部分を以下のように構成してください。

  ingress:
    enabled: true
    annotations:
      kubernetes.io/ingress.class: alb
      alb.ingress.kubernetes.io/scheme: internet-facing
      alb.ingress.kubernetes.io/target-type: instance
      alb.ingress.kubernetes.io/certificate-arn: <<certificate-arn>> # ACMで作成した証明書のArnを入力
      alb.ingress.kubernetes.io/inbound-cidrs: 0.0.0.0/0
      alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
      alb.ingress.kubernetes.io/actions.ssl-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}'
    hosts:
      - host: <<host-name>> # パブリックDNS名を入力
        redirectPaths:
          - path: /*
            name: ssl-redirect
            port: use-annotation
        paths:
          - /*

まとめ

今回のようにストレージ部分をAWSサービスに外出しする構成にできれば、データベース部分のバックアップがやりやすくなる他、アップデート作業にも対応しやすくなります。最新バージョンを保てればセキュリティ面でも安心できますね。

また、AWS Opensearchに移行できたことで、クラスター内のElasticsearchの日本語プラグインの対応をしなくて済むのも大きなメリットです。

しかしながら、日本語の検索機能をより向上させるには別途インデックスのカスタマイズは必要になるかと思います。

参考