GitEngHar / CiCdHandsOn

awsのcicdをハンズオンするための環境
0 stars 0 forks source link

ビルドのあれこれ #2

Open GitEngHar opened 1 year ago

GitEngHar commented 1 year ago

いろんなコードで テスト・ビルドしてみよう🎉 DockerImageをビルドしてHubにアップロードできるようになるといいなぁ

GitEngHar commented 1 year ago

GithubActionでGo言語をテストしてみる

コードを作成 https://zenn.dev/engharu/articles/166c611d650cca

コードをCIしてみた https://zenn.dev/engharu/articles/5f450768c9152d

コードを少しいじって環境変数で値を設定し、Dockerコンテナ化して動かしてみた

code

package main

import (
    "fmt"
    "os"
    "strconv"
)

type HumanStatus struct {
    Name          string
    BirthdayMonth int
    BirthdayDay   int
}

func personalColor(personalData HumanStatus) (personalColorData string) {
    nameLength := len(personalData.Name)
    redElement := int((nameLength * 255) / 10)
    greenElement := int((personalData.BirthdayMonth * 255) / 12)
    blueElement := int((personalData.BirthdayDay * 255) / 31)
    personalColorData = fmt.Sprintf("%d,%d,%d", redElement, greenElement, blueElement)
    return personalColorData
}

func main() {
    month, _ := strconv.Atoi(os.Getenv("MONTH"))
    day, _ := strconv.Atoi(os.Getenv("DAY"))
    personalData := HumanStatus{os.Getenv("MyName"), month, day}
    myLiteral := fmt.Sprintf("YourColorCode is %s", personalColor(personalData))
    fmt.Printf(myLiteral)
}

compose

services: 
    app: 
        build: ./
        tty: true
        volumes:
            - ./:/go/app
        environment:
            - MyName=satoshi
            - MONTH=12
            - DAY=10

Dockerfile

FROM golang:1.21.2-alpine3.18
COPY . /go/src/app
WORKDIR /go/src/app/

composeの立ち上げ image

Goの実行をシェル化けして..

実行シェル

[node1] (local) root@192.168.0.28 ~/CiCdHandsOn/Go
$ cat runGoApp.sh
docker compose exec app go run ghTestActionApps.go

テストシェル

[node1] (local) root@192.168.0.28 ~/CiCdHandsOn/Go
$ cat testGoApp.sh
docker compose exec app go test -v

実行結果

[node1] (local) root@192.168.0.28 ~/CiCdHandsOn/Go
$ sh runGoApp.sh
YourColorCode is 178,255,82
[node1] (local) root@192.168.0.28 ~/CiCdHandsOn/Go
$ sh testGoApp.sh
=== RUN   Test_personalColor
=== RUN   Test_personalColor/test1
=== RUN   Test_personalColor/test2
--- PASS: Test_personalColor (0.00s)
    --- PASS: Test_personalColor/test1 (0.00s)
    --- PASS: Test_personalColor/test2 (0.00s)
=== RUN   Test_main
=== RUN   Test_main/test1
YourColorCode is 178,255,82--- PASS: Test_main (0.00s)
    --- PASS: Test_main/test1 (0.00s)
PASS
ok      github.com/GitEngHar/CiCdHandsOn.git    0.003s
GitEngHar commented 1 year ago

compose時の build imageを指定して、アプリを起動しない

Image名の指定

services:
    app:
        image: personalgo
        container_name: personalcolor
        build: ./
        tty: true
        volumes:
            - ./:/go/app
        environment:
            - MyName=satoshi
            - MONTH=12
            - DAY=10
[node1] (local) root@192.168.0.28 ~/CiCdHandsOn/Go
$ docker compose build
[+] Building 0.2s (8/8) FINISHED
 => [app internal] load .dockerignore                                                            0.0s
 => => transferring context: 2B                                                                  0.0s
 => [app internal] load build definition from Dockerfile                                         0.0s
 => => transferring dockerfile: 107B                                                             0.0s
 => [app internal] load metadata for docker.io/library/golang:1.21.2-alpine3.18                  0.1s
 => [app internal] load build context                                                            0.0s
 => => transferring context: 290B                                                                0.0s
 => [app 1/3] FROM docker.io/library/golang:1.21.2-alpine3.18@sha256:a76f153cff6a59112777c071b0  0.0s
 => CACHED [app 2/3] COPY . /go/src/app                                                          0.0s
 => CACHED [app 3/3] WORKDIR /go/src/app/                                                        0.0s
 => [app] exporting to image                                                                     0.0s
 => => exporting layers                                                                          0.0s
 => => writing image sha256:9f8436fc36955578bb3101a9413967073b9c3f146940c675f7db04a2045e0ad9     0.0s
 => => naming to docker.io/library/personalgo                                                    0.0s

確認

$ docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED             SIZE
personalgo   latest    9f8436fc3695   3 minutes ago       222MB
GitEngHar commented 1 year ago

image名を書き換える

[node1] (local) root@192.168.0.28 ~/CiCdHandsOn/Go
$ docker tag personalgo haruapp/gopersonalcolor:latest

確認

[node1] (local) root@192.168.0.28 ~/CiCdHandsOn/Go
$ docker image ls
REPOSITORY                TAG       IMAGE ID       CREATED             SIZE
haruapp/gopersonalcolor   latest    9f8436fc3695   8 minutes ago       222MB
personalgo                latest    9f8436fc3695   8 minutes ago       222MB
go-app                    latest    e944bcc2d160   36 minutes ago      222MB
root-app                  latest    5a6c270e3231   About an hour ago   352MB

push ログ

[node1] (local) root@192.168.0.28 ~/CiCdHandsOn/Go
$ docker push haruapp/gopersonalcolor:latest
The push refers to repository [docker.io/haruapp/gopersonalcolor]
5f70bf18a086: Pushed
8ffadd81c74c: Pushed
cc2ac6e7152c: Mounted from library/golang
a9727a6bf15a: Mounted from library/golang
f6a51e30f545: Mounted from library/golang
cc2447e1835a: Mounted from library/golang
latest: digest: sha256:e1d88183407a448f61f2296b79a7d33bf99cee73add004abaf3129f83cafdd59 size: 1571

pushできた

image

GitEngHar commented 1 year ago

あとはこれをパイプラインでやるのみ🔥

GitEngHar commented 1 year ago

docker hub へ composeを使って image名とコンテナ名を指定してPUSH!

name: Docker

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Check out the repo
        uses: actions/checkout@v4
      - name: Log in to Docker Hub
        uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
        with:
          images: haruapp/gopersonalcolor
      - name: Build 
        env: 

          tags: ${{ steps.meta.outputs.tags }}
        run: docker compose -f ./Go/compose.yaml build && export IMAGENAME=`docker image ls | head -n 2  | tail -n 1 | awk '{print $1}'` && docker tag $IMAGENAME $tags && docker push $tags
      #   tag: ${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}

使わなかったけども、以下コマンドで出力されているjsonからtag情報を取得できる

tag: ${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}

image

GitEngHar commented 1 year ago

実行に依存関係を持たせる

image

name: Docker

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  prebuild:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - name: Set up Go
      uses: actions/setup-go@v4
      with:
        go-version: '1.21.2'
    - name: Test
      run: go test -v ./Go/...    
  build:
    runs-on: ubuntu-latest
    needs: prebuild
    steps:
      - name: Check out the repo
        uses: actions/checkout@v4
      - name: Log in to Docker Hub
        uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
        with:
          images: haruapp/gopersonalcolor
      - name: Build 
        env: 

          tags: ${{ steps.meta.outputs.tags }}
        run: docker compose -f ./Go/compose.yaml build && export IMAGENAME=`docker image ls | head -n 2  | tail -n 1 | awk '{print $1}'` && docker tag $IMAGENAME $tags && docker push $tags
      #   tag: ${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}

goでtest成功後ビルドされるように変更。脆弱性診断も入れたいが、スコープ外にする needs を入れて依存関係を持たせた

GitEngHar commented 1 year ago

GithubからAWSへ

認証回り

参考

ブログ https://dev.classmethod.jp/articles/github-actions-aws-sts-credentials-iamrole/

公式 https://docs.github.com/ja/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services

image

ブログを参照して、譲渡Roleを作成し、以下ブログを参考にしてECRへのPushができた 次はデプロイをしていこう!

https://zenn.dev/aldagram_tech/articles/748bde18ae0b42

デプロイログ https://github.com/GitEngHar/CiCdHandsOn/actions/runs/6549007301/job/17784981738?pr=5

GitEngHar commented 1 year ago

晴れてBG-deploy成功

DeployCode

AWSTemplateFormatVersion: "2010-09-09"
Description: CodeDeploy with ECS Blue/Green deploy
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label: 
          default: Parameters for CodeDeploy
        Parameters:
          - ClusterName
          - ServiceName
          - ALBName
          - ALBTargetGroupBlueName
          - ALBTargetGroupGreenName
Parameters:
  ClusterName:
    Type: String
    Default: FrontCluster
  ServiceName:
    Type: String
    Default: FrontSvc
  ALBName:
    Type: String
    Default: webAppFrontALB
  ALBTargetGroupBlueName:
    Type: String 
    Default: ALBTargetGroupBlue
  ALBTargetGroupGreenName:
    Type: String 
    Default: ALBTargetGroupGreen
Resources:
  # ALB???
  webAppALB:
    Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer'
    Properties:
      Name: !Ref ALBName
      Subnets:
        - !ImportValue PublicSubnet1-ID
        - !ImportValue PublicSubnet2-ID
      SecurityGroups:
        - !ImportValue SecurityGroupFrontALB-ID
      Scheme: internet-facing
      Type: application
      IpAddressType: ipv4
  # TargetGroup(1)???
  ALBTargetGroupBlue:
    Type: 'AWS::ElasticLoadBalancingV2::TargetGroup'
    Properties:
      Name: !Ref ALBTargetGroupBlueName
      Protocol: HTTP
      Port: 80
      TargetType: ip
      VpcId: !ImportValue Vpc-ID
      HealthCheckIntervalSeconds: 5
      HealthCheckPath: /
      HealthCheckPort: '80'
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 2
      HealthyThresholdCount: 2
      Matcher:
        HttpCode: '200'
      UnhealthyThresholdCount: 4
  # ???????
  ALBListenerProdTraffic:
    Type: 'AWS::ElasticLoadBalancingV2::Listener'
    Properties:
      LoadBalancerArn: !Ref webAppALB
      Protocol: HTTP
      Port: 80
      DefaultActions:
        - Type: forward
          ForwardConfig:
            TargetGroups:
              - TargetGroupArn: !Ref ALBTargetGroupBlue
                Weight: 1
  # TargetGroup(2)???
  ALBTargetGroupGreen:
    Type: 'AWS::ElasticLoadBalancingV2::TargetGroup'
    Properties:
      Name: !Ref ALBTargetGroupGreenName
      Protocol: HTTP
      Port: 8080
      TargetType: ip
      VpcId: !ImportValue Vpc-ID
      HealthCheckIntervalSeconds: 5
      HealthCheckPath: /
      HealthCheckPort: '80'
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 2
      HealthyThresholdCount: 2
      Matcher:
        HttpCode: '200'
  # Cluster??
  ECSDemoCluster:
    Type: 'AWS::ECS::Cluster'
    Properties: 
      ClusterName: !Ref ClusterName
  # Service???
  ECSDemoService:
    Type: 'AWS::ECS::Service'
    Properties:
        Cluster: !Ref ECSDemoCluster
        ServiceName: !Ref ServiceName
        TaskDefinition:  !Ref BlueTaskDefinition
        LoadBalancers:
          - ContainerName: simpleWebapp
            ContainerPort: 80
            TargetGroupArn: !Ref ALBTargetGroupBlue
        LaunchType: "FARGATE"
        SchedulingStrategy: "REPLICA"
        DeploymentController:
          Type: CODE_DEPLOY
        NetworkConfiguration:
          AwsvpcConfiguration:
            SecurityGroups: 
              - !ImportValue SecurityGroupFrontService-ID
            Subnets: 
              - !ImportValue PublicSubnet1-ID
              - !ImportValue PublicSubnet2-ID
            AssignPublicIp: ENABLED
        DesiredCount: 1
  # ?????
  BlueTaskDefinition:
    Type: 'AWS::ECS::TaskDefinition'
    DependsOn: ALBListenerProdTraffic
    Properties:
      NetworkMode: awsvpc
      ContainerDefinitions:
        - Image: '429535751272.dkr.ecr.ap-northeast-1.amazonaws.com/viewcertweb:f9b058ceae38161afc33a679aa39e037d0cad343'
          Name: simpleWebapp
          PortMappings:
            - HostPort: 80
              Protocol: tcp
              ContainerPort: 80
          Essential: true
      RequiresCompatibilities:
        - FARGATE
      Cpu: '256'
      Memory: '512'
      Family: frontTask
      ExecutionRoleArn: !ImportValue ECSTaskExecutionRole-Arn 
  EcsCodeDeploy:
    Type: AWS::CodeDeploy::Application
    Properties:
      ApplicationName: !Sub AppECS-${ClusterName}-${ServiceName}
      ComputePlatform: ECS

  EcsDeploymentGroup:
    Type: AWS::CodeDeploy::DeploymentGroup
    DependsOn: ECSDemoService
    Properties:
      ApplicationName: !Ref EcsCodeDeploy
      AutoRollbackConfiguration:
        Enabled: True
        Events:
          - "DEPLOYMENT_FAILURE"
      BlueGreenDeploymentConfiguration:
        DeploymentReadyOption:
          ActionOnTimeout: CONTINUE_DEPLOYMENT
          WaitTimeInMinutes: 0
        TerminateBlueInstancesOnDeploymentSuccess:
          Action: TERMINATE
          TerminationWaitTimeInMinutes: 5
      DeploymentConfigName: "CodeDeployDefault.ECSLinear10PercentEvery1Minutes"
      DeploymentGroupName: !Sub DgpECS-${ClusterName}-${ServiceName}
      DeploymentStyle:
        DeploymentOption: "WITH_TRAFFIC_CONTROL"
        DeploymentType: "BLUE_GREEN"
      ECSServices:
           - ServiceName: !Ref ServiceName
             ClusterName: !Ref ClusterName
      LoadBalancerInfo:
        TargetGroupPairInfoList:
          - TargetGroups:
              - Name:
                  !Ref ALBTargetGroupBlueName
              - Name:
                  !Ref ALBTargetGroupGreenName
            ProdTrafficRoute:
              ListenerArns:
                - !Ref ALBListenerProdTraffic 
      ServiceRoleArn:
        !ImportValue CodeDeployServiceRole-Arn
Outputs:
  appname:
      Description: appname
      Value: !Ref EcsCodeDeploy

FrontCode

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
<body>

    <div class="items">
        <div class="head-base">
            <div class="head">
                <div class="itemtitle">資格情報管理システム</div>
            </div>
            <div class="head">
                <div class="contentbutton">
                    <button id="getrequest" onclick="getCertData();" class="buttonitem">GET</button>
                    <button id="postrequest" onclick="postCertData();" class="buttonitem">POST</button>
                </div>
            </div>

        </div>
        <div class="inputBox">
            <input id="dblink" type="text" placeholder="endpoint : http://xxx" class="inputcontent">
            <input id="skillType" type="text" placeholder="CertTYpe" class="inputcontent">
            <input id="certName" type="text" placeholder="CertName" class="inputcontent">
            <textarea id="view" class="textareacontent" placeholder="Output" readonly></textarea>
        </div>
        <script  language="javascript" type="text/javascript">
            async function  getCertData(){
                var target = document.getElementById("view");
                var dbgetLink = document.getElementById("dblink").value;
                document.getElementById("dblink").value = ""
                var viewText = ""
                if(!inputCheck(dbgetLink,"http")){
                    target.innerHTML = "エンドポイントを正しくを入力してください";
                    return 0
                }
                let requestUrl = new URL("/getcert",dbgetLink)
                var response = await fetch(requestUrl);
                if(!response.ok){
                    viewText = "ResponseError";
                }            
                const certRawJsonData = await response.json();
                const certJsonToStringData = JSON.stringify(certRawJsonData)
                var fixCertJsonTypeData = certJsonToStringData.replace(/'/g,"\"")
                fixCertJsonTypeData = fixCertJsonTypeData.replace("\"{{","[{")
                fixCertJsonTypeData = fixCertJsonTypeData.replace("}}\"","}]")
                const certJsonData = JSON.parse(fixCertJsonTypeData,(key,value) => {
                    switch(key){
                        case "skillType":
                            viewText += `資格スキルの種類 : ${value} <br>`;
                            break;
                        case "certName":
                            viewText += `資格名 : ${value} <br><br>`
                    }
                })
                target.innerHTML = viewText;
                console.log('response.json():', viewText);

            }

            async function postCertData(){
                var target = document.getElementById("view");
                var dbgetLink = document.getElementById("dblink").value;
                var skillTypeText = document.getElementById("skillType").value
                var certName = document.getElementById("certName").value
                document.getElementById("skillType").value = ""
                document.getElementById("certName").value = ""
                document.getElementById("dblink").value = ""
                if(!inputCheck(dbgetLink,"http")){
                    target.innerHTML = "エンドポイントを正しくを入力してください";
                    return 0
                }
                if(!inputCheck(skillTypeText,"none") || !inputCheck(certName,"none")){
                    target.innerHTML = "バックエンドへ送るテキストを入力してください";
                    return 0
                }
                var data = {
                    skilltype: skillTypeText,
                    certname: certName
                }
                let requestUrl = new URL("/regcert",dbgetLink)
                target.innerHTML = "Success"
                const response = await fetch(requestUrl, {
                    method: "POST",
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify(data)
                });
            }
            function inputCheck(checktr,type){
                switch(type){
                    case "http":
                        return checktr.startsWith("http")
                    case "none":
                        var response = true
                        if(checktr.length == 0){
                            response = false
                        }
                        return response
                }
            }
        </script>
        <style type="text/css">
            .items{
                display:flex;
                flex-flow: column;
                padding: 10% 30% 30% 30%;
            }
            .head-base{
                display:flex;
                flex-flow: row;
                width: 100%;
            }
            .head{
                width: 50%;
            }
            .contentbutton{
                margin-left: 50%;
                cursor: auto;
            }
            .buttonitem{
                cursor: pointer;
                padding:0.5em;
                background-color: white;
            }
            .buttonitem:hover{
                background-color: aliceblue;
                /*background-color: greenyellow;*/
            }
            .itemtitle{
                margin-left: 0px;
                font-size: 18px;
                font-weight: bold;
                font-family:'メイリオ', 'Meiryo', sans-serif;
            }
            .inputBox{
                margin-left: 0;
                margin-top: 5%;
            }
            .inputcontent{
                padding: 1em;
                margin:0.5em auto;
                width: 100%;
            }
            .textareacontent{
                width: 100%;
                padding: 1em 1em 5em 1em;
                margin:0.5em auto;
            }
        </style>
    </div>
</body>
</html>

githubActionCode

name: Deploy app to Amazon ECS
on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]
# permission can be added at job level or workflow level
permissions:
  id-token: write
env:
  ECR_REPONAME: viewcertweb
  ECS_CLUSTERNAME: FrontCluster
  ECS_SERVICENAME: FrontSvc
  ECS_CONTAINERNAME: simpleWebapp 
  ROLE_NAME: GithubCICD
  IMAGE_TAG: ${{ github.sha }}
jobs:
  ecr-push:
    name: Deploy
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1-node16
        with:
          role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ env.ROLE_NAME }}
          role-session-name: GitHubActions-${{ github.run_id }}
          aws-region: ap-northeast-1
      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1
      # DockerイメージをビルドしてECRにプッシュ
      - name: Docker image build and push to Amazon ECR
        id: build-image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        #aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin 429535751272.dkr.ecr.ap-northeast-1.amazonaws.com
        run: |
          echo $ECR_REGISTRY/${{ env.ECR_REPONAME }}
          docker login -u AWS -p $(aws ecr get-login-password) https://${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.ap-northeast-1.amazonaws.com
          docker build -t $ECR_REGISTRY/${{ env.ECR_REPONAME }}:${{ env.IMAGE_TAG }} -f ./SimpleWebApp/Front/Dockerfile --build-arg AWS_ACCOUNT_ID=${{ secrets.AWS_ACCOUNT_ID }} --build-arg IMAGE_TAG=${{ env.IMAGE_TAG }} .
          docker push $ECR_REGISTRY/${{ env.ECR_REPONAME }}:${{ env.IMAGE_TAG }}
          echo "image=$ECR_REGISTRY/${{ env.ECR_REPONAME }}:${{ env.IMAGE_TAG }}" >> $GITHUB_OUTPUT
      - name: Download task definition
        run: |
          aws ecs describe-task-definition --task-definition frontTask --query taskDefinition > task-definition.json

      # TaskDefinition の image を push した最新のものに書き換える
      - name: Render TaskDefinition
        id: render-container-api
        uses: aws-actions/amazon-ecs-render-task-definition@v1
        with:
          task-definition: task-definition.json
          container-name: ${{ env.ECS_CONTAINERNAME }}
          image: ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPONAME }}:${{ env.IMAGE_TAG }}
      # デプロイする
      - name: Deploy Amazon ECS task definition
        uses: aws-actions/amazon-ecs-deploy-task-definition@v1
        with:
          task-definition: ${{ steps.render-container-api.outputs.task-definition }}
          service: ${{ env.ECS_SERVICENAME }}
          cluster: ${{ env.ECS_CLUSTERNAME }}
          codedeploy-appspec: appspec.yaml
          wait-for-service-stability: true

おまけでGoのコード