sitateru tech blog: Docker

sitateru tech blog

シタテルの技術やエンジニアの取り組みを紹介するテックブログです。

ラベル Docker の投稿を表示しています。 すべての投稿を表示
ラベル Docker の投稿を表示しています。 すべての投稿を表示

2021年12月22日水曜日

GitHub Container Registryに公開リポジトリを作ってみた

12月 22, 2021

みなさんはコンテナイメージはどこに置いているでしょうか。
シタテルでは「基本はECR、GCPで利用するイメージはGCR」というまあ普通な構成になっています。
ですがこの度、「せっかく作ったし公開しておいてもいいかな」というコンテナイメージの置き場としてGHCRを使い始めました。

GHCRとは

コンテナレジストリの利用 - GitHub Docs
GHCRはGitHub Container Registryの頭文字で、GitHub上でいろいろなパッケージを公開できるGitHub Packagesの一機能といったところでしょうか。
DockerfileをGitHubで管理するならGitHub上でイメージ配布まで完結するわけですね。

料金ですが、パブリックなパッケージについては無料となってます。今回のニーズにはちょうどいいですね。
About billing for GitHub Packages - GitHub Docs

イメージのpull/push等は ghcr.io/<OWNER>/<IMAGE_NAME>:<tag> という表記になります。(OWNERはGitHubのユーザー名やorganization名になります)

push前などに認証する場合は以下のようになります。

  1. write:packages スコープを持つアクセストークンを作っておく
  2. $ echo <token> | docker login ghcr.io -u USERNAME --password-stdin

リポジトリ作成・push

というわけで、organizationのPackagesにコンテナイメージをアップロードして公開してみましょう。
上記のコマンドで認証したら、好きなイメージ名とタグ名をつけてpushするだけです。

$ echo $GH_TOKEN | docker login ghcr.io -u sitateru --password-stdin
$ docker pull hello-world:latest
$ docker tag hello-world:latest ghcr.io/sitateru/hello-world:latest
$ docker push ghcr.io/sitateru/hello-world:latest

アップロードしたパッケージは、https://github.com/orgs/&lt;OWNER>/packagesのURLで一覧できます。


organizationにアップロードすると、最初はInternalというアクセス設定になるようです。

公開するためには、設定画面から Change package visiblity をクリックしてPublicを選択。
確認のためパッケージ名を入力してボタンをクリックすれば完了です。



リポジトリと紐づけ

作成したパッケージにはリポジトリを紐づけできるようになっています。
パッケージのページで Connect Repository をクリックしてリポジトリを選択すればOKです。


紐づけするとリポジトリのREADMEがパッケージのREADMEとして表示されるほか、パッケージのアクセス権限を「紐づけたリポジトリと等しくする」ことができます。管理の手間が楽になってありがたいですね。


どんなイメージを公開するか

現在のところ、ローカル開発環境やCIで使うために公式イメージに一手間加えたようなものばかりです。詳しくはリポジトリをご参照ください。

GitHub - sitateru/docker-images

ご利用はご自由にどうぞなのですが、保証やサポート等はできませんのでご了承ください🙇‍♂️
タグ追加などの要望があれば、issueなりPRなりいただければできる限り対応しますのでよろしくお願いします。

2021年1月15日金曜日

GitHub dependabot でDockerタグをバージョン指定する

1月 15, 2021

寒い日が続きますね。

ところで皆さんはGitHubのdependabotは使っていますでしょうか?
リポジトリをスキャンして、使っているライブラリやパッケージの脆弱性をお知らせしてくれるサービスですね。

依存関係を自動的に更新する - GitHub Docs

そのdependabotでちょっとした問題を調べて解決したので記録しておこうと思います。
題して「特定のDockerタグを無視する設定の書き方」です。
dependabotの概要や初期設定などはこの記事ではちょっと飛ばしますのでご了承ください🙇‍


dependabotはDockerタグにも対応していて、脆弱性対応された新しいバージョンタグがある場合はそれを出すようになっています。
例えばリポジトリの docker/Dockerfile をスキャン対象にする場合、コミットしておく設定ファイル .github/dependabot.yml はこのようになります。

version: 2
updates:
  - package-ecosystem: "docker"
    directory: "/docker"

さて、基本的にはdependabotはメジャーバージョンアップも含めて最新バージョンにアップデートさせるように動作するようです。
そのため、Dokerfileで node:14.x.x イメージを使っている場合

このように node:15 系にアップデートしなよ!というPRが作成されるのです。

でもNode.jsはバージョン14はLTSで15はLTSじゃない・・・更新するなら14系の最新にしてほしい!
というわけで、「Dockerタグの特定バージョンを無視する設定」を調べて入れてみました。


依存関係の更新の設定オプション - GitHub Docs

こちらのドキュメントによると、あるバージョンへの更新を無視するためにはdependabot.ymlにそのバージョンを明記すればいいのですが、

範囲を定義する場合は、パッケージマネージャーの標準パターンを使用します

Dockerタグの標準パターンって何だ・・・?🤔

結局わからなかったのでソースを見てみたところ、判定しているのはおそらくこのあたり。

dependabot-core/update_checker.rb at main · dependabot/dependabot-core · GitHub

バージョンの取り扱いに使っているのは Gem::Versionクラス のようです。

class Gem::Version (Ruby 3.0.0 リファレンスマニュアル)

ということはGemfileでバージョンを指定するのと同じ記法で良さそうですね。
dependabot.ymlはこうなりました。

version: 2
updates:
  - package-ecosystem: "docker"
    directory: "/docker"
    ignore:
      - dependency-name: "node"
        versions: ["~>15.0"]
        # nodeイメージの 15.x 系は無視

これでnodeイメージは 15.x を対象外にして更新するようになりました。
新しく作られるPRは14系の範囲でバージョンアップするものになっていますね。


dependabotでDocker更新、なかなか便利なのでお試しください🙏

2020年11月12日木曜日

AWS ECRのライフサイクルポリシーを設定して自動クリーンアップ

11月 12, 2020

今回は軽く、AWS ECR (Elastic Container Registry) の小ネタです。

AWSでコンテナを使って何かする場合ECRにコンテナイメージを保存しておくことが多いと思いますが、日ごろからイメージをよくビルド&プッシュしているとどんどんイメージが増えていきますね。


イメージサイズがものすごく大きいとかでなければそれほど料金を食うわけでもないのですが、まず使わない古いイメージをずっと保存しておくこともないよなーと思ったのでそのあたりを設定してみました。

ECRではライフサイクルポリシーという機能があり、いろいろな条件を定めてイメージを自動クリーンアップすることができるんですね。
Amazon ECRのライフサイクルポリシーでコンテナイメージのクリーンアップ | Amazon Web Services ブログ

設定できる条件は

  • タグ
  • イメージ数
  • プッシュされてからの経過日数
を組み合わせることができます。

今回は、「タグが latest ではない」かつ「プッシュされて90日以上」の条件に合うイメージを削除するポリシーを設定してみました。

AWSコンソールで設定する場合は、ECRのコンソールでリポジトリを選択して左のメニューの Lifecycle Policy をクリックすると設定画面があります。


「テストルールの編集」ボタンを押すとポリシーを適用した結果が確認できる(実際に保存されているイメージには何もしない)テスト用ページに移動するので、まずはそこで試してみるのがいいですね。

さて、ライフサイクルポリシーは「ルール」を優先順位つきで好きな数設定することで構成します。ルール作成画面はこうなっていて、一致条件は「イメージをプッシュしてから」「次の数値を超えるイメージ数」が選べます。


今回作りたい条件は「タグが latest ではない」かつ「プッシュされて90日以上」ということで、

  • タグ付け済("latest")、次の数値を超えるイメージ数(1)
  • すべて、イメージをプッシュしてから(90日)

という2つのルールを作ってこのようになります。


ルールを作ればあとは自動でそれに従ってイメージが毎日掃除されるので、とても楽ですね😊


ここまではコンソールで操作してきましたが、リポジトリが多い場合やコンソール面倒だと言う場合はもちろんCLIが使えます。

put-lifecycle-policy — AWS CLI 2.1.0 Command Reference

まずは先ほどのルールをJSON化して policy.json とでもファイルを作ります。

{
    "rules": [
        {
            "rulePriority": 1,
            "description": "keep latest image",
            "selection": {
                "tagStatus": "tagged",
                "tagPrefixList": ["latest"],
                "countType": "imageCountMoreThan",
                "countNumber": 1
            },
            "action": {
                "type": "expire"
            }
        },
        {
            "rulePriority": 2,
            "description": "expire images older than 90 days",
            "selection": {
                "tagStatus": "any",
                "countType": "sinceImagePushed",
                "countUnit": "days",
                "countNumber": 90
            },
            "action": {
                "type": "expire"
            }
        }
    ]
}

今回は全リポジトリに適用したかったのでシェルスクリプトを作りました。

#!/bin/sh
# ECRリポジトリにライフサイクルポリシーを設定する

for REPO in $(aws ecr describe-repositories --query "repositories[*].{name:repositoryName}" --output text)
do
  echo "repository: $REPO"
  aws ecr put-lifecycle-policy --lifecycle-policy-text "file://policy.json" --repository-name $REPO
done

これで一括適用ができますね💪

比較的簡単に設定ができるので、「うわっ…ECRのイメージ、多すぎ…?」と気づいた際には設定してみてはいかがでしょうか。

ちなみにterraformだとaws_ecr_lifecycle_policyリソースで設定できるようです。

aws_ecr_lifecycle_policy | Resources | hashicorp/aws | Terraform Registry

2018年12月7日金曜日

CircleCIでAWS ECSに自動デプロイする(おまけで環境面の切り替えも)

12月 07, 2018

こんにちは、シタテルの茨木です。

今回はCircleCIでDockerをビルドし、そのままAWS ECR/ECSにデプロイする為の設定例を紹介します。

Nuxt.jsアプリ用に作ったものですが、言語やAPサーバが違っても共通の部分がほとんどです。

PRマージからの自動デプロイ、いいですよね。

https://circleci.com/docs/2.0/ecs-ecr/
https://github.com/CircleCI-Public/circleci-demo-aws-ecs-ecr

基本的にはCircleCI公式ガイドの上記リンクを参考にしていますが、元ネタはterraform前提だったりでなかなか大仰です。(下記に説明漏れがあったら原典を見てください…)

今回紹介するのはリンク先のスクリプト類を簡素化し、少し機能を追加(リポジトリ名からデプロイ先の環境面を切り替えできるように)したものです。

以下で省略しているけれど必要なモノ

  • ECS/ECRの設定

これだけで別の記事が書けてしまうので割愛します

ecsTaskExecutionRoleの割り当てとか忘れがちですかね

  • githubリポジトリ

言わずもがな。CircleCIとの接続設定はされているものとします

  • Dockerfile

リポジトリのルートに。Dockerfileの書き方は解説しません

  • requirements.txt

リポジトリのルートに。awscliを使えるようにします

https://github.com/CircleCI-Public/circleci-demo-aws-ecs-ecr/blob/master/requirements.txt

  • CircleCIの環境変数設定

github上にaccess_keyを晒さなくていいように、入れておきます

https://circleci.com/docs/2.0/env-vars/#setting-an-environment-variable-in-a-project

この手順で設定した環境変数はCircleCIのジョブから参照できます

.circleci/config.yml:build

version: 2
jobs:
~省略~
  build:
    docker:
      - image: circleci/node:10-stretch
    steps:
      - checkout
      - setup_remote_docker
      - run:
          name: Setup common environment variables
          command: |
            echo 'export ECR_REPOSITORY_NAME="YOUR-ECR-REPO-NAME"' >> $BASH_ENV
            echo 'export FULL_IMAGE_NAME="${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/${ECR=REPOSITORY_NAME}:${CIRCLE_SHA1}"' >> $BASH_ENV
      - run:
          name: Build image
          command: |
            docker build -t $FULL_IMAGE_NAME .
      - run:
          name: Save image to an archive
          command: |
            mkdir docker-image
            docker save -o docker-image/image.tar $FULL_IMAGE_NAME
      - persist_to_workspace:
          root: .
          paths:
            - docker-image
~省略~

さっそくですがビルドジョブです。

  • YOUR-HOGEHOGE箇所はご自身のECS/ECRの設定に合わせて置き換えてください
  • - image: circleci/node:10-stretchnode:10を使っていますが、これはビルドする対象がNuxt.jsアプリだからですね。Dockerビルド時に必要なモノに合わせて修正してください

.circleci/config.yml:deploy

~省略~
  deploy:  
    docker:
      - image: circleci/python:3.6.1
    environment:
      AWS_DEFAULT_OUTPUT: json
    steps:
      - checkout
      - setup_remote_docker
      - attach_workspace:
          at: workspace
      - restore_cache:
          key: v1-{{ checksum "requirements.txt" }}
      - run:
          name: Install awscli
          command: |
            python3 -m venv venv
            . venv/bin/activate
            pip install -r requirements.txt
      - save_cache:
          key: v1-{{ checksum "requirements.txt" }}
          paths:
            - "venv"
      - run:
          name: Load image
          command: |
            docker load --input workspace/docker-image/image.tar
      - run:
          name: Setup target environment variables
          command: |
            echo 'export TARGET=`echo $CIRCLE_BRANCH | sed -e s/develop.*/dev/ -e s/release.*/stg/ -e s/master/prd/`' >> $BASH_ENV
            source $BASH_ENV
      - run:
          name: Setup common environment variables
          command: |
            echo 'export ECR_REPOSITORY_NAME="YOUR-ECR-REPO-NAME"' >> $BASH_ENV
            echo 'export ECS_CLUSTER_NAME="YOUR-ECS-CULSTER-NAME-PREFIX${TARGET}"' >> $BASH_ENV
            echo 'export ECS_SERVICE_NAME="YOUR-ECS-SERVICE-NAME-PREFIX${TARGET}"' >> $BASH_ENV
      - run:
          name: Push image
          command: |
            . venv/bin/activate
            eval $(aws ecr get-login --region ap-northeast-1 --no-include-email)
            docker push $AWS_ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/$ECR_REPOSITORY_NAME:$CIRCLE_SHA1
      - run:
          name: Deploy
          command: |
            . venv/bin/activate
            export AWS_DEFAULT_REGION="ap-northeast-1"
            export ECS_TASK_FAMILY_NAME="YOUR-ECS-TASK-NAME-PREFIX${TARGET}"
            export ECS_CONTAINER_DEFINITION_NAME="YOUR-ECS-CONTAINER-DEF-NAME-PREFIX${TARGET}"
            export EXECUTION_ROLE_ARN="arn:aws:iam::$AWS_ACCOUNT_ID:role/ecsTaskExecutionRole"
            bash ./deploy.sh
~省略~

デプロイジョブです。

  • ブランチ名から環境名へ

echo 'export TARGET=`echo $CIRCLE_BRANCH | sed -e s/develop.*/dev/ -e s/release.*/stg/ -e s/master/prd/`' >> $BASH_ENV

ブランチ名に応じた値(developブランチなら"dev")を環境変数に入れています。これをsuffixとして使い、デプロイ先のクラスタ名等に反映しています

  • YOUR-HOGEHOGE箇所はご自身のECS/ECRの設定に合わせて置き換えてください

余談ですが、ECSはクラスタ・サービス・タスク・タスク定義といろいろ設定しないといけないのですが、なかなか直感的にわかりにくくてイマイチな印象です

.circleci/config.yml:workflows

~省略~
workflows:
  version: 2
  build-deploy:
    jobs:
      - build:
          filters:
            branches:
              only:
                - develop
                - /release\/.*/
                - master
      - deploy:
          requires:
            - build
          filters:
            branches:
              only:
                - develop
                - /release\/.*/
                - master

この辺はよしなに

deploy.sh

#!/usr/bin/env bash
set -eo pipefail
# more bash-friendly output for jq
JQ="jq --raw-output --exit-status"

deploy_cluster() {
  make_task_def   
  register_definition

  if [[ $(aws ecs update-service --cluster $ECS_CLUSTER_NAME --service $ECS_SERVICE_NAME --task-definition $revision | \
      $JQ '.service.taskDefinition') != $revision ]]; then
    echo "Error updating service."
    return 1
  fi

  # wait for older revisions to disappear
  # not really necessary, but nice for demos
  for attempt in {1..30}; do
    if stale=$(aws ecs describe-services --cluster $ECS_CLUSTER_NAME --services $ECS_SERVICE_NAME | \
        $JQ ".services[0].deployments | .[] | select(.taskDefinition != \"$revision\") | .taskDefinition"); then
      echo "Waiting for stale deployment(s):"
      echo "$stale"
      sleep 30
    else
      echo "Deployed!"
      return 0
    fi
  done
  echo "Service update took too long - please check the status of the deployment on the AWS ECS console"
  return 1
}

make_task_def() {
  task_template='[
    {
      "name": "%s",
      "image": "%s.dkr.ecr.%s.amazonaws.com/%s:%s",
      "essential": true,
      "portMappings": [
        {
          "containerPort": 80
        }
      ],
      "environment": [
        {
          "name": "TARGET",
          "value": "%s"
        }
      ]
    }
  ]'

  task_def=$(printf "$task_template" $ECS_CONTAINER_DEFINITION_NAME $AWS_ACCOUNT_ID $AWS_DEFAULT_REGION $ECR_REPOSITORY_NAME $CIRCLE_SHA1 $TARGET)
}

register_definition() {
  if revision=$(aws ecs register-task-definition --requires-compatibilities FARGATE --cpu 256 --memory 1024 --network-mode awsvpc --execution-role-arn $EXECUTION_ROLE_ARN --container-definitions "$task_def" --family $ECS_TASK_FAMILY_NAME | $JQ '.taskDefinition.taskDefinitionArn'); then
    echo "New deployment: $revision"
  else
    echo "Failed to register task definition"
    return 1
  fi
}

deploy_cluster

デプロイジョブから呼び出すスクリプトです。

task_templateの内容を色々弄るとカスタマイズできます

設定可能な項目はこちら

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/create-task-definition.html

ここでは、environmentで環境変数TARGETを設定しています

ECSがdocker runする際に引数として付与され、docker内のアプリから環境変数として参照可能になります

実際には、nuxt.config.js内でTARGETを参照し、APIサーバの向け先を切り替えたり等しています

以上、ご参考になれば幸いです。