CodeBuild + SkaffoldのCI/CD環境のビルド時間を改善した話

# 課題

　元々、PlayFrameworkアプリケーション（Scala）のビルドを、Docker ImageのビルドからECRへのImage Push、EKSへのデプロイまでをSkaffoldに内包する形で行なっていました。
　これらをGithubへのTagプッシュを契機にCodeBuild上で動かしていたのですが、新たに機械学習アプリケーション（Python）が加わり、ビルド時間が12分から20分まで伸びてしまったことで遅さが目についたので、改善に踏み切りました。


# 環境

 - プラットフォーム： EKS(PlayFramework + Python)
 - ビルド環境： CodeBuild + Skaffold

![CI_CD構成図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316261/9184fd0f-744b-8435-8217-8086c76de31d.png)


# 改善施策

改善施策として以下の二点について対応しました。

- 並列ビルドの導入
- ビルドキャッシュの有効化

## 並列ビルドの導入

　Skaffoldではデフォルトでは並列度1、直列でビルドが行われるのですが、Concurrencyを設定することで並列度を増やすことができます。今回の場合はビルドする対象が1から2に増えたので、2を設定しました。（当然のことながら、それ以上増やしてもビルド時間は向上しませんでした。）

```yaml:skaffold.yml

apiVersion: skaffold/v2alpha3
kind: Config
build:
  artifacts:
    - image: scala/api
      custom:
        buildCommand: ./build.sh api
        dependencies:
          paths:
            - docker/api
    - image: python/ml
      custom:
        buildCommand: ./build.sh ml
        dependencies:
          paths:
            - docker/ml
  local:
    concurrency: 2
  tagPolicy:
    gitCommit: {}
deploy:
  kustomize: {}
  - name: development
    deploy:
      kustomize:
        paths:
          - kubernetes/development
    patches:
      - op: replace
        path: /build/artifacts/0/image
        value: 000000000000.dkr.ecr.us-east-1.amazonaws.com/scala/api
      - op: replace
        path: /build/artifacts/1/image
        value: 000000000000.dkr.ecr.us-east-1.amazonaws.com/python/ml
  - name: development
    deploy:
      kustomize:
        paths:
          - kubernetes/staging
    patches:
      - op: replace
        path: /build/artifacts/0/image
        value: 111111111111.dkr.ecr.us-east-1.amazonaws.com/scala/api
      - op: replace
        path: /build/artifacts/1/image
        value: 111111111111.dkr.ecr.us-east-1.amazonaws.com/python/ml

```
参考：https://skaffold.dev/docs/references/yaml/

これを設定したことで20分→12分、単一のアプリケーションをビルドしていた時点まで改善しました。

## ビルドキャッシュの有効化

　次にCodeBuildのキャッシュの有効化を行いました。元々、Dockerレイヤーキャッシュは有効化していたのですが、今回、新たにカスタムキャッシュを有効化しました。カスタムキャッシュはbuildspec.ymlで指定した特定のディレクトリ以下をキャッシュする機能になります。
　CFnテンプレートは以下のようになります。`- LOCAL_CUSTOM_CACHE`が追加した設定になります。

```yaml:codebuild-sample.yml
  CodeBuildProject:
    Type: 'AWS::CodeBuild::Project'
    Properties:
      Artifacts:
        Type: 'CODEPIPELINE'
      BadgeEnabled: false
      Cache: 
        Modes: 
         - LOCAL_DOCKER_LAYER_CACHE
         - LOCAL_CUSTOM_CACHE
        Type: LOCAL
      Description: !Sub 'created by ${AWS::StackName}'
      Name: !Ref ApplicationName
      ServiceRole: !GetAtt IAMRoleCodeBuild.Arn
      Environment:
        ComputeType: 'BUILD_GENERAL1_SMALL'
        Image: 'aws/codebuild/standard:3.0'
        ImagePullCredentialsType: 'CODEBUILD'
        EnvironmentVariables:
          - Name: 'EKS_CLUSTER_NAME'
            Value: !Ref EKSCluster
            Type: 'PLAINTEXT'
          - Name: 'ENVIRONMENT_NAME'
            Value: !Ref EnvironmentName
            Type: 'PLAINTEXT'
        PrivilegedMode: true
        Type: 'LINUX_CONTAINER'
      Source:
        Type: 'CODEPIPELINE'
      TimeoutInMinutes: 30
```

　buildspec.ymlにはsbtのビルドキャッシュを利用するため、 `/root/.sbt/**/*`、`/root/.ivy2/**/*`を追加し、Skaffoldのキャッシュを利用するため、`/root/.skaffold/**/*`を新規に追加しました。Skaffoldでは、skaffold.ymlに記載した `dependencies` に指定したディレクトリ配下の依存関係を元にキャッシュを生成します。（今回記載したskaffold.ymlはあくまでサンプルであり、実際にはDockerfile以外にも指定する必要があります。）

```yaml:buildspec.yml

version: 0.2

phases:
  install:
    runtime-versions:
      docker: 18
      java: openjdk8
    commands:
      ## Install kubectl
      - wget -O /usr/local/bin/kubectl https://storage.googleapis.com/kubernetes-release/release/v1.14.7/bin/linux/amd64/kubectl
      - chmod +x /usr/local/bin/kubectl
      ## Install kustomize
      - wget -O /usr/local/bin/kustomize https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/v3.2.3/kustomize_kustomize.v3.2.3_linux_amd64
      - chmod +x /usr/local/bin/kustomize
      ## Install skaffold
      - wget -O /usr/local/bin/skaffold https://storage.googleapis.com/skaffold/releases/latest/skaffold-linux-amd64
      - chmod +x /usr/local/bin/skaffold
  pre_build:
    commands:
      - aws eks update-kubeconfig --name ${EKS_CLUSTER_NAME} --region ${AWS_DEFAULT_REGION}
      - aws eks get-token --cluster-name ${EKS_CLUSTER_NAME}
      - $(aws ecr get-login --no-include-email --region ${AWS_DEFAULT_REGION})
  build:
    commands:
      - skaffold run -p ${ENVIRONMENT_NAME}
cache:
 paths:
   - '/root/.sbt/**/*'
   - '/root/.ivy2/**/*'
   - '/root/.skaffold/**/*'
```

この設定を行なった結果、ビルド時間が3分40秒程度に改善し、アプリケーションを変更せずにk8sマニフェストファイルの変更のみ適用する場合は30秒程度まで改善することができました。