Rocket.Chat と EC2 で使い捨てチャットサーバをつくる [AWS CloudFormation]

こんにちは、広野です。

社内研修開催のため、研修期間の数日だけ受講者とのコミュニケーションに使用する使い捨てチャットサーバを Rocket.Chat を使って構築しました。AWS CloudFormation テンプレート化してあるので、前日にサクッと作って、終わったらサクッと捨てる、そんな用途に最適です。

Rocket.Chat インストール手順は Ubuntu であれば簡単すぎて衝撃でした。EC2 の userdata に仕込んでいます。

Snaps - Rocket.Chat Docs

前提とか

  • VPC や マルチ AZ のパブリックサブネット、プライベートサブネット、インターネットゲートウェイは構築済み
  • 独自ドメインが Route 53 ホストゾーンに登録済み
  • SSL 証明書は構築するリージョンの AWS Certificate Manager で作成済み
  • OS は Ubuntu 22.04 とする( Rocket.Chat は snap install でインストールする)
  • EC2 への SSH アクセスは SSM とするため、セキュリティグループには 22 ポートを開けていない。が、一応キーペアも関連付けておく(キーペア作成済みであること)
  • SSM セッションマネージャでの操作ログを Amazon S3 バケットに吐き出す場合、EC2 の IAM ロールに吐き出し先バケットへの put 権限を追加すること
  • 完成すると、AWS CloudFormation テンプレートのパラメータで指定したドメイン名の URL で、Rocket.Chat サーバが起動した状態で立ち上がる(初期画面で、管理者ユーザの登録画面が表示されている状態)
  • IPv6 非対応

アーキテクチャ

  • EC2 インスタンスはプライベートサブネットに配置。シングル構成。
  • ALB は SSL アクセラレータとしての意味しかない。
  • Rocket.Chat は HTTP 3000 番ポートでリッスンしているので、ALB からはそこにアクセスするよう設定する。
  • 図には書いていないが、Route 53 で ALB をエイリアスレコードに紐づける。
  • 図には書いていないが、ALB に AWS Certificate Manager の SSL 証明書を紐づける。

AWS CloudFormation テンプレート

適宜、IAM ロールやセキュリティーグループなどは要件に応じて変更してください。ドメイン関連も不要であれば削除で。
東京リージョン Ubuntu 22.04 AMI の Image ID をベタにパラメータのデフォルト値に書いているので、適宜変更してください。

AWSTemplateFormatVersion: "2010-09-09"
Description: The CloudFormation template that creates an EC2 instance, an ALB and a DNS record in Route 53 for Rocket.Chat.

# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
  SystemName:
    Type: String
    Description: System name. (e.g. EXAMPLE)
    Default: EXAMPLE
    MaxLength: 10
    MinLength: 1
  GroupName:
    Type: String
    Description: Group name. (e.g. RocketChat)
    Default: RocketChat
    MaxLength: 30
    MinLength: 1
  HostName:
    Type: String
    Description: Host name. (e.g. ROCKETCHAT)
    Default: ROCKETCHAT
    MaxLength: 30
    MinLength: 1
  VpcId:
    Type: AWS::EC2::VPC::Id
    Description: Choose a existing VPC ID you deploy the EC2 instance in.
  InstanceSubnet:
    Type: AWS::EC2::Subnet::Id
    Description: Choose an existing Private Subnet ID you deploy the EC2 instance in.
  AlbSubnet1:
    Type: AWS::EC2::Subnet::Id
    Description: Choose an existing Public Subnet ID you deploy the Application Load Balancer in.
  AlbSubnet2:
    Type: AWS::EC2::Subnet::Id
    Description: Choose an existing Public Subnet ID you deploy the Application Load Balancer in.
  ImageID:
    Type: String
    Description: OS AMI Image ID (Ubuntu)
    Default: ami-03f4fa076d2981b45
    MaxLength: 100
    MinLength: 1
  InstanceType:
    Type: String
    Default: t3.small
  KeyPairName:
    Type: AWS::EC2::KeyPair::KeyName
    Description: Choose a existing key pair you associate with the EC2.
  DomainName:
    Type: String
    Description: Domain name for URL. xxxxx.xxx (e.g. example.com)
    Default: example.com
    MaxLength: 40
    MinLength: 5
  SubDomainName:
    Type: String
    Description: Sub domain name for URL. xxxxx.example.com
    Default: chat
    MaxLength: 20
    MinLength: 1
  CertificateArn:
    Type: String
    Description: ACM certificate ARN.
    Default: "arn:aws:acm:ap-northeast-1:xxxxxxxxxx:certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    MaxLength: 128
    MinLength: 10

Resources:
# ------------------------------------------------------------#
# EC2
# ------------------------------------------------------------#
  Ec2Instance:
    Type: AWS::EC2::Instance
    Properties:
      IamInstanceProfile: !Ref Ec2InstanceProfile
      ImageId: !Ref ImageID
      KeyName: !Ref KeyPairName
      InstanceType: !Ref InstanceType
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            VolumeType: gp2
            VolumeSize: 40
      SecurityGroupIds:
        - !Ref Ec2SecurityGroup
      SourceDestCheck: false
      SubnetId: !Ref InstanceSubnet
      Tags:
        - Key: Cost
          Value: !Ref SystemName
        - Key: Name
          Value: !Sub ${SystemName}-${HostName}
      UserData:
        Fn::Base64:
          !Sub |
            #!/bin/bash
            snap install rocketchat-server
            systemctl status snap.rocketchat-server.rocketchat-server.service
            systemctl status snap.rocketchat-server.rocketchat-mongo.service
    DependsOn:
      - Ec2SecurityGroup
      - Ec2InstanceProfile

# ------------------------------------------------------------#
# EC2 Security Group
# ------------------------------------------------------------#
  Ec2SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VpcId
      GroupDescription: Allow web access via port TCP 3000.
      SecurityGroupIngress:
        - SourceSecurityGroupId: !GetAtt AlbSecurityGroup.GroupId
          FromPort: 3000
          IpProtocol: tcp
          ToPort: 3000
      Tags:
        - Key: Cost
          Value: !Ref SystemName
        - Key: Name
          Value: !Sub SG-${SystemName}-${GroupName}
    DependsOn:
      - AlbSecurityGroup

# ------------------------------------------------------------#
# EC2 Role / Instance Profile (IAM)
# ------------------------------------------------------------#
  Ec2Role:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub Ec2Role-${SystemName}-${GroupName}
      Description: This role allows EC2 instance to invoke SSM.
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
              - ec2.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
      Policies:
        - PolicyName: !Sub Ec2S3Policy-${SystemName}-${GroupName}
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Action:
                  - "s3:GetEncryptionConfiguration"
                  - "kms:Decrypt"
                  - "kms:GenerateDataKey"
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"
                  - "logs:DescribeLogGroups"
                  - "logs:DescribeLogStreams"
                Resource: "*"
                Effect: Allow

  Ec2InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      InstanceProfileName: !Ref Ec2Role
      Path: /
      Roles:
        - !Ref Ec2Role
    DependsOn:
      - Ec2Role

# ------------------------------------------------------------#
# ALB
# ------------------------------------------------------------#
  ApplicationLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: !Sub ${SystemName}-${GroupName}
      Type: application
      IpAddressType: ipv4
      LoadBalancerAttributes:
        - Key: deletion_protection.enabled
          Value: false
        - Key: access_logs.s3.enabled
          Value: false
        - Key: idle_timeout.timeout_seconds
          Value: 60
        - Key: routing.http.desync_mitigation_mode
          Value: defensive
        - Key: routing.http.drop_invalid_header_fields.enabled
          Value: true
        - Key: routing.http.preserve_host_header.enabled
          Value: false
        - Key: routing.http.x_amzn_tls_version_and_cipher_suite.enabled
          Value: false
        - Key: routing.http.xff_client_port.enabled
          Value: false
        - Key: routing.http.xff_header_processing.mode
          Value: append
        - Key: routing.http2.enabled
          Value: true
        - Key: waf.fail_open.enabled
          Value: false
      Scheme: internet-facing
      SecurityGroups:
        - !GetAtt AlbSecurityGroup.GroupId
      Subnets:
        - !Ref AlbSubnet1
        - !Ref AlbSubnet2
      Tags:
        - Key: Cost
          Value: !Ref SystemName
        - Key: Name
          Value: !Sub ${SystemName}-${GroupName}
    DependsOn:
      - AlbSecurityGroup

  AlbListenerHttps:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      Certificates:
        - CertificateArn: !Ref CertificateArn
      DefaultActions:
        - TargetGroupArn: !Ref AlbListenerHttpsTargetgroup
          Type: forward
      LoadBalancerArn: !Ref ApplicationLoadBalancer
      Port: 443
      Protocol: HTTPS
      SslPolicy: ELBSecurityPolicy-2016-08
    DependsOn:
      - ApplicationLoadBalancer

  AlbListenerHttpsTargetgroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: !Sub TG-${SystemName}-${GroupName}
      TargetType: instance
      HealthCheckEnabled: true
      HealthCheckIntervalSeconds: 30
      HealthCheckPath: /
      HealthCheckPort: traffic-port
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 5
      IpAddressType: ipv4
      Matcher:
        HttpCode: 200
      Port: 3000
      Protocol: HTTP
      ProtocolVersion: HTTP1
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: 300
        - Key: stickiness.enabled
          Value: false
        - Key: load_balancing.algorithm.type
          Value: round_robin
        - Key: slow_start.duration_seconds
          Value: 0
        - Key: stickiness.app_cookie.cookie_name
          Value: APPCOOKIE
        - Key: stickiness.app_cookie.duration_seconds
          Value: 86400
        - Key: stickiness.lb_cookie.duration_seconds
          Value: 86400
      Targets:
        - Id: !Ref Ec2Instance
          Port: 3000
      UnhealthyThresholdCount: 2
      VpcId: !Ref VpcId
      Tags:
        - Key: Cost
          Value: !Ref SystemName
    DependsOn:
      - Ec2Instance

# ------------------------------------------------------------#
# ALB Security Group
# ------------------------------------------------------------#
  AlbSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VpcId
      GroupDescription: Allow access via HTTPS.
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          FromPort: 443
          IpProtocol: tcp
          ToPort: 443
      Tags:
        - Key: Cost
          Value: !Ref SystemName
        - Key: Name
          Value: !Sub SG-${SystemName}-${GroupName}-ALB

# ------------------------------------------------------------#
# Route 53
# ------------------------------------------------------------#
  Route53RecordA:
    Type: AWS::Route53::RecordSet
    Properties:
      HostedZoneName: !Sub ${DomainName}.
      Name: !Sub ${SubDomainName}.${DomainName}.
      Type: A
      AliasTarget:
        HostedZoneId: !GetAtt ApplicationLoadBalancer.CanonicalHostedZoneID
        DNSName: !GetAtt ApplicationLoadBalancer.DNSName
    DependsOn: ApplicationLoadBalancer

# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#
Outputs:
# Server URL
  RocketChatUrl:
    Value: !Sub https://${SubDomainName}.${DomainName}

まとめ

いかがでしたでしょうか?

研修用途、一時利用の環境なので構成がかなりテキトーですが、起動した状態にまでサクっと作れるのが売りです。CloudFormation での ALB の記述方法の参考にもなるかもです。

本記事が皆様のお役に立てれば幸いです。

著者について
広野 祐司

AWSサーバーレスアーキテクチャを駆使して社内クラウド人材育成アプリや教育コンテンツをつくっています。ReactでSPAを書き始めたら、快適すぎて他の開発言語には戻れなくなりました。AWSサーバーレスやReactの仲間を増やしたいです。
取得資格:AWS認定は7つ、ITサービスマネージャ、ITIL v3 Expert、等
2020, 2021 APN AWS Top Engineers 受賞
2022 AWS Partner Ambassador 受賞
好きなAWSサービス:AWS Amplify / Amazon Cognito / AWS Step Functions / AWS CloudFormation

広野 祐司をフォローする
クラウドに強いによるエンジニアブログです。
SCSKは専門性と豊富な実績を活かしたクラウドサービス USiZE(ユーサイズ)を提供しています。
USiZEサービスサイトでは、お客様のDX推進をワンストップで支援するサービスの詳細や導入事例を紹介しています。
AWSIaC技術ナレッジ
TechHarmony
タイトルとURLをコピーしました