COVID-19(SARS-CoV-2)の解析プロジェクトへの参加環境をCloudFormationで自動構築しました

新型コロナウイルス感染症に罹患された皆さまおよび関係者の皆さまに、心よりお見舞い申し上げます。一日も早い収束と、皆さまのご健康を心からお祈り申し上げます。


さて、ご覧の皆様は、新型コロナウイルスを解析するプロジェクトがあるのをご存知でしょうか。
有名なところですと「[Folding@Home](https://foldingathome.org/)」などがあります。
[こちら](https://foldingathome.org/start-folding/)のサイトからアプリケーションプログラムをダウンロードして、ちょこちょこっと設定を行うと、コンピュータの余剰リソースを利用して解析を実施してくれるのです！

というわけで、この Folding@Home による新型コロナウイルス解析プロジェクトへの参加環境を自動構築する CloudFormation テンプレートを作成しましたので共有します。

## こんな方におすすめです
* COVID-19(SARS-CoV-2)の解析に貢献したい AWS を利用している方
 * AWS のアカウント作成から1年以内で無料枠が残っている方!
 * Reserved Instance の前払いを行っているが余剰リソースがある企業の皆様!!
 * イベントなどで獲得した AWS クレジットが残っており、有効活用したい方!!!

このテンプレートを元に解析プロジェクトに貢献してみてはいかがでしょうか。

## 事前準備
### アクセス元 IP アドレスの確認
自分のパソコンなどのみからのアクセスに絞るため、現在の自分の IP アドレスを確認します。
お手軽な方法は、 AWS マネージメントコンソールで セキュリティグループの作成画面を使うことです。

流れは以下の通りです。
1. AWS マネージメントコンソールで EC2 の画面を表示する
2. セキュリティグループの一覧を表示し、セキュリティグループの作成画面を呼び出す
3. インバウンドルール、アウトバウンドルールどちらでもかまわないので送信元もしくは送信先のプルダウンから「マイ IP」を選択する。
4. そうすると、自分の IP アドレスが表示されるので CIDR 表記(末尾の/32)を含めて コピーする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/548623/f3b0bf97-5edf-c820-54f9-4dd633816ba4.png)


### キーペアがなければキーペアを作成
EC2 インスタンスを作成する際にキーペアが必要になります。未作成であれば、事前に作成します。

1. AWS マネージメントコンソールで EC2 の画面を表示する
2. キーペアの一覧を表示し、キーペアの作成画面を表示する
3. 任意の名称をつけて、pem もしくは ppk タイプの鍵を生成します。
※本環境では SSM Agentによる操作を可能にするために EC2 インスタンスへ埋め込むのみで SSH 接続などには利用しません。


## テンプレート
ちょっと長いので折りたたんでいます。

<details><summary>CloudFormation テンプレートを見る</summary><div>

```yml:kaiseki.yml
---
AWSTemplateFormatVersion: '2010-09-09'

Parameters:
  AmaLin2ID:
    Type: AWS::SSM::Parameter::Value<String>
    Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2

  InstanceType:
    Type: String
    Default: t2.micro
  
  MyIP:
    Type: String
    Description: 'Your Public IP Address'
    MinLength: 9
    MaxLength: 18
    AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
    ConstraintDescription: "Must be a valid IP CIDR range of the form x.x.x.x/xx"

  KeyPair:
    Type: "AWS::EC2::KeyPair::KeyName"
  
  CPUPower:
    Type: String
    Description: "CPU power settings"
    Default: full
    AllowedValues:
      - full
      - medium
      - light

  UserName:
    Type: String
    Description: "Folding@Home UserName"
    Default: Anonymous
    MinLength: 1

  TeamId:
    Type: String
    Description: "Folding@Home TeamId. The ID '222' is team Japan."
    Default: 222

Conditions:
  isT2Micro: !Equals [!Ref InstanceType, "t2.micro"]


Resources:
# Network
  VPC:
    Type: "AWS::EC2::VPC"
    Properties:
      CidrBlock:                10.0.0.0/16
      InstanceTenancy:          default
      EnableDnsSupport:         true
      EnableDnsHostnames:       true
      Tags:
        - Key:                  Name
          Value:                vpc_kaiseki

  SubnetA:
    DependsOn:                  VPC
    Type: "AWS::EC2::Subnet"
    Properties: 
      CidrBlock:                10.0.0.0/24
      AvailabilityZone:         "ap-northeast-1a"
      VpcId:
        Ref:                    VPC
      MapPublicIpOnLaunch:      true
      Tags: 
        - Key:                  Name
          Value:                subnet_kaiseki_az-a  

  IGW:
    Type: "AWS::EC2::InternetGateway"
    Properties: 
      Tags: 
        - Key:                  Name
          Value:                igw_kaiseki

  AttachIGW:
    DependsOn:                  VPC
    Type: "AWS::EC2::VPCGatewayAttachment"
    Properties:
      VpcId:                    !Ref VPC
      InternetGatewayId:        !Ref IGW
  
  RouteTable:
    DependsOn:                  VPC
    Type: "AWS::EC2::RouteTable"
    Properties: 
      VpcId: 
        Ref:                    VPC
      Tags: 
        - Key:                  Name
          Value:                rtb_kaiseki

  AttachRouteTableA:
    DependsOn:                  SubnetA
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties: 
      RouteTableId:             !Ref RouteTable
      SubnetId:                 !Ref SubnetA

  AddRouteRtbIGW:
    DependsOn:                  IGW 
    Type: "AWS::EC2::Route"
    Properties: 
      DestinationCidrBlock:     0.0.0.0/0
      GatewayId:                !Ref IGW
      RouteTableId:             !Ref RouteTable

  SecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties: 
      GroupName:                sg_kaiseki
      GroupDescription:         sg_kaiseki
      VpcId:                    !Ref VPC
      Tags: 
        - Key:                  Name
          Value:                sg_kaiseki
      ## Inbound Rule  ########################################################
      SecurityGroupIngress:
        - CidrIp:               !Sub ${MyIP}
          IpProtocol:           tcp
          FromPort:             7396
          ToPort:               7396
          Description:          "Folding@Home Web UI"

# IAM Role
  IAMRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:  
        Version: "2012-10-17"  
        Statement:   
          -   
            Effect: "Allow"  
            Principal:   
              Service:   
                - "ec2.amazonaws.com"  
            Action:   
              - "sts:AssumeRole"  
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
      Path: "/"
      RoleName: role-kaiseki-ssm
 
  InstanceProfile:
    Type: AWS::IAM::InstanceProfile  
    Properties:  
      Path: "/"  
      Roles:  
      - !Ref IAMRole


# EC2
  Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId:                  !Ref AmaLin2ID
      InstanceType:             !Ref InstanceType
      KeyName:                  !Sub ${KeyPair}
      IamInstanceProfile:       !Ref InstanceProfile
      InstanceInitiatedShutdownBehavior: stop
      Monitoring:               false
      BlockDeviceMappings:
        - DeviceName:           /dev/xvda
          Ebs: 
            VolumeSize:         30
            VolumeType:         gp2
      NetworkInterfaces: 
        - DeviceIndex:          0
          GroupSet: 
            -                   !Ref SecurityGroup
          SubnetId:             !Ref SubnetA
          DeleteOnTermination:  true
      Tags:
        - Key:                  Name
          Value:                ec2-kaiseki
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          # Configure Timezone
          timedatectl set-timezone Asia/Tokyo
          localectl set-locale LANG=ja_JP.UTF-8
          
          # Install SSMAgent
          yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm

          # Install fahclient
          rpm -i --nodeps https://download.foldingathome.org/releases/public/release/fahclient/centos-6.7-64bit/v7.5/fahclient-7.5.1-1.x86_64.rpm

          # backup fahclient
          cp -p /etc/fahclient/config.xml /etc/fahclient/config.xml.org

          sleep 5

          # Stop fahclient
          /etc/init.d/FAHClient stop

          sed -i -e 's/light/${CPUPower}/g' /etc/fahclient/config.xml
          sed -i -e 's/anonymous/${UserName}/g' /etc/fahclient/config.xml
          sed -i -e '$d' /etc/fahclient/config.xml
          echo "  <team v='${TeamId}'/>">>/etc/fahclient/config.xml
          echo "  <allow>127.0.0.1 0.0.0.0/0</allow>">>/etc/fahclient/config.xml
          echo "  <web-allow>127.0.0.1 0.0.0.0/0</web-allow>">>/etc/fahclient/config.xml
          echo "</config>">>/etc/fahclient/config.xml

          # Start fahclient
          /etc/init.d/FAHClient start

          # Delete keys
          shred -u /etc/ssh/*_key /etc/ssh/*_key.pub
          sed -i -e '$!d' /home/ec2-user/.ssh/authorized_keys
          sed -i -e '$!d' /root/.ssh/authorized_keys

  AutoStartRole:
    Type: "AWS::IAM::Role"
    Condition: isT2Micro
    Properties:
      AssumeRolePolicyDocument:  
        Version: "2012-10-17"  
        Statement:   
          -   
            Effect: "Allow"  
            Principal:   
              Service:   
                - "events.amazonaws.com" 
                - "ssm.amazonaws.com"
                - "ec2.amazonaws.com"
            Action:   
              - "sts:AssumeRole"  
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/service-role/AmazonSSMAutomationRole"
      Path: "/"
      RoleName: role-kaiseki-auto-start


  AutoStartEC2Event:
    Type: AWS::Events::Rule
    Condition: isT2Micro
    Properties: 
      EventPattern: 
        source: 
          - "aws.ec2"
        detail-type: 
          - "EC2 Instance State-change Notification"
        detail:
          instance-id:
            - !Ref Instance
          state:
            - "stopped"
      State: "ENABLED"
      Name: AutoStartEC2Event
      RoleArn: !Sub "arn:aws:iam::${AWS::AccountId}:role/role-kaiseki-auto-start"
      Targets: 
        - Id: AutoStartEC2EventTarget
          Arn: !Sub "arn:aws:ssm:${AWS::Region}::automation-definition/AWS-StartEC2Instance:$DEFAULT"
          RoleArn: !Sub "arn:aws:iam::${AWS::AccountId}:role/role-kaiseki-auto-start"
          Input: 
            !Join
              - ''
              - - !Sub "{\"InstanceId\":[\""
                - !Ref Instance
                - !Sub "\"]}"

  AutoStopEC2Event:
    Type: AWS::CloudWatch::Alarm
    Condition: isT2Micro
    Properties:
      AlarmName: AutoStopEC2Alarm
      MetricName: CPUCreditBalance
      Namespace: AWS/EC2
      Statistic: Maximum
      Period: 60
      EvaluationPeriods: 1
      Threshold: 1
      ComparisonOperator: LessThanOrEqualToThreshold
      Dimensions:
        - Name: InstanceId
          Value: !Ref Instance
      AlarmActions:
        - !Sub arn:aws:automate:${AWS::Region}:ec2:stop
  
  EIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain: VPC
      InstanceId:  !Ref Instance

```
</div></details>

## このテンプレートで作成されるもの
* VPC(10.0.0.0/16)
* Subnet(10.0.0.0/24, ap-northeast-1a)※いわゆるパブリックサブネット
* Internet Gateway
* RouteTable
 * Internet Gateway に対するルートを設定 
* セキュリティグループ
 * インバウンドは Folding@Home の状況確認ができる画面に対するポート(7396)のみ。SSH などは未開封
 * アウトバウンドは 0.0.0.0/0 のデフォルト設定
* IAMRole
 * SSM Agent 経由で EC2 インスタンスに接続するための IAMRole  
* EC2 インスタンス
 * 最新の AmazonLinux2 AMI から起動し、 Folding@Home Client をインストールし初期セットアップ 
* **インスタンスタイプが 無料枠の対象である t2.micro の場合**
 * CPU クレジットが 0 になった際に EC2 インスタンスを停止する CloudWatch Alarm
 * 停止した EC2 を起動する CloudWatch Events
 * この CloudWatch Events を動かすための IAMRole

## 使い方
### 環境構築
CloudFormation テンプレートを使って CloudFormation スタックを作成します。
その際、以下のパラメータが指定可能ですので、適宜設定を行ってください。

* CPUPower
 * CPU をどれだけ利用するかを指定できます、デフォルトでは full です。 middle, light も選択できます。 
* InstanceType
 * デフォルトでは無料枠対象の t2.micro としています。GPUインスタンスやコンピュート最適化インスタンスなどを指定すると良いでしょう。 t2.micro の場合は前述の通り CPU クレジットが 0 になると自動的に停止、起動するようになり、自動的に CPU クレジットを回復できるようになっています。
* KeyPair
 * 作成済みのキーペアから使用するものを選択してください。
* MyIP
 * 事前準備で確認した IP アドレスを 10.11.12.13/32 のような CIDR 表記で指定します。 
* TeamId
 * デフォルトではチーム名が [Japan](https://stats.foldingathome.org/team/222) である 222 を設定しています。
* UserName
 * デフォルトでは初期値である Anonymous(匿名の意) にしてあります。任意のユーザー名をご指定ください。 

### 動作確認
EC２ のパブリックDNS(IPv4)の値を確認します。
起動した EC2 インスタンスを選択して、赤枠で囲んである箇所に表示されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/548623/8a21ae5c-1115-4cf8-2943-4ba9b6d7d1b8.png)


確認したパブリックDNS名にポート番号7396を指定してWebブラウザからアクセスします。
例えば、パブリックDNS名が ec2-111-222-333-444.ap-northeast-1.compute.amazonaws.com であれば、以下のようになります。

http://ec2-111-222-333-444.ap-northeast-1.compute.amazonaws.com:7396/
※ありえない URL です。

以下のような画面が表示されれば OK です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/548623/b8a7c7d4-c790-83c0-03fe-7f0f546c20bd.png)

インスタンスタイプが t2.micro であれば、起動から概ね30分～35分後に自動で停止して起動します。

## 注意点
vCPU 数が多すぎても動作に支障がでるようです。
検証した範囲では vCPU 数が16より多い(例えば g4dn.8xlarge のように vCPU 数が32個など)と解析がうまく動きませんでした。

同様に GPU についても、Tesla ドライバではうまく動かず、 GRID ドライバを利用する必要がありました。そのため、 g系インスタンスを使うか、p3インスタンスに GRID ドライバインスト済み AMI を導入する必要があります。。
※わたしの環境だけかもしれませんが、ご注意ください。
※本作例の CFn で利用している AMI では GPU ドライバ未適用ですので利用の際は適宜改造してください。

あなたがもし石油王であっても GPU インスタンスの一番強力なインスタンスを大量利用するのではなく、ほどほどのインスタンスを超大量に利用するのがよいでしょう。

## 成果
### コンピューティング最適化インスタンス
インスタンスタイプ：c5n.4xlarge
vCPU数:16
メモリ:42GiB
動作期間：約3日程度
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/548623/8d143a81-aee5-6b2a-04dd-42ad4d1408c9.png)

### GPUインスタンス
インスタンスタイプ：g4dn.4xlarge
vCPU数：16
メモリ：64GiB
動作期間：約3時間程度
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/548623/c4cab2fc-1f1e-f028-83c4-0ab2cc99219d.png)


## まとめ
この貢献により1日も早く新型コロナによる被害の鎮静化、特効薬の創出などに繋がることを祈念しております。

## その他
スポットインスタンスを利用することで廉価に解析に貢献することも可能です！

## 参考資料
https://qiita.com/deflatSOCO/items/965208e74168f6e0d984
https://dev.classmethod.jp/articles/foldingathome_covid-19/
https://pc.watch.impress.co.jp/docs/news/1243458.html
https://ascii.jp/elem/000/004/008/4008887/