サーバレスで実現する!ゲノムブラウザ JBrowse 2 の実行環境

サーバレスで実現する!ゲノムブラウザ JBrowse 2 の実行環境

CloudFormation テンプレートで実行環境を簡単にデプロイできます。
Clock Icon2025.01.31

ゲノムブラウザ JBrowse2 のサーバレス構成について、研究現場からのフィードバックを反映した改善版を紹介します。本記事では、CloudFront + S3 による基本構成に加え、以下の改善を実施しました。

  • CloudFront Origin Access Control (OAC) の採用
  • CloudFront 標準ログ(v2)の採用
  • 複数プロジェクトでの CloudFront のログ集約管理

https://dev.classmethod.jp/articles/publish-jbrowse2-to-the-web/

ゲノムブラウザをサーバレスな構成にすることのメリット

サーバレスなので Linux などのサーバの管理が不要になります。利用費はデータ保存料(サイズが大きなファイルは主にゲノムデータ)と、データ転送料のみでとても安価に運用できます。CDN(Contents Delivery Network)を介して配信するため、アクセスが集中しても問題なく、世界中からレスポンス良くアクセスできます。

デメリット

AWS の学習コストが発生することです。ですが、今の時代オンプレミス環境で Web サーバを起動して配信環境の構築、日々の運用負担を考えると学習価値はあると思います。実際問題、運用フェーズですと一番高いのは人件費ですからね。サーバのお守りに費やす時間を削減しましょう。

構成概要

一般的な CloundFront + S3 構成です。また、前回の構成から以下の AWS のアップデートを反映させた構成としました。

JBrowse2CloudFront-1-1.png

運用面の改善

ドメインの管理を省略するために独自のドメインを使用せずに CloundFront デフォルトの URL を使用して https アクセスする方針とします。

また、CloudFront のアクセスログは複数の CloundFront ディストリビューションから単一の S3 バケットに保存する方針とします。

JBrowse2CloudFront-1(3).png

CloudFront + S3 環境構築面の改善

運用面の改善に伴い、CloudFormation テンプレートを 2 つに分けました。初回に 1 度作成が必要な S3 バケット(青色)と、配信環境用の CloundFront + S3 セット(ピンク色)に分けています。

JBrowse2CloudFront-1(1).png

これにより、研究プロジェクト毎に CloudFront + S3 セットを簡単に量産できます。

JBrowse2CloudFront-1(2).png

CloudFormation テンプレート

テンプレートの分割理由

  1. 共有リソース(ログバケット)の一元管理
  2. プロジェクト固有リソースの独立したライフサイクル管理
  3. 環境構築の再利用性向上

CloudFront 用のログ保存 S3 バケット作成

  • S3 バケットの基本設定
    • バケット名は AWS アカウント ID をサフィックスとして使用
    • バケット名はハードコードしているため、必要に応じて修正してください
      • multiple-cloudfront-logs-bucket-[AWSアカウントID]
    • バージョニングを有効化
  • セキュリティ設定
    • CloudFront からのアクセスのみを許可(OAC を採用)
  • ライフサイクルルール
    • 保存したログについては削除しない限りは永続保存
    • 7 日以上経過した古いバージョン(削除したログ) を自動削除
    • 未完了のマルチパートアップロードを 7 日後に削除

バケットポリシーは制限を厳しくしかったのですが、今後追加されていく複数の CloudFront ディストリビューションに備えてガチガチな制限はしていません。CloudFront 作成後に都度バケットポリシーを変更する運用が可能でしたら、CloudFrontn のディストリビューション名で制限入れた方がベターです。

テンプレート 1
cloudfront-logs-bucket.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: CloudFront Logs S3 Bucket for Access Log v2 (Shared by multiple CloudFront distributions)

Parameters:
  NoncurrentVersionRetentionDays:
    Description: Number of days to retain noncurrent versions
    Type: Number
    Default: 7
    MinValue: 1
    MaxValue: 365

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Required input parameters"
        Parameters:
          - NoncurrentVersionRetentionDays

Resources:
  S3BucketLogs:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      BucketName: !Sub multiple-cloudfront-logs-bucket-${AWS::AccountId}
      OwnershipControls:
        Rules:
          - ObjectOwnership: BucketOwnerEnforced
      PublicAccessBlockConfiguration:
        BlockPublicAcls: True
        BlockPublicPolicy: True
        IgnorePublicAcls: True
        RestrictPublicBuckets: True
      VersioningConfiguration:
        Status: Enabled
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: "AES256"
            BucketKeyEnabled: true
      LifecycleConfiguration:
        Rules:
          - Id: AbortIncompleteMultipartUpload
            AbortIncompleteMultipartUpload:
              DaysAfterInitiation: 7
            Status: "Enabled"
          - Id: DeleteOldVersions
            NoncurrentVersionExpiration:
              NoncurrentDays: !Ref NoncurrentVersionRetentionDays
            Status: Enabled

  S3BucketLogsPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref S3BucketLogs
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: AllowCloudFrontServicePrincipalWrite
            Effect: Allow
            Principal:
              Service: delivery.logs.amazonaws.com
            Action:
              - s3:PutObject
            Resource: !Sub ${S3BucketLogs.Arn}/*
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref 'AWS::AccountId'

Outputs:
  LogsBucketName:
    Description: Name of the CloudFront logs bucket
    Value: !Ref S3BucketLogs
    Export:
      Name: !Sub ${AWS::StackName}-LogsBucketName
  LogsBucketArn:
    Description: ARN of the CloudFront logs bucket
    Value: !GetAtt S3BucketLogs.Arn
    Export:
      Name: !Sub ${AWS::StackName}-LogsBucketArn
  LogsBucketDomainName:
    Description: Domain name of the CloudFront logs bucket
    Value: !GetAtt S3BucketLogs.DomainName
    Export:
      Name: !Sub ${AWS::StackName}-LogsBucketDomainName

CloudFront + S3 配信構成セット

  • S3 バケットの設定
    • 入力したプロジェクト名とアカウント ID によるユニークなバケット名を作成
    • 30 日後に STANDARD_IA ストレージクラスへ自動移行
    • 削除、または上書きしたファイルは最新から 3 バージョンのみ保持
  • CloudFront の設定
    • OAC によるセキュアなアクセス制御
    • 1 秒キャッシュポリシーの適用
    • HTTP/2 および IPv6 の有効化
  • ログ配信の設定
    • 標準ログ(v2)を採用
    • JSON 形式でのログ出力
テンプレート 2
cloundfornt-s3.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: Static contents distribution using S3 and CloudFront with OAC

Parameters:
  ProjectName:
    Description: Project Name
    Type: String
  Description:
    Description: Description for CloudFront Distribution
    Type: String

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Required input parameters"
        Parameters:
          - ProjectName
          - Description

Resources:
  S3Bucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain
    Properties:
      BucketName: !Sub ${ProjectName}-bucket-${AWS::AccountId}
      OwnershipControls:
        Rules:
          - ObjectOwnership: "BucketOwnerEnforced"
      PublicAccessBlockConfiguration:
        BlockPublicAcls: True
        BlockPublicPolicy: True
        IgnorePublicAcls: True
        RestrictPublicBuckets: True
      VersioningConfiguration:
        Status: Enabled
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: "AES256"
            BucketKeyEnabled: true
      LifecycleConfiguration:
        Rules:
          - Id: AbortIncompleteMultipartUpload
            AbortIncompleteMultipartUpload:
              DaysAfterInitiation: 7
            Status: "Enabled"
          - Id: TransitionToIA
            Transitions:
              - StorageClass: STANDARD_IA
                TransitionInDays: 30
            Status: "Enabled"
          - Id: NoncurrentVersionExpiration
            NoncurrentVersionExpiration:
              NewerNoncurrentVersions: 3
              NoncurrentDays: 7
            Status: Enabled

  BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref S3Bucket
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: AllowCloudFrontServicePrincipal
            Effect: Allow
            Principal:
              Service: cloudfront.amazonaws.com
            Action: s3:GetObject
            Resource: !Sub ${S3Bucket.Arn}/*
            Condition:
              StringEquals:
                AWS:SourceArn: !Sub arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}

  CloudFrontOriginAccessControl:
    Type: AWS::CloudFront::OriginAccessControl
    Properties:
      OriginAccessControlConfig:
        Name: !Sub ${ProjectName}-OAC
        Description: Origin Access Control for S3
        SigningBehavior: always
        SigningProtocol: sigv4
        OriginAccessControlOriginType: s3

  OneSecondCachePolicy:
    Type: AWS::CloudFront::CachePolicy
    Properties:
      CachePolicyConfig:
        Name: !Sub ${ProjectName}-cache-policy
        Comment: "Cache for 1 second"
        DefaultTTL: 1
        MaxTTL: 1
        MinTTL: 1
        ParametersInCacheKeyAndForwardedToOrigin:
          CookiesConfig:
            CookieBehavior: none
          HeadersConfig:
            HeaderBehavior: none
          QueryStringsConfig:
            QueryStringBehavior: none
          EnableAcceptEncodingBrotli: true
          EnableAcceptEncodingGzip: true

  CloudFrontLogsDeliverySource:
    Type: AWS::Logs::DeliverySource
    Properties:
      Name: !Sub ${ProjectName}-cloudfront-delivery-source
      ResourceArn: !Sub arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}
      LogType: ACCESS_LOGS

  CloudFrontLogsDeliveryDestination:
    Type: AWS::Logs::DeliveryDestination
    Properties:
      Name: !Sub ${ProjectName}-cloudfront-delivery-destination
      DestinationResourceArn: !Sub arn:aws:s3:::multiple-cloudfront-logs-bucket-${AWS::AccountId}
      OutputFormat: json

  CloudFrontLogsDelivery:
    Type: AWS::Logs::Delivery
    DependsOn: CloudFrontLogsDeliverySource
    Properties:
      DeliverySourceName: !Sub ${ProjectName}-cloudfront-delivery-source
      DeliveryDestinationArn: !GetAtt CloudFrontLogsDeliveryDestination.Arn
      RecordFields:
        - timestamp
        - DistributionId
        - date
        - time
        - x-edge-location
        - sc-bytes
        - c-ip
        - cs-method
        - cs(Host)
        - cs-uri-stem
        - sc-status
        - cs(Referer)
        - cs(User-Agent)
        - cs-uri-query
        - cs(Cookie)
        - x-edge-result-type
        - x-edge-request-id
        - x-host-header
        - cs-protocol
        - cs-bytes
        - time-taken
        - x-forwarded-for
        - ssl-protocol
        - ssl-cipher
        - x-edge-response-result-type
        - cs-protocol-version
        - fle-status
        - fle-encrypted-fields
        - c-port
        - time-to-first-byte
        - x-edge-detailed-result-type
        - sc-content-type
        - sc-content-len
        - sc-range-start
        - sc-range-end
        - timestamp(ms)
        - origin-fbl
        - origin-lbl
        - asn
        - c-country
        - cache-behavior-path-pattern
      S3SuffixPath: !Sub ${ProjectName}/{yyyy}/{MM}/{dd}/{HH}
      S3EnableHiveCompatiblePath: false

  CloudFrontDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Comment: !Ref Description
        PriceClass: PriceClass_200
        Origins:
          - DomainName: !GetAtt S3Bucket.RegionalDomainName
            Id: S3Origin
            OriginAccessControlId: !Ref CloudFrontOriginAccessControl
            S3OriginConfig:
              OriginAccessIdentity: ''
        DefaultRootObject: index.html
        DefaultCacheBehavior:
          TargetOriginId: S3Origin
          Compress: true
          ViewerProtocolPolicy: redirect-to-https
          CachePolicyId: !Ref OneSecondCachePolicy
          OriginRequestPolicyId: 88a5eaf4-2fd4-4709-b370-b4c650ea3fcf # CORS-S3Origin
          AllowedMethods:
            - GET
            - HEAD
          CachedMethods:
            - GET
            - HEAD
        HttpVersion: http2
        IPV6Enabled: true
        Enabled: true

Outputs:
  CloudFrontDomainName:
    Description: CloudFront Distribution Domain Name
    Value: !GetAtt CloudFrontDistribution.DomainName
  S3BucketName:
    Description: S3 Bucket Name
    Value: !Ref S3Bucket
  S3BucketArn:
    Description: S3 Bucket ARN
    Value: !GetAtt S3Bucket.Arn
  CloudFrontLogsDeliveryArn:
    Description: CloudFront Logs Delivery ARN
    Value: !GetAtt CloudFrontLogsDelivery.Arn

CloudFront の URL にアクセスした結果

CloudFront のデフォルトの URL にアクセスしてみます。問題なく JBrowse の画面を操作できます。

JBrowse_と_クエリエディタ___Athena___us-east-1.png

コンテンツのアップロード方法は前回の記事を参考にしてください。

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

ログ確認

CloudFront のアクセスログは、プロジェクト名ごとに以下のような階層構造で保存されます。
以下は CloudFront + S3 のセットを 2 組作成しているため、プロジェクト名が 2 つ(abashiri と kitami)があります。

multiple-cloudfront-logs-bucket-257284627556
└── AWSLogs
    └── 257284627556
        └── CloudFront
            ├── abashiri # <- CloudFormation テンプレートで入力したプロジェクト名
            │   └── 2025
            │       └── 01
            │           └── 31
            │               ├── 06
            │               │   └── E2K8036MS1GDS5.2025-01-31-06.a57adb5e.gz
            │               └── 07
            │                   ├── E2K8036MS1GDS5.2025-01-31-07.24dea1d9.gz
            │                   └── E2K8036MS1GDS5.2025-01-31-07.7610839b.gz
            └── kitami # <- 同じくプロジェクト名
                └── 2025
                    └── 01
                        └── 31
                            └── 07
                                └── E2F5T7ZC07SF6T.2025-01-31-07.d42c4df1.gz

Athena でログ検索

CloudFront の標準ログ(v2)で JSON 形式で出力するようにしました。Athena で検索が可能か試してみます。

プロジェクト名毎にプレフィックスが異なるため、abashiriプロジェクトのログを検索するためのテーブルを作成しました。

CREATE EXTERNAL TABLE `cloudfront_logs_abashiri`(
  `timestamp` string COMMENT 'from deserializer',
  `DistributionId` string COMMENT 'from deserializer',
  `date` string COMMENT 'from deserializer',
  `time` string COMMENT 'from deserializer',
  `x-edge-location` string COMMENT 'from deserializer',
  `sc-bytes` string COMMENT 'from deserializer',
  `c-ip` string COMMENT 'from deserializer',
  `cs-method` string COMMENT 'from deserializer',
  `cs(Host)` string COMMENT 'from deserializer',
  `cs-uri-stem` string COMMENT 'from deserializer',
  `sc-status` string COMMENT 'from deserializer',
  `cs(Referer)` string COMMENT 'from deserializer',
  `cs(User-Agent)` string COMMENT 'from deserializer',
  `cs-uri-query` string COMMENT 'from deserializer',
  `cs(Cookie)` string COMMENT 'from deserializer',
  `x-edge-result-type` string COMMENT 'from deserializer',
  `x-edge-request-id` string COMMENT 'from deserializer',
  `x-host-header` string COMMENT 'from deserializer',
  `cs-protocol` string COMMENT 'from deserializer',
  `cs-bytes` string COMMENT 'from deserializer',
  `time-taken` string COMMENT 'from deserializer',
  `x-forwarded-for` string COMMENT 'from deserializer',
  `ssl-protocol` string COMMENT 'from deserializer',
  `ssl-cipher` string COMMENT 'from deserializer',
  `x-edge-response-result-type` string COMMENT 'from deserializer',
  `cs-protocol-version` string COMMENT 'from deserializer',
  `fle-status` string COMMENT 'from deserializer',
  `fle-encrypted-fields` string COMMENT 'from deserializer',
  `c-port` string COMMENT 'from deserializer',
  `time-to-first-byte` string COMMENT 'from deserializer',
  `x-edge-detailed-result-type` string COMMENT 'from deserializer',
  `sc-content-type` string COMMENT 'from deserializer',
  `sc-content-len` string COMMENT 'from deserializer',
  `sc-range-start` string COMMENT 'from deserializer',
  `sc-range-end` string COMMENT 'from deserializer',
  `timestamp(ms)` string COMMENT 'from deserializer',
  `origin-fbl` string COMMENT 'from deserializer',
  `origin-lbl` string COMMENT 'from deserializer',
  `asn` string COMMENT 'from deserializer',
  `c-country` string COMMENT 'from deserializer',
  `cache-behavior-path-pattern` string COMMENT 'from deserializer'
)
PARTITIONED BY (
  year STRING,
  month STRING,
  day STRING,
  hour STRING
)
ROW FORMAT SERDE
  'org.openx.data.jsonserde.JsonSerDe'
STORED AS INPUTFORMAT
  'org.apache.hadoop.mapred.TextInputFormat'
OUTPUTFORMAT
  'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION
  's3://multiple-cloudfront-logs-bucket-257284627556/AWSLogs/257284627556/CloudFront/abashiri/'
TBLPROPERTIES (
  'compressionType'='gzip',
  'has_encrypted_data'='false',
  'ignore.malformed.json'='true',
  'projection.enabled'='true',
  'projection.year.type'='integer',
  'projection.year.range'='2024,2025',
  'projection.month.type'='integer',
  'projection.month.range'='1,12',
  'projection.month.digits'='2',
  'projection.day.type'='integer',
  'projection.day.range'='1,31',
  'projection.day.digits'='2',
  'projection.hour.type'='integer',
  'projection.hour.range'='0,23',
  'projection.hour.digits'='2',
  'storage.location.template'='s3://multiple-cloudfront-logs-bucket-257284627556/AWSLogs/257284627556/CloudFront/abashiri/${year}/${month}/${day}/${hour}'
);

ログの検索もできました。

クエリエディタ___Athena___us-east-1.png

まとめ

  • CloudFront + S3 のサーバレス構成による運用負荷の削減
  • OAC 採用とログ集約による、セキュリティと運用性の向上
  • CloudFormation テンプレートの分割による、環境構築の再利用性向上

おわりに

CloudFront の標準ログ(v2)設定に苦労しました。もう少しバケットポリシーの制限を頑張れないか考えてみます。

参考

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.