AWSアーティファクト管理:ECR、CodeArtifact、イメージビルド戦略

Shunku

CI/CDパイプラインにおいて、アーティファクトの適切な管理は品質とセキュリティの要です。本記事では、AWSでのアーティファクト管理サービスと戦略を解説します。

アーティファクト管理の全体像

サービス比較

flowchart TB
    subgraph Artifacts["アーティファクトタイプ"]
        Container["コンテナイメージ"]
        Package["パッケージ"]
        Binary["バイナリ/JAR"]
        AMI["マシンイメージ"]
    end

    Container --> ECR["Amazon ECR"]
    Package --> CodeArtifact["CodeArtifact"]
    Binary --> S3["Amazon S3"]
    AMI --> ImageBuilder["EC2 Image Builder"]

    style Artifacts fill:#3b82f6,color:#fff
サービス 用途 対応形式
ECR コンテナイメージ Docker, OCI
CodeArtifact パッケージ管理 npm, Maven, pip, NuGet
S3 汎用アーティファクト 任意のファイル
EC2 Image Builder AMI Amazon Linux, Windows

Amazon ECR

リポジトリ設定

ECRRepository:
  Type: AWS::ECR::Repository
  Properties:
    RepositoryName: my-app
    ImageTagMutability: IMMUTABLE  # タグの上書き禁止
    ImageScanningConfiguration:
      ScanOnPush: true  # プッシュ時スキャン
    EncryptionConfiguration:
      EncryptionType: KMS
      KmsKey: !GetAtt KMSKey.Arn
    Tags:
      - Key: Environment
        Value: production

ライフサイクルポリシー

{
  "rules": [
    {
      "rulePriority": 1,
      "description": "Keep last 10 production images",
      "selection": {
        "tagStatus": "tagged",
        "tagPrefixList": ["prod-", "release-"],
        "countType": "imageCountMoreThan",
        "countNumber": 10
      },
      "action": {
        "type": "expire"
      }
    },
    {
      "rulePriority": 2,
      "description": "Delete untagged images older than 1 day",
      "selection": {
        "tagStatus": "untagged",
        "countType": "sinceImagePushed",
        "countUnit": "days",
        "countNumber": 1
      },
      "action": {
        "type": "expire"
      }
    },
    {
      "rulePriority": 3,
      "description": "Delete dev images older than 14 days",
      "selection": {
        "tagStatus": "tagged",
        "tagPrefixList": ["dev-", "feature-"],
        "countType": "sinceImagePushed",
        "countUnit": "days",
        "countNumber": 14
      },
      "action": {
        "type": "expire"
      }
    }
  ]
}

イメージスキャン

flowchart LR
    subgraph Scanning["イメージスキャン"]
        Basic["基本スキャン<br/>(Clair)"]
        Enhanced["拡張スキャン<br/>(Inspector)"]
    end

    Basic --> CVE["CVE検出"]
    Enhanced --> OS["OS脆弱性"]
    Enhanced --> Package["パッケージ脆弱性"]
    Enhanced --> Continuous["継続的スキャン"]

    style Enhanced fill:#22c55e,color:#fff
# 拡張スキャンの有効化
ECRScanningConfiguration:
  Type: AWS::ECR::RegistryPolicy
  Properties:
    PolicyText:
      rules:
        - repositoryFilters:
            - filter: "*"
              filterType: WILDCARD
          scanFrequency: CONTINUOUS_SCAN

クロスアカウント/リージョン複製

ECRReplicationConfiguration:
  Type: AWS::ECR::ReplicationConfiguration
  Properties:
    ReplicationConfiguration:
      Rules:
        - Destinations:
            - Region: us-west-2
              RegistryId: !Ref AWS::AccountId
            - Region: ap-northeast-1
              RegistryId: "222222222222"  # 別アカウント
          RepositoryFilters:
            - Filter: prod-
              FilterType: PREFIX_MATCH

プルスルーキャッシュ

# パブリックレジストリのキャッシュ
PullThroughCacheRule:
  Type: AWS::ECR::PullThroughCacheRule
  Properties:
    EcrRepositoryPrefix: ecr-public
    UpstreamRegistryUrl: public.ecr.aws
# 使用例: ECR Public経由でイメージ取得
docker pull 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/ecr-public/amazonlinux/amazonlinux:2023

AWS CodeArtifact

ドメインとリポジトリ

flowchart TB
    subgraph CodeArtifact["CodeArtifact"]
        Domain["ドメイン"]
        subgraph Repos["リポジトリ"]
            Internal["内部リポジトリ"]
            Upstream["上流リポジトリ"]
        end
    end

    subgraph External["外部ソース"]
        NPM["npmjs.com"]
        Maven["Maven Central"]
        PyPI["PyPI"]
    end

    Domain --> Repos
    Upstream --> External

    style Domain fill:#3b82f6,color:#fff

セットアップ

CodeArtifactDomain:
  Type: AWS::CodeArtifact::Domain
  Properties:
    DomainName: my-organization
    EncryptionKey: !GetAtt KMSKey.Arn
    Tags:
      - Key: Environment
        Value: production

CodeArtifactRepository:
  Type: AWS::CodeArtifact::Repository
  Properties:
    DomainName: !GetAtt CodeArtifactDomain.Name
    RepositoryName: my-packages
    Description: Internal packages repository
    ExternalConnections:
      - public:npmjs
    Upstreams:
      - !GetAtt UpstreamRepository.Name

npm設定

# 認証トークン取得
aws codeartifact get-authorization-token \
    --domain my-organization \
    --domain-owner 123456789012 \
    --query authorizationToken \
    --output text

# npm設定
aws codeartifact login \
    --tool npm \
    --domain my-organization \
    --domain-owner 123456789012 \
    --repository my-packages

# .npmrc(自動生成される)
# registry=https://my-organization-123456789012.d.codeartifact.ap-northeast-1.amazonaws.com/npm/my-packages/
# //my-organization-123456789012.d.codeartifact.ap-northeast-1.amazonaws.com/npm/my-packages/:_authToken=${CODEARTIFACT_AUTH_TOKEN}

Maven設定

<!-- settings.xml -->
<settings>
  <profiles>
    <profile>
      <id>codeartifact</id>
      <repositories>
        <repository>
          <id>codeartifact</id>
          <url>https://my-organization-123456789012.d.codeartifact.ap-northeast-1.amazonaws.com/maven/my-packages/</url>
        </repository>
      </repositories>
    </profile>
  </profiles>
  <servers>
    <server>
      <id>codeartifact</id>
      <username>aws</username>
      <password>${env.CODEARTIFACT_AUTH_TOKEN}</password>
    </server>
  </servers>
  <activeProfiles>
    <activeProfile>codeartifact</activeProfile>
  </activeProfiles>
</settings>

CodeBuildでの使用

version: 0.2

env:
  exported-variables:
    - CODEARTIFACT_AUTH_TOKEN

phases:
  pre_build:
    commands:
      # CodeArtifact認証
      - export CODEARTIFACT_AUTH_TOKEN=$(aws codeartifact get-authorization-token --domain my-organization --domain-owner $AWS_ACCOUNT_ID --query authorizationToken --output text)
      - aws codeartifact login --tool npm --domain my-organization --domain-owner $AWS_ACCOUNT_ID --repository my-packages

  build:
    commands:
      - npm ci
      - npm run build

  post_build:
    commands:
      # パッケージ公開
      - npm publish

EC2 Image Builder

パイプライン構成

flowchart TB
    subgraph ImageBuilder["EC2 Image Builder"]
        Recipe["イメージレシピ"]
        Infra["インフラ設定"]
        Dist["配布設定"]
        Pipeline["パイプライン"]
    end

    Recipe --> |"コンポーネント"| Build["Build"]
    Infra --> |"インスタンス設定"| Build
    Build --> |"Testing"| Test["検証"]
    Test --> Dist
    Dist --> |"AMI配布"| Regions["複数リージョン"]

    style ImageBuilder fill:#f59e0b,color:#000

イメージレシピ

ImageRecipe:
  Type: AWS::ImageBuilder::ImageRecipe
  Properties:
    Name: my-golden-image
    Version: 1.0.0
    ParentImage: arn:aws:imagebuilder:ap-northeast-1:aws:image/amazon-linux-2023-x86/x.x.x
    Components:
      # AWS提供コンポーネント
      - ComponentArn: arn:aws:imagebuilder:ap-northeast-1:aws:component/update-linux/x.x.x
      - ComponentArn: arn:aws:imagebuilder:ap-northeast-1:aws:component/amazon-cloudwatch-agent-linux/x.x.x
      # カスタムコンポーネント
      - ComponentArn: !Ref CustomComponent
    BlockDeviceMappings:
      - DeviceName: /dev/xvda
        Ebs:
          VolumeSize: 30
          VolumeType: gp3
          Encrypted: true
          KmsKeyId: !Ref KMSKey
    WorkingDirectory: /tmp
    Tags:
      BaseOS: AmazonLinux2023

CustomComponent:
  Type: AWS::ImageBuilder::Component
  Properties:
    Name: install-app-dependencies
    Version: 1.0.0
    Platform: Linux
    Data: |
      name: InstallAppDependencies
      schemaVersion: 1.0
      phases:
        - name: build
          steps:
            - name: InstallPackages
              action: ExecuteBash
              inputs:
                commands:
                  - dnf install -y docker git jq
                  - systemctl enable docker

            - name: InstallCodeDeployAgent
              action: ExecuteBash
              inputs:
                commands:
                  - dnf install -y ruby wget
                  - cd /tmp
                  - wget https://aws-codedeploy-ap-northeast-1.s3.amazonaws.com/latest/install
                  - chmod +x install
                  - ./install auto
                  - systemctl enable codedeploy-agent

        - name: validate
          steps:
            - name: ValidateInstallation
              action: ExecuteBash
              inputs:
                commands:
                  - docker --version
                  - systemctl is-enabled codedeploy-agent

配布設定

DistributionConfiguration:
  Type: AWS::ImageBuilder::DistributionConfiguration
  Properties:
    Name: multi-region-distribution
    Distributions:
      - Region: ap-northeast-1
        AmiDistributionConfiguration:
          Name: my-app-{{ imagebuilder:buildDate }}
          Description: Golden AMI for my-app
          AmiTags:
            Name: my-app-golden
            BuildDate: "{{ imagebuilder:buildDate }}"
          LaunchPermissionConfiguration:
            UserIds:
              - "222222222222"  # 共有先アカウント
              - "333333333333"
      - Region: us-west-2
        AmiDistributionConfiguration:
          Name: my-app-{{ imagebuilder:buildDate }}

パイプライン

ImagePipeline:
  Type: AWS::ImageBuilder::ImagePipeline
  Properties:
    Name: weekly-golden-image
    ImageRecipeArn: !Ref ImageRecipe
    InfrastructureConfigurationArn: !Ref InfrastructureConfiguration
    DistributionConfigurationArn: !Ref DistributionConfiguration
    Schedule:
      ScheduleExpression: cron(0 0 ? * SUN *)  # 毎週日曜
      PipelineExecutionStartCondition: EXPRESSION_MATCH_AND_DEPENDENCY_UPDATES_AVAILABLE
    ImageTestsConfiguration:
      ImageTestsEnabled: true
      TimeoutMinutes: 60
    Status: ENABLED

InfrastructureConfiguration:
  Type: AWS::ImageBuilder::InfrastructureConfiguration
  Properties:
    Name: build-infrastructure
    InstanceProfileName: !Ref ImageBuilderInstanceProfile
    InstanceTypes:
      - m5.large
    SubnetId: !Ref PrivateSubnet
    SecurityGroupIds:
      - !Ref BuildSecurityGroup
    TerminateInstanceOnFailure: true
    Logging:
      S3Logs:
        S3BucketName: !Ref LogBucket
        S3KeyPrefix: image-builder/

S3アーティファクト管理

バージョニングとライフサイクル

ArtifactBucket:
  Type: AWS::S3::Bucket
  Properties:
    BucketName: !Sub "${AWS::StackName}-artifacts"
    VersioningConfiguration:
      Status: Enabled
    LifecycleConfiguration:
      Rules:
        - Id: DeleteOldVersions
          Status: Enabled
          NoncurrentVersionExpiration:
            NoncurrentDays: 30
        - Id: MoveToGlacier
          Status: Enabled
          Transitions:
            - StorageClass: GLACIER
              TransitionInDays: 90
          TagFilters:
            - Key: Archive
              Value: "true"
        - Id: CleanupIncompleteUploads
          Status: Enabled
          AbortIncompleteMultipartUpload:
            DaysAfterInitiation: 7
    BucketEncryption:
      ServerSideEncryptionConfiguration:
        - ServerSideEncryptionByDefault:
            SSEAlgorithm: aws:kms
            KMSMasterKeyID: !Ref KMSKey

アーティファクト構造

s3://my-artifacts/
├── releases/
│   ├── v1.0.0/
│   │   ├── app.jar
│   │   └── checksums.txt
│   └── v1.1.0/
│       ├── app.jar
│       └── checksums.txt
├── snapshots/
│   └── develop/
│       └── build-123/
│           └── app.jar
└── docker/
    └── my-app/
        └── v1.0.0/
            └── image.tar.gz

セキュリティベストプラクティス

イメージ署名

flowchart LR
    subgraph Signing["イメージ署名"]
        Build["Build"]
        Sign["署名"]
        Push["プッシュ"]
        Verify["検証"]
    end

    Build --> Sign
    Sign --> Push
    Push --> Verify

    style Signing fill:#22c55e,color:#fff

ECRイメージ署名(Notation)

# 署名の作成
notation sign \
    123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/my-app:v1.0.0 \
    --key my-signing-key

# 署名の検証
notation verify \
    123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/my-app:v1.0.0 \
    --trust-policy trust-policy.json

脆弱性対応フロー

# EventBridgeルール
ImageScanFindingsRule:
  Type: AWS::Events::Rule
  Properties:
    EventPattern:
      source:
        - aws.ecr
      detail-type:
        - ECR Image Scan
      detail:
        scan-status:
          - COMPLETE
        finding-severity-counts:
          CRITICAL:
            - numeric:
                - ">"
                - 0
    Targets:
      - Id: NotifySecurityTeam
        Arn: !Ref SecuritySNSTopic
      - Id: BlockDeployment
        Arn: !GetAtt BlockDeploymentLambda.Arn

まとめ

flowchart TB
    subgraph ArtifactManagement["アーティファクト管理"]
        ECR["ECR<br/>コンテナ"]
        CodeArtifact["CodeArtifact<br/>パッケージ"]
        ImageBuilder["Image Builder<br/>AMI"]
        S3["S3<br/>汎用"]
    end

    ECR --> Security["セキュリティスキャン"]
    CodeArtifact --> Upstream["上流プロキシ"]
    ImageBuilder --> Golden["ゴールデンイメージ"]
    S3 --> Lifecycle["ライフサイクル管理"]

    style ArtifactManagement fill:#3b82f6,color:#fff
サービス ベストプラクティス
ECR イミュータブルタグ、スキャン有効化
CodeArtifact 上流接続、ドメイン分離
Image Builder 定期ビルド、テスト自動化
S3 バージョニング、ライフサイクル

適切なアーティファクト管理により、セキュアで効率的なCI/CDパイプラインを実現できます。

参考資料