はじめに
「俺が使う俺のための環境構築」の第3シリーズです。
シリーズ紹介
概要の作成はChatGPTに書いてもらいました。
第1シリーズ 「 オレオレ開発環境2017〜2019年版 」
第1シリーズ の概要
ターミナル周りの開発環境の推移についてまとめられています。2017年の開発環境では、dotfilesとGitを使用しています。インストール方法や使用しているパッケージについて詳細が記載されています。
また、bacpacというパッケージマネージャやzsh、zplug、tmux、nvimなどのツールやエディタの設定方法も紹介されています。
さらに、Python環境の構築方法やVagrantとVirtual Boxを使用した開発環境についても触れられています。
第2シリーズ 「 オレオレdocker開発環境を作ってみた 」
第2シリーズ の概要
この記事では、俺が使う俺のための環境構築用Dockerイメージを作成する方法について説明されています。以下は記事の要点です。- マイナーだが、様々なパッケージの最新版をインストールしやすいArchlinuxベースのイメージを使用しています。
- 日本語環境が適用されています。
- 自分のdotfiles も適用されています。
- ファイルの所有者の問題を解決するために、UID=1000、GID=1000、USERNAME=u1and0でイメージを再構築しました。
- Neovim、zplug、Python、Rustなどの開発環境が含まれています。
- docker-in-docker環境も作成されています。
- docker-compose.ymlも追記されています。
このDockerイメージの継承関係は以下のようになっています。
- archlinux/archlinux (716MB)
- u1and0/archlinux (777MB)
- u1and0/neovim (1.2GB)
- u1and0/zplug (1.3GB)
- u1and0/vim-go (2.4GB)
- u1and0/Python-conda (4.4GB)
- u1and0/rust (3.3GB)
- u1and0/docker (1.6GB)
- u1and0/zplug (1.3GB)
- u1and0/neovim (1.2GB)
- u1and0/archlinux (777MB)
このDockerイメージ内では、pacmanやyayといったパッケージマネージャを使用してAURパッケージ1をインストールすることも可能です。
やること
第3シリーズとなる今回は Github actionsを使って自動化をしていきたいと思います。
成果物
name: Weekly Docker Build
on:
# 手動ビルド
workflow_dispatch:
# mainにpushされたとき
push:
branches:
- main
# Run every Sunday at 9:00 AM
schedule:
- cron: '0 9 * * 0'
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
context: ["docker", "go", "deno", "python"]
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Get GitHub SHA
id: get_sha
run: echo "SHA=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# Build & Push images
#
# yay - archlinux - neovim - zplug は継承関係があるので
# 直列で実行する。
- name: Build and Push yay Image
id: build_yay
uses: docker/build-push-action@v5
with:
context: ./images/yay
push: true
tags: |
u1and0/yay:latest
u1and0/yay:${{ steps.get_sha.outputs.SHA }}
- name: Image Digest
run: echo ${{ steps.build_yay.outputs.digest }}
- name: Build and Push Archlinux Image
id: build_archlinux
uses: docker/build-push-action@v5
with:
context: ./images/archlinux
push: true
tags: |
u1and0/archlinux:latest
u1and0/archlinux:${{ steps.get_sha.outputs.SHA }}
- name: Image Digest
run: echo ${{ steps.build_archlinux.outputs.digest }}
- name: Build and Push Neovim Image
id: build_neovim
uses: docker/build-push-action@v5
with:
context: ./images/neovim
push: true
tags: |
u1and0/neovim:latest
u1and0/neovim:${{ steps.get_sha.outputs.SHA }}
- name: Image Digest
run: echo ${{ steps.build_neovim.outputs.digest }}
- name: Build and Push Zplug Image
id: build_zplug
uses: docker/build-push-action@v5
with:
context: ./images/zplug
push: true
tags: |
u1and0/zplug:latest
u1and0/zplug:${{ steps.get_sha.outputs.SHA }}
- name: Image Digest
run: echo ${{ steps.build_zplug.outputs.digest }}
# docker, go, deno, python はzplugを継承しているので
# 並列に実行できる。
- name: Build and Push Docker in Docker, Go, Deno, Python Image
id: build_from_zplug
uses: docker/build-push-action@v5
with:
context: ./images/${{ matrix.context }}
push: true
tags: |
u1and0/${{ matrix.context }}:latest
u1and0/${{ matrix.context }}:${{ steps.get_sha.outputs.SHA }}
- name: Image Digest
run: echo ${{ steps.build_from_zplug.outputs.digest }}
# PythonコンテナのときだけNimをビルド
- name: Build and Push Nim Image
if: matrix.context == 'python'
id: build_nim
uses: docker/build-push-action@v5
with:
context: ./images/nim
push: true
tags: |
u1and0/nim:latest
u1and0/nim:${{ steps.get_sha.outputs.SHA }}
- name: Image Digest
if: matrix.context == 'python'
run: echo ${{ steps.build_nim.outputs.digest }}
Actionsの説明
以下は、指定されたYAMLファイルから読み取った内容をわかりやすく記事にまとめたものです。ChatGPTにまとめてもらって、適宜修正しています。
週次のDockerイメージビルドを行うGitHub Actionsの設定
この記事では、週次でDockerイメージをビルド・プッシュするGitHub Actionsのワークフロー設定について説明します。
ビルドトリガ
- 主ブランチ(main)へのpush時
- 週毎のスケジュール(日曜日午前9時)
- ワークフローの手動実行
と3つのトリガーでビルドを行います。
on:
# 手動ビルド
workflow_dispatch:
# mainにpushされたとき
push:
branches:
- main
# Run every Sunday at 9:00 AM
schedule:
- cron: '0 9 * * 0'
ビルドジョブ
Dockerイメージのプッシュ前準備
- リポジトリのチェックアウト
- docker buildxを使って並列ビルドを行うためにQEMUの準備
- docker hubへのログイン
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# 略
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
Github Actionsでのシークレット
Docker hub PATを作成
ここからdocker hubのPAT(Personal Access Token)を作成する。
Github Secretに登録
https://github.com/{user-name}/{repo-name}/settings/secrets/actions
PATをコピーしてGithub secretに登録
ユーザー名も登録
これらの操作を行ってからActionを回すとログインに成功した。
コンテキストのマトリックス設定
ビルド対象のコンテキスト("docker", "go", "deno", "Python")をマトリックスとしてまとめます。
strategy:
matrix:
context: ["docker", "go", "deno", "python"]
Dockerイメージのビルド順序
- yay -> archlinux -> neovim -> zplug
の順にビルドしていきます。子イメージが親イメージを継承しているため順次ビルドが必要です。 - docker, go, deno, Pythonはzplugを継承しているので並列にビルドできます。
# Build & Push images
#
# yay - archlinux - neovim - zplug は継承関係があるので
# 直列で実行する。
- name: Build and Push yay Image
id: build_yay
uses: docker/build-push-action@v5
with:
context: ./images/yay
push: true
tags: |
u1and0/yay:latest
u1and0/yay:${{ steps.get_sha.outputs.SHA }}
# 略
- name: Build and Push Archlinux Image
id: build_archlinux
uses: docker/build-push-action@v5
with:
context: ./images/archlinux
# 略
- name: Build and Push Neovim Image
id: build_neovim
uses: docker/build-push-action@v5
with:
context: ./images/neovim
# 略
- name: Build and Push Zplug Image
id: build_zplug
uses: docker/build-push-action@v5
with:
context: ./images/zplug
# 略
{{ matrix.context }}
には docker, go, deno, Python いずれかの文字列が入ります。
# docker, go, deno, python はzplugを継承しているので
# 並列に実行できる。
- name: Build and Push Docker in Docker, Go, Deno, Python Image
id: build_from_zplug
uses: docker/build-push-action@v5
with:
context: ./images/${{ matrix.context }}
push: true
tags: |
u1and0/${{ matrix.context }}:latest
u1and0/${{ matrix.context }}:${{ steps.get_sha.outputs.SHA }}
- name: Image Digest
run: echo ${{ steps.build_from_zplug.outputs.digest }}
SHAタグ付け
GitのコミットSHAを取り出し、イメージにタグ付けします。
ここでDockerfileを管理しているリポジトリのSHAを取得します。
- name: Get GitHub SHA
id: get_sha
run: echo "SHA=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
次のようにしてSHAをイメージタグにします。
- name: Build and Push yay Image
id: build_yay
uses: docker/build-push-action@v5
with:
context: ./images/yay
push: true
tags: |
u1and0/yay:latest
u1and0/yay:${{ steps.get_sha.outputs.SHA }}
Python時のみNimイメージもビルド
Pythonコンテキストのときのみ、追加でNimイメージもビルドします。
# PythonコンテナのときだけNimをビルド
- name: Build and Push Nim Image
if: matrix.context == 'python'
id: build_nim
uses: docker/build-push-action@v5
以上で、より整然としたDockerイメージのビルドパイプラインが構成できるようになっています。
Actionsの状態確認
ここから確認できます。
pushするたびに、あるいは毎週日曜日、あるいは手動でActionを走らせます。
記事にするまでの経緯
- Dockerの自動ビルドが無料で使えなくなったりした。2
- docker v19.03以降から docker buildx が推奨されるようになった。
無料でAutomate buildが使えなくなった2021年からはPythonコマンドを駆使してワンコマンドで複数イメージをビルドするシステムを構築したりしていました。
複数イメージを構築するPythonスクリプト
#!/usr/bin/env python3
"""Auto build my docker images"""
import sys
from datetime import datetime
import subprocess
BASE_IMAGE = "archlinux/archlinux:base-devel"
USER = "u1and0"
IMAGES = {
# [IMAGE NAME]:DIRECTORY NAME
"yay": "docker_archlinux_yay",
"archlinux": "docker_archlinux_env",
"neovim": "docker_neovim_env",
"zplug": "docker_zplug",
"docker": "docker_docker",
"deno": "docker_deno",
"vim-go": "docker_vim-go",
"python-conda": "docker_python-conda",
# "rust": "docker_rust_env",
"nim": "docker-nim",
}
DAY = datetime.strftime(datetime.today(), '%Y%m%d')
def init():
"""mirrorlist initialize"""
cmd = [
"reflector",
"--verbose",
"--country",
"Japan",
"--age",
"48",
"--protocol",
"https",
"--protocol",
"rsync",
"--sort",
"rate",
"--save",
"../docker_archlinux_env/mirrorlist",
]
try:
run_command(cmd)
except subprocess.CalledProcessError as err:
sys.exit(err)
def main():
"""Build docker images"""
success_count:int = 0
for image, dirname in IMAGES.items():
cmds = [
# PULL
f"sudo docker pull {BASE_IMAGE}",
# BUILD
f"sudo docker build --no-cache -t {USER}/{image} ../{dirname}",
# TAG
f"sudo docker tag {USER}/{image}:latest {USER}/{image}:{DAY}",
# PUSH
f"sudo docker push {USER}/{image}:latest",
f"sudo docker push {USER}/{image}:{DAY}",
]
for i, cmd in enumerate(cmds):
try:
run_command(cmd.split())
if i == 2: # `docker tag` command succeed message
print(f"Successfully tagged {USER}/{image}:{DAY}")
except subprocess.CalledProcessError as err:
sys.exit(err)
success_count += 1
print(f"Succeeded build images ({success_count}/{len(IMAGES)})")
def run_command(cmd: list) -> subprocess.CompletedProcess:
"""Run docker command"""
res = subprocess.run(cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
check=True)
sys.stdout.buffer.write(res.stdout) # Write stdout
res.check_returncode() # Unless code 0 => raise CalledProcessError
if __name__ == "__main__":
init()
main()
このPythonスクリプトもやや凝っていますが、単純にdocker build u1and0/{イメージ名}
とdocker tag u1and0/{イメージ名}
とdocker push u1and0/{イメージ名}
をsubprocess
を使って実行しているだけです。PythonでなくてもShellscriptでもなんでもできますが、書きやすさでPythonを選択しています。
当時Go言語にハマっていたので、init()とmain()という書き方が無駄にGoっぽいです。
2021年頃からあらゆる場所でCI/CDやら Github Actions という仕組みを見聞きして、興味は持ちつつも優先順位が低くてなかなか触れられずにいました。
優先順位が低いのは、とりあえず今のdockerコマンドでもビルドできてましたし、古いイメージのままでも1年くらいは余裕で開発できていますので、「困ることがなかった」というのが正直なところです。
何十年も昔のLinuxイメージでもパッケージマネージャを使って新しくしていけば十分に扱える環境に仕上げられる、というのがLinuxの良いところです。
そして本当に困ったときはイメージをリセットすればまたスタートから始められる、これが仮想環境の良いところです。
当然ながら、新たな技術を取得するには腰を据える必要がありました。
これはCI/CD, Github Actionsに関わらずどんな技術でも共通して言えることです。 自動環境構築するための術を学ぶための時間と労力が必要でした。
これらをあわせると、「現状維持」でも困ることが本当になかったのです。 「新たな技術取得」のプライオリティが十分低くなるほどに、LinuxというシステムとDockerという仮想環境の組み合わせの相性が良すぎるのです。
年末年始にようやく手を付けられる時間が僅かにできたものの、結局ショートカットすることを考えてやったことが「ChatGPTにGithub Actionsのyamlファイルを書いてもらう」ことでした。
これでほぼ十分に動きましたし、足りないところは適宜勉強して1日で自動ビルドシステムは完成しました。
まとめ
開発環境を構築するのは時間と労力が必要です。システムのクラッシュやOSの移行など、開発環境の移行は何度か経験する出来事です。 これを容易にするため、DockerとGithub Actionsを使用したコード化されたインフラ環境が重要です。日常的に開発環境を整理し、いつでも移行できる準備をしておくことが重要です。