📚

マルチ CPU アーキテクチャ対応イメージを GitLab CE へ直接出力してみる

2021/09/11に公開

はじめに

元ネタは以下。
Dockerの「マルチCPUアーキテクチャ」に対応したイメージをビルドする | DevelopersIO

元ネタは Docker Hub を使っていたので、GitLab CE を使ってみる。

前提

  • gitlab.example.com というドメインで GitLab CE を動かしている。
  • oppara というアカウントで、docker-buildx というリポジトリを作成済み。
  • Dockerfile, go のソースは元ネタから変更なし。
  • Docker Desktop の「実験的機能」は有効化済み。

手順

1. ビルダーインスタンス作成

イメージ作成用のビルダーインスタンスを作成する。

% docker buildx create --name mybuilder
% docker buildx use mybuilder

2. GitLab にログイン

% docker login gitlab.example.com:5002

3. イメージのビルドと出力

GitLab の Container Registry へ直接出力する。

% docker buildx build --platform linux/amd64,linux/arm64 -t gitlab.example.com:5002/oppara/docker-buildx/go-webserver-sample --push .

ローカルで動作確認

コンテナを起動して

% docker run --rm -p 8080:8080 -d gitlab.example.com:5002/oppara/docker-buildx/go-webserver-sample

curl で確認。

% curl -s http://localhost:8080/ | npx -y html-cli && echo
<h1>Welcome Golang-WebServer!</h1>
<h2>Hostname: 1d13773c6cd9</h2>
<h2>OS: linux</h2>
<h2>Architecture: amd64</h2>

AWS で動作確認

1. 環境作成

CloudFormation を使って、x86(AMD64) と ARM64 な EC2 インスタンスを 1 台ずつ立ち上げる。

CloudFormation テンプレート

元ネタからの変更点

  • 安い EC2 インスタンスを立ち上げる 😅
  • Session Manager でアクセス出来るようにする
    • KeyName, MyIp のパラメータを削除
  • 各 EC2 インスタンスの IP アドレスを出力
  • 軽微なシンタックスエラーの修正
docker-multiarch.cf.yaml
---
AWSTemplateFormatVersion: "2010-09-09"
Description: "Launch x86(AMD64) and ARM64(Graviton2) EC2 instances with VPC environment"

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "General Information"
        Parameters:
          - SystemName
      - Label:
          default: "Network Configuration"
        Parameters:
          - CidrBlockVPC
          - CidrBlockSubnetPublic
      - Label:
          default: "x86(AMD64) EC2 Instance Configuration"
        Parameters:
          - X86ImageID
          - X86InstanceType
          - X86VolumeType
          - X86VolumeSize
      - Label:
          default: "ARM64(Graviton2) EC2 Instance Configuration"
        Parameters:
          - ARM64ImageID
          - ARM64InstanceType
          - ARM64VolumeType
          - ARM64VolumeSize

Parameters:
  SystemName:
    Type: String
    Default: docker-multiarch

  CidrBlockVPC:
    Type: String
    Default: 192.168.0.0/16

  CidrBlockSubnetPublic:
    Type: String
    Default: 192.168.1.0/24

  X86ImageID:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2

  X86InstanceType:
    Type: String
    Default: t3.small

  X86VolumeType:
    Type: String
    Default: gp2

  X86VolumeSize:
    Type: String
    Default: 20

  ARM64ImageID:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-arm64-gp2

  ARM64InstanceType:
    Type: String
    Default: t4g.small

  ARM64VolumeType:
    Type: String
    Default: gp2

  ARM64VolumeSize:
    Type: String
    Default: 20

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref CidrBlockVPC
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: !Sub "${SystemName}-vpc"
        - Key: System
          Value: !Ref SystemName

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub "${SystemName}-igw"
        - Key: System
          Value: !Ref SystemName

  VPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

  SubnetPublic:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select
        - 0
        - Fn::GetAZs: !Ref AWS::Region
      CidrBlock: !Ref CidrBlockSubnetPublic
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub "${SystemName}-public-subnet"
        - Key: System
          Value: !Ref SystemName

  RouteTablePublic:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${SystemName}-public-rtb"
        - Key: System
          Value: !Ref SystemName

  RouteIGW:
    DependsOn:
      - VPCGatewayAttachment
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref RouteTablePublic
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  RouteTableAssociationPublic:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref SubnetPublic
      RouteTableId: !Ref RouteTablePublic

  SecurityGroupServer:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub "${SystemName}-server-sg"
      GroupDescription: "Security group for server"
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: "0.0.0.0/0"
      Tags:
        - Key: Name
          Value: !Sub "${SystemName}-server-sg"
        - Key: System
          Value: !Ref SystemName

  EC2SsmRole:
    Type: 'AWS::IAM::Role'
    Properties:
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM
        - arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: AssumeRole1
            Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: sts:AssumeRole

  EC2InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: "/"
      Roles:
        - !Ref EC2SsmRole

  EC2InstanceX86:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref X86ImageID
      InstanceType: !Ref X86InstanceType
      IamInstanceProfile:
        !Ref EC2InstanceProfile
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            VolumeType: !Ref X86VolumeType
            VolumeSize: !Ref X86VolumeSize
      NetworkInterfaces:
        - DeviceIndex: "0"
          SubnetId: !Ref SubnetPublic
          GroupSet:
            - !Ref SecurityGroupServer
      UserData:
        Fn::Base64: |
          #!/bin/bash -xe
          yum update -y
          amazon-linux-extras install -y docker=latest
          systemctl enable docker.service
          systemctl start docker.service
          usermod -aG docker ec2-user
      Tags:
        - Key: Name
          Value: !Sub "${SystemName}-x86"
        - Key: System
          Value: !Ref SystemName

  EC2InstanceARM64:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref ARM64ImageID
      InstanceType: !Ref ARM64InstanceType
      IamInstanceProfile:
        !Ref EC2InstanceProfile
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            VolumeType: !Ref ARM64VolumeType
            VolumeSize: !Ref ARM64VolumeSize
      NetworkInterfaces:
        - DeviceIndex: "0"
          SubnetId: !Ref SubnetPublic
          GroupSet:
            - !Ref SecurityGroupServer
      UserData:
        Fn::Base64: |
          #!/bin/bash -xe
          yum update -y
          amazon-linux-extras install -y docker=latest
          systemctl enable docker.service
          systemctl start docker.service
          usermod -aG docker ec2-user
      Tags:
        - Key: Name
          Value: !Sub "${SystemName}-arm64"
        - Key: System
          Value: !Ref SystemName

Outputs:
  VPC:
    Value: !Ref VPC
    Export:
      Name: !Sub "${AWS::StackName}::VPC"

  SubnetPublic:
    Value: !Ref SubnetPublic
    Export:
      Name: !Sub "${AWS::StackName}::SubnetPublic"

  SecurityGroupServer:
    Value: !Ref SecurityGroupServer
    Export:
      Name: !Sub "${AWS::StackName}::SecurityGroupServer"

  EC2InstanceX86:
    Value: !Ref EC2InstanceX86
    Export:
      Name: !Sub "${AWS::StackName}::EC2InstanceX86"

  EC2InstanceX86Ip:
    Value: !GetAtt EC2InstanceX86.PublicIp

  EC2InstanceARM64:
    Value: !Ref EC2InstanceARM64
    Export:
      Name: !Sub "${AWS::StackName}::EC2InstanceARM64"

  EC2InstanceARM64Ip:
    Value: !GetAtt EC2InstanceARM64.PublicIp

% rain deploy -y docker-multiarch.cf.yaml docker-multiarch

2. 各 EC2 インスタンスでコンテナを起動

Session Manager で各 EC2 インスタンスに接続して、以下を実行する。

ec2-user になる。

sh-4.2$ sudo su - ec2-user

GitLab にログインしてコンテナを起動。

[ec2-user@ip-xxx ~]$ docker login gitlab.example.com:5002
[ec2-user@ip-xxx ~]$ docker run -p 80:8080 --rm -d gitlab.example.com:5002/oppara/docker-buildx/go-webserver-sample

3. 動作確認

% curl http://x86なEC2インスタンスのIPアドレス/ | npx -y html-cli && echo
<h1>Welcome Golang-WebServer!</h1>
<h2>Hostname: 4acffe99db35</h2>
<h2>OS: linux</h2>
<h2>Architecture: amd64</h2>

% curl http://ARM64なEC2インスタンスのIPアドレス/ | npx -y html-cli && echo
<h1>Welcome Golang-WebServer!</h1>
<h2>Hostname: 4acffe99db35</h2>
<h2>OS: linux</h2>
<h2>Architecture: arm64</h2>

4. 後片付け

% rain rm -y docker-multiarch

環境

  • GitLab CE: 13.12.10
  • Docker Desktop: 4.0.0(67817)
% sw_vers
ProductName: macOS
ProductVersion: 11.3.1
BuildVersion: 20E241

Docker Desktop の「実験的機能」の有効化

以下を追加して、[Apply & Restart] をクリック。

  "experimental": true

Discussion