ゲノムブラウザ JBrowse2 を CloudFront + S3 で Web 公開してみた

モダンなゲノムブラウザ Web 公開環境を構築しよう!
2022.06.15

ゲノムブラウザの JBrowse を Web 公開するときに Web サーバー(Apache, Nginx)が必要なのか調べていました。 すると、静的サイトなので Amazon S3 でも動作すると公式ガイドに書いてあります。ということで試してみました。

JBrowse doesn't strictly need Apache or nginx, it is "static site compatible" meaning it uses no server code and can run on any static website hosting. For example, you can upload the jbrowse folder that we prepared here in /var/www/html/jbrowse2 to Amazon S3, and it will work there too. See the FAQ for what webserver do I need for more info.

引用: JBrowse 2 combined guide | JBrowse

ところで S3 で公開するとなにが嬉しいの?と言われますと、サーバーに比べると格段に維持費が安いのも嬉しいですけど、なにより Web サーバーの運用・管理をしなくて済むのが嬉しいのではないでしょうか。今回は定番の構成で CDN のサービスとセットで環境構築します。

構成図

CloudFront(CDN)+ S3 構成にカスタムドメインでアクセスできる環境を Cloudformation で作成します。

CloudFront と S3 の準備

ゲノムブラウザを Web 公開するための静的サイトを構築します。あとで中身(ファイル)を入れて静的サイトが完成します。

前提

テンプレート

CloudFront のログは180日保存で自動削除のライフサイクルルールが設定してあります。ログ保持期限ポリシーがある場合はお気をつけください。

bigmuramura/cfn-cloudfront-s3にも置いてあります。

折りたたみ
AWSTemplateFormatVersion: "2010-09-09"
Description: Cloudfront and S3

Parameters:
  ProjectName:
    Description: Project Name
    Type: String
    Default: unnamed
  Environment:
    Description: Environment
    Type: String
    Default: dev
    AllowedValues:
      - prod
      - dev
  S3BucketName:
    Description: Bucket name for static site
    Type: String
  AliasName:
    Description: Alias for CloudFront
    Type: String
  HostedZoneId:
    Description: Route53 Host Zone ID
    Type: String
  CertificateId:
    Description: ACM Certificate ID must be us-east-1 region
    Type: String

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Common Settings
        Parameters:
          - ProjectName
          - Environment
      - Label:
          default: SSL Settings
        Parameters:
          - AliasName
          - HostedZoneId
          - CertificateId

Resources:
  # ------------------------------------------------------------------------------------ #
  # S3
  # ------------------------------------------------------------------------------------ #
  # Static site bucket
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub ${ProjectName}-${Environment}-${S3BucketName}-${AWS::AccountId}
      OwnershipControls:
        Rules:
          - ObjectOwnership: "BucketOwnerEnforced"
      PublicAccessBlockConfiguration:
        BlockPublicAcls: True
        BlockPublicPolicy: True
        IgnorePublicAcls: True
        RestrictPublicBuckets: True
      VersioningConfiguration:
        Status: Enabled
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: "AES256"
            BucketKeyEnabled: false
      LifecycleConfiguration:
        Rules:
          - Id: AbortIncompleteMultipartUpload
            AbortIncompleteMultipartUpload:
              DaysAfterInitiation: 7
            Status: "Enabled"
          - Id: NoncurrentVersionExpiration
            NoncurrentVersionExpiration:
              NewerNoncurrentVersions: 3
              NoncurrentDays: 1
            Status: Enabled
  # Bucket Policy for CloudFront
  BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref S3Bucket
      PolicyDocument:
        Statement:
          - Action: s3:GetObject
            Effect: Allow
            Resource: !Sub arn:aws:s3:::${ProjectName}-${Environment}-${S3BucketName}-${AWS::AccountId}/*
            Principal:
              AWS: !Sub arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${CloudFrontOriginAccessIdentity}

  # ------------------------------------------------------------------------------------ #
  # CloudFront
  # ------------------------------------------------------------------------------------ #
  CloudFrontDistribution:
    Type: "AWS::CloudFront::Distribution"
    Properties:
      DistributionConfig:
        PriceClass: PriceClass_All
        Origins:
          - DomainName: !Sub ${ProjectName}-${Environment}-${S3BucketName}-${AWS::AccountId}.s3.${AWS::Region}.amazonaws.com
            Id: !Sub "S3origin-${ProjectName}-${Environment}-${S3BucketName}-${AWS::AccountId}"
            S3OriginConfig:
              OriginAccessIdentity: !Sub "origin-access-identity/cloudfront/${CloudFrontOriginAccessIdentity}"
        DefaultRootObject: index.html
        DefaultCacheBehavior:
          TargetOriginId: !Sub "S3origin-${ProjectName}-${Environment}-${S3BucketName}-${AWS::AccountId}"
          Compress: true
          ViewerProtocolPolicy: redirect-to-https
          CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6 # CachingOptimized
          AllowedMethods:
            - GET
            - HEAD
          CachedMethods:
            - GET
            - HEAD
          ForwardedValues:
            Cookies:
              Forward: none
            QueryString: false
        Logging:
          Bucket: !GetAtt S3BucketLogs.DomainName
          IncludeCookies: false
        Aliases:
          - !Ref AliasName
        ViewerCertificate:
          SslSupportMethod: sni-only
          MinimumProtocolVersion: TLSv1.2_2021
          AcmCertificateArn: !Sub "arn:aws:acm:us-east-1:${AWS::AccountId}:certificate/${CertificateId}"
        HttpVersion: http2
        IPV6Enabled: true
        Enabled: true
  # OAI
  CloudFrontOriginAccessIdentity:
    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
    Properties:
      CloudFrontOriginAccessIdentityConfig:
        Comment: Unique Domain Hosting Environment
  # CloudFront log bucket
  S3BucketLogs:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      BucketName: !Sub ${ProjectName}-${Environment}-${S3BucketName}-cloudfrontlogs-${AWS::AccountId}
      OwnershipControls:
        Rules:
          - ObjectOwnership: "ObjectWriter"
      PublicAccessBlockConfiguration:
        BlockPublicAcls: True
        BlockPublicPolicy: True
        IgnorePublicAcls: True
        RestrictPublicBuckets: True
      VersioningConfiguration:
        Status: Enabled
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: "AES256"
            BucketKeyEnabled: false
      LifecycleConfiguration:
        Rules:
          - Id: AbortIncompleteMultipartUpload
            AbortIncompleteMultipartUpload:
              DaysAfterInitiation: 7
            Status: "Enabled"
          - Id: CurrentVersionExpiration
            ExpirationInDays: 180
            Status: "Enabled"
          - Id: NoncurrentVersionExpiration
            NoncurrentVersionExpiration:
              NoncurrentDays: 30
            Status: "Enabled"
  # Alias Record
  Route53RecordSet:
    Type: AWS::Route53::RecordSet
    Properties:
      Name: !Sub ${AliasName}
      HostedZoneId: !Sub ${HostedZoneId}
      Type: A
      AliasTarget:
        DNSName: !GetAtt CloudFrontDistribution.DomainName
        HostedZoneId: Z2FDTNDATAQYW2 # fixed

パラメータで前提事項であった事前作成済みリソースのIDを入力します。

  • ホストゾーンID
    • Route53よりドメインを確認
  • 証明書ID
    • バージニア北部(us-east-1)で発行した証明書を確認

CloudFront の他、S3 バケットが2つ作成されます。上が静的サイトとして CloudFront を通して Web 公開されるバケットです。下は CloudFront のアクセスログ保存用のバケットです。後ほど上のバケットに Jbrowse2 のファイルをアップロードします。

JBrowse のファイル準備

前回 JBrowse ローカル実行環境を作成しましたので、以下のリンクを参考にjbrowse2フォルダを作成し静的サイトに必要ファイルを生成します。 最終的に /var/www/html配下に作成されるjbrowse2フォルダを S3 にアップロードして Web 公開します。

jbrowse2 フォルダ作成

/var/www/html配下にjbrowseフォルダ作成する予定のためローカル PC のディレクトリをマウントします。これでコンテナ内の JBrowse CLI でセットアップする初期ファイルを永続化できます。ここではマウントするディレクトリは/Users/ohmura.yasutaka/work/jbrowse/volを例にすすめていきます。

$ docker run -it -p 3000:3000 -v /Users/ohmura.yasutaka/work/jbrowse/vol:/var/www/html jbrowse2:v1 bash

前回のローカル環境構築手順と同様にショウジョウバエのリファレンスゲノムをサンプルに使用します。

# jbrowse create /var/www/html/jbrowse2
# cd /tmp
# wget https://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/000/001/215/GCF_000001215.4_Release_6_plus_ISO1_MT/GCF_000001215.4_Release_6_plus_ISO1_MT_genomic.fna.gz
# gunzip GCF_000001215.4_Release_6_plus_ISO1_MT_genomic.fna.gz
# samtools faidx GCF_000001215.4_Release_6_plus_ISO1_MT_genomic.fna
# jbrowse add-assembly GCF_000001215.4_Release_6_plus_ISO1_MT_genomic.fna --out /var/www/html/jbrowse2 --load copy

コンテナにマウントしたローカルディレクトリ(vol配下)に必要なjbrowse2フォルダが作成・保存されました。

$ pwd
/Users/ohmura.yasutaka/work/jbrowse/vol

 $ tree -L 2
.
└── jbrowse2
    ├── GCF_000001215.4_Release_6_plus_ISO1_MT_genomic.fna
    ├── GCF_000001215.4_Release_6_plus_ISO1_MT_genomic.fna.fai
    ├── asset-manifest.json
    ├── config.json
    ├── favicon.ico
    ├── index.html
    ├── manifest.json
    ├── robots.txt
    ├── static
    ├── test_data
    └── version.txt

ファイルをアップロード

Cloudformation で作成した静的サイト用の S3 バケットに jbrowse2 配下のファイルをコピーします。オリジナルの TOP ページを作成する場合は index.html を自作してアップロードし、jbrowse2のファイル類はパスを切ってアップロードしてください。ここでは Jbrowse2 で必要なファイルをシンプルにアップロードします。

.fnaファイルの容量はそこそこのサイズがあると思いますので aws s3 sync で転送すると効率が良いです。

$ aws s3 sync ~/work/jbrowse/vol/jbrowse2/ s3://sample-dev-jbrowse-123456789012

S3 バケットを確認するとファイルがアップロードされています。

index.html について触れたのは CloudFront の設定にあるデフォルトルートオブジェクトで指定済みだったためです。CloudFront のテンプレートに書き込んでいるため、必要に応じてファイル名に変更してください。

Web アクセスしてみる

https://独自ドメイン でアクセスできます。

CloudFront + S3 バケットで構成した環境でもゲノムブラウザを操作できますね。

リロードするとキャッシュにヒットしていることから CloudFront からの配信であることも確認できます。

補足 アクセス制限

S3 へ直接アクセスはできない構成にしてあります。CloudFront でアクセス制限をかければ公開範囲を制限することもできます。

グローバル IP 制限

Basic 認証

おわりに

バイオインフォマティクスで使われるツールのドキュメントにも AWS の用語が登場する時代なんだという発見がありました。と同時に第一線の研究者が S3 で静的サイトを構築できるかというとまた話は別かなとも思いました。研究者とクラウドのインフラをいい感じに取り持てる人は研究現場で重宝されそうですね。

参考