能書き
おうちサーバー構築報告:予告からのおうちサーバー構築です。
先日は GitLab Runner をインストールしてGitLabサーバーと連係した訳ですが。今回はこれを使用してCI/CDを試してみました。折角なので、セキュリティにも気を配った手順を追及してみました。
前提
- GitLab Runner をインストールしてGitLabサーバーと連係している
- ProxmoxVEをインストールしていて、システムコンテナ(または仮想マシン)を容易に作成できる
- ローカル環境は用意されているものとする(Windows11上にWSL2でUbuntu環境を用意してgit操作)
- 実験プロジェクトはPythonで用意(私がPythonの勉強中なので)
目標
- GitLab Flowを意識する
- ステージング環境と本番環境を用意する
- 開発環境→ステージング環境→本番環境の GitLab CI/CD を試行する
参考文献
GitLab Flow とはこんな概念のようです。
CI/CDについては全く知識の無い私ですが、主に下記の記事で学習させていただきました。
用意する環境については私自身が大昔こんな記事を書いたりしました。しかし今回は GitLab Flow を意識した事もあり、世間で一般的と思われるステージング環境と本番環境だけ用意する事にします。
Ansibleのplaybookについては下記記事を参考にしました。
- [Ansible] user モジュールの基本的な使い方(ユーザーの作成・削除など) - てくなべ
- ansible.posix.authorized_key module – Adds or removes an SSH authorized key - Ansible community forum Documentation
今回の実験ではUbuntu上でPythonを使ってみます。
環境ごとに異なる設定値を管理する事に関する記事です。要するに.envファイルを使うという事です。
- Pythonでdotenvを使う理由とやり方まとめ(.env / load_dotenv / os.getenv) - Qiita
- Pythonの設定管理を安全にする方法|本番・開発・テスト環境を切り替える設計パターン - Pythonラボ
- Web フレームワーク (Django, Flask) との連携 - ユースケースとベストプラクティス - Python-dotenv 完全ガイド: .env ファイルで環境変数をスマートに管理! - omomuki
- 【脱・.env管理】dotenvxで環境変数を安全かつスマートに管理しよう! - Qiita
.gitlab-ci.ymlを作成するパイプラインエディタと言う機能がGitLabには備わっていました。
各環境へデプロイするにはssh経由でrsyncする訳ですが、そこで必要になるアレコレ。
- ssh-keyscan コマンドがあるらしい - Qiita
- rsync(over SSH)をセキュアに設定する - Qiita
- rsyncオプション - Qiita
- rsync専用鍵ペアを作成してrsync+sshでデータバックアップをする - VPS Life
- ssh アクセス元の IP を指定してログインを制限する - ふらっと考える
ssh鍵のGitLabへの登録については一捻り必要なようです。
- GitLab CI/CDでSSHキーを使用する - ジョブ - CI/CDを使用してアプリケーションをビルドする - GitLabを使用する - GitLab Docs
- Gitlab CI/CD の環境変数で秘密鍵をマスクしたい - Zenn
rootユーザー禁止に関するアレコレ。
- Linuxで直接rootでのログインを禁止にして、SSHログインをセキュアにする - Qiita
- SSHD セキュリティ設定ロールの作成 - Ansible でSSHのセキュリティ設定 - あぱーブログ
- Ubuntu 24.04でユーザーをsudoersに追加する方法 - Linux-JP.org
- Ansibleでsudoerの登録記法について - teratailレバテック
小ネタとして、Pythonに関するアレコレ。
- Python仮想環境(venv)の使い方を完全理解する - Zenn
- Pythonテストフレームワーク「pytest」でテストコードを書く【完全ガイド】 - Zenn
- __pycache__をgit ignoreする方法~任意のサブディレクトリからの除外 - Wunderhorn
- 【Python】if __name__ == "__main__": って結局なに? - Qiita
そもそもの前提として、我が家ではProxmoxVE上で下記のように環境(システムコンテナと仮想マシン)を用意しています。
環境構築
自動デプロイと運用保守の為、2つの口が環境ごとに必要です。そして運用保守は踏み台経由とする方針にします。
踏み台の公開鍵を確認
ProxmoxVE上で最初にサーバー体系を構築した際のAnsibleコントロールノード(IPアドレス: 172.16.1.100)を踏み台とします。まずはそれのssh公開鍵を確認します。公開鍵を表示してメモしておきます。
cd
cat .ssh/id_ed25519.pub
ステージング環境と本番環境
ProxmoxVEでコンテナを2つ作成して、ステージング環境と本番環境にします。今回は実験用と言う事で、最低限の設定で揃えました。
本番環境:
- ホスト名: ExampleProject-PRD
- ssh公開鍵: 上記でメモした踏み台の公開鍵
- テンプレート: ubuntu-24.04-standard_24.04-2_amd64.tar.zst
- ディスクサイズ: 8GiB
- CPUコア: 1
- メモリ: 512MiB
- スワップ: 0MiB …ホストマシンのストレージデバイスがSSDなので、その為のチューニングを考慮
- IPアドレス/CIDR: 172.16.1.106/16 …我が家のネットワークIPアドレス体系に合わせた設定
- ゲートウェイ: 172.16.2.1 …我が家のネットワークIPアドレス体系に合わせた設定
- DNS: ドメインもサーバも空欄(ホスト設定を使用)
ステージング環境:
- ホスト名: ExampleProject-STG
- IPアドレス/CIDR: 172.16.1.107/16
- それ以外の設定は上記の本番環境と同じ
コンテナを作成したら両方とも起動して、踏み台コンテナ(172.16.1.100)からrootユーザーでパスワード無しのログインが出来る事を確認しておきます。
ssh root@172.16.1.106
ssh root@172.16.1.107
保守用ssh接続の準備
保守用接続に使用するssh鍵は踏み台の鍵を使用します。先程メモした踏み台の公開鍵をまた利用します。
デプロイ用ssh接続の準備
デプロイ時に使用するssh鍵を作成します。
cd
mkdir cicd
cd cicd
ssh-keygen -t ed25519 -P "" -f deploy-key -C runner@GitLabRunner
この公開鍵deploy-key.pubと、秘密鍵deploy-keyの内容もメモしておきます。
秘密鍵は後程GitLabに登録するのですが、その都合で、空白文字や改行コードが無い状態にする必要があります。そのためbase64 -w0コマンドで変換します。
cat deploy-key.pub
base64 -w0 deploy-key
Ansible
まずはAnsibleのインベントリを設定します。
cd
cd cicd
cat <<___ >inventory.ini
[servers]
172.16.1.106
172.16.1.107
[servers:vars]
ansible_user=root
___
インベントリの確認の為にpingします。
ansible all -m ping -i inventory.ini
成功したらplaybook.yamlを作ります。内容は下記の通り。
- 毎度お馴染みの
apt update&&apt upgrade - デプロイ兼実行ユーザーrunnerの作成(sshログインは
rsyncコマンド実行専用とする) - 保守ユーザーmaintainerの作成(sudoersに追加)
- プロジェクトを置くディレクトリを
/srv/exampleprojectとし、そのパーミッションを整える - Pythonのインストールと関連設定
- rootユーザーのログイン禁止とsshdの再起動
MAINTAINER_PUB_KEY="≪メモしておいた、保守用ssh接続の為の公開鍵≫"
RUNNER_PUB_KEY="≪メモしておいた、デプロイ用ssh接続の為の公開鍵≫"
RUNNER_PUB_KEY_="from=\"172.16.*.*\",command=\"rsync --server -vvlogDtprze.iLsfxCIvu --delete . /srv/exampleproject/\" $RUNNER_PUB_KEY"
上記RUNNER_PUB_KEY_で指定したfrom="172.16.*.*"について、172.16.*.*は我が家のLANのIPアドレスです。外部からは接続できなくしています。同時にrsyncコマンドを指定して、他のコマンドを実行できないようにします。
cat <<___ >playbook.yaml
- name: Setup CI/CD environment
hosts: servers
tasks:
- name: apt update
apt:
update_cache: yes
become: yes
- name: apt upgrade
apt:
upgrade: yes
become: yes
- name: add the user 'runner'
user:
name: runner
- name: set up the public key of the user 'runner'
ansible.posix.authorized_key:
user: runner
key: '$RUNNER_PUB_KEY_'
- name: add the user 'maintainer'
user:
name: maintainer
password: "{{ 'initial password' | password_hash('sha512') }}"
shell: /bin/bash
- name: set up the public key of the user 'maintainer'
ansible.posix.authorized_key:
user: maintainer
key: "$MAINTAINER_PUB_KEY"
- name: add the user 'maintainer' to sudoers
community.general.sudoers:
name: maintainer
user: maintainer
runas: ALL
commands: ALL
nopassword: false
- name: create the directory '/srv/exampleproject'
file:
path: /srv/exampleproject
state: directory
owner: runner
group: runner
mode: "775"
become: yes
- name: install python3
apt:
name: [python3, python3-pip, python3-dotenv, python-is-python3]
become: yes
- name: disable password authentication
lineinfile:
dest: /etc/ssh/sshd_config
regexp: "^PasswordAuthentication"
insertafter: "^#PasswordAuthentication"
line: "PasswordAuthentication no"
- name: disable challenge response authentication
lineinfile:
dest: /etc/ssh/sshd_config
regexp: "^ChallengeResponseAuthentication"
insertafter: "^#ChallengeResponseAuthentication"
line: "ChallengeResponseAuthentication no"
- name: prohibit to login the user 'root'
lineinfile:
dest: /etc/ssh/sshd_config
regexp: "^PermitRootLogin"
insertafter: "^#PermitRootLogin"
line: "PermitRootLogin no"
- name: restart sshd
service:
name: sshd
state: restarted
___
文法チェック。
ansible-playbook -i inventory.ini playbook.yaml --syntax-check
問題無ければ実行しましょう。
ansible-playbook -i inventory.ini playbook.yaml
環境毎の手動設定
下記項目を手動設定します。
- maintainerユーザーのパスワード
- 上記のAnsibleの設定だと
initial passwordになっている - 環境毎に異なる強固なパスワードを
passwdコマンドで手動設定(パスワードはsudo時に必要)
- 上記のAnsibleの設定だと
-
.envを作成- 今回はダミーデータを格納しておく
- ステージング環境には、Python仮想環境を作成して
pytest(及び必要なPythonモジュール)をインストール
まずは本番環境から。まぁどちらが先でもいいんですが。
ssh maintainer@172.16.1.106
passwd
cd /srv/exampleproject
cat <<___ | sudo tee -a .env >/dev/null
EXAMPLE_ENV=production
___
sudo chown runner:runner .env
sudo chmod 444 .env
exit
次にステージング環境。
ssh maintainer@172.16.1.107
passwd
sudo apt install -y python3.12-venv
sudo su runner
cd /srv/exampleproject
cat <<___ >.env
EXAMPLE_ENV=staging
___
chmod 444 .env
cd
python3 -m venv venv
. venv/bin/activate
pip install pytest pytest-cov dotenv
exit
exit
以上で環境の準備が整いました。
プロジェクト準備
ここからはGitLabの操作になります。一般的なプロジェクト設定なので、画面キャプチャは省略します。
GitLabアカウントの用意
プロジェクトのオーナー役とデベロッパー役の2人分のアカウントを用意します。既にアカウントを作ってあればそれを流用しても良いですが、私はこれから用意します。
アカウントは2つとも普通に作ります。特別な設定はありません。
- taro : オーナー役として運用
- hanako : デベロッパー役として運用
プロジェクトの作成
実験なので設定は何でもいいのですが、今回は下記のようにしてみます。
- GitLabにtaroアカウントでログイン
- 空のプロジェクトを作成
- プロジェクト名:
ExampleProject - 表示レベル: 非公開
- リポジトリを初期化しREADMEファイルを生成する
ExampleProjectを作成したらhanakoを招待します。
- ロールを選択: デベロッパー
デプロイ用ssh接続の準備
先にメモしておいたデプロイ用ssh秘密鍵(公開鍵ではなく)のbase64 -w0の結果を、ExampleProjectのCI/CD変数として設定します。その手順は公式ドキュメントを参照して下さい。
- タイプ: 変数
- 環境: すべて
- 可視化: マスクして非表示
- フラグ: 「変数の保護」のチェックを外す
- 説明: デプロイ用ssh秘密鍵
- キー: SSH_PRIVATE_KEY
- 値: メモしておいたデプロイ用ssh秘密鍵の
base64 -w0の結果を貼り付け
.gitlab-ci.ymlと.gitignore
GitLabの 左メニュー>ビルド>パイプラインエディタ で.gitlab-ci.ymlを作成・編集できるようです。下記内容でmainブランチに作成します。
要点:
- ステージング環境への自動デプロイにおいては下記ファイルをデプロイから除外(
rsyncの設定).env.git.gitlab-ci.ymlREADME.md.venv- (
test*.pyは除外せずにデプロイする)
- 本番環境への自動デプロイにおいては下記ファイルをデプロイから除外(
rsyncの設定).env.git.gitlab-ci.ymlREADME.mdtest*.py
具体的には下記の通りです。
こういう場合、imageはalpine:latestを使うものだと思いますが。rsyncコマンドのバージョンが合わなかった(2026年5月9日現在でaplineのrsyncはubuntuの物より新し過ぎて、しかも古いバージョンを指定してのインストールが出来なかった)ので、ubuntu:24.04を指定しました。
stages:
- test
- deploy
run_unit_tests:
stage: test
image: python:3.14.4-slim
before_script:
- pip install pytest pytest-cov python-dotenv
script:
- pytest
deploy-staging:
stage: deploy
image: ubuntu:24.04
before_script:
- apt update
- apt install -y openssh-client rsync
- mkdir -p ~/.ssh
- echo "$SSH_PRIVATE_KEY" | base64 -d >~/.ssh/id_key
- chmod 700 ~/.ssh
- chmod 600 ~/.ssh/id_key
- ssh-keyscan -H 172.16.1.107 >>~/.ssh/known_hosts
script:
- rsync -azvv --delete --exclude=".env" --exclude=".git/" --exclude=".gitignore" --exclude=".gitlab-ci.yml" --exclude="README.md" -e "ssh -i ~/.ssh/id_key" ./ runner@172.16.1.107:/srv/exampleproject/
rules:
- if: '$CI_COMMIT_BRANCH == "pre-production"'
deploy-producton:
stage: deploy
image: ubuntu:24.04
before_script:
- apt update
- apt install -y openssh-client rsync
- mkdir -p ~/.ssh
- echo "$SSH_PRIVATE_KEY" | base64 -d >~/.ssh/id_key
- chmod 700 ~/.ssh
- chmod 600 ~/.ssh/id_key
- ssh-keyscan -H 172.16.1.106 >>~/.ssh/known_hosts
script:
- rsync -azvv --delete --exclude=".env" --exclude=".git/" --exclude=".gitignore" --exclude=".gitlab-ci.yml" --exclude="README.md" --exclude="test*.py" -e "ssh -i ~/.ssh/id_key" ./ runner@172.16.1.106:/srv/exampleproject/
rules:
- if: '$CI_COMMIT_BRANCH == "production"'
それから.gitignoreを作ります。GitLabの左メニュー>コード>リポジトリで表示されるリポジトリ画面において、画面右上の +ボタンクリック>ポップアップメニュー>新しいファイル の選択でオンラインエディタが開きます。これで.gitignoreファイルを作成します。
まずはテンプレートを選択してプロジェクトに応じたファイル群を記載します。今回はPythonを使用するので、テンプレートとして「Python」を選択してみます。大量のファイルとディレクトリが表示されます。良く見ると.envや.venvなどもテンプレート内に含まれてますね。
__pycache__/
.venv
.env
などなど、テンプレート内容をそのままコミット
そうしたらエディタ右上の「変更をコミットする」ボタンをクリックして、現在のmainブランチにコミットします。mainブランチは保護されてますが、プロジェクトのオーナーならコミットできます。
ブランチの作成と保護
最初は下記ブランチのみ存在し、デフォルトで保護されています。そしてこれがデフォルトブランチです。
main
mainブランチから下記ブランチを作成して保護し、デベロッパーが直接コミットできないようにします。
pre-productionproduction
ここまで出来たらtaroはGitLabからサインアウトします。
開発環境の構築
私の場合、普段使いのノートパソコンがWindows11です。ここにWSL2(Ubuntu)で環境を作っています。
今回はPythonですので、一応、仮想環境を用意します。
まずはvenvをインストール。
sudo apt install python3.12-venv
それからPythonの仮想環境を作成。
cd
mkdir ExampleProject
cd ExampleProject
python3 -m venv .venv
source .venv/bin/activate
基本的なライブラリやフレームワークを、Python仮想環境内にインストールします。
pip install pytest pytest-cov python-dotenv
ここからのGitLab操作はhanakoアカウントで実施する事にします。
sshでgit cloneできるように、鍵を作ってhanakoに登録します。まずは鍵の作成。下記コマンドを実行します。
ssh-keygen -t ed25519 -P "" -C "hanako@local"
作成した鍵ペアのうち公開鍵の方を表示します。
cat ~/.ssh/id_ed25519.pub
これをGitLabのhanakoアカウントのSSHキーとして登録します。GitLab画面右上のユーザーアイコンをクリックしてポップアップメニュー>設定を選択、表示された画面左メニュー>SSHキー、そして新しいキーを追加します。
この操作が完了したらExampleProjectをsshでクローンできるはずです。
git clone ≪プロジェクトで指定されるURL≫
そうしたら最初に.envを作ります。
cd exampleproject
cat <<___ >.env
EXAMPLE_ENV=develop
___
chmod 444 .env
これで開発環境を構築できました。
プロジェクトを回す
イシューを作成
GitLabは引き続きhanakoで操作します。
まずはGitLabでイシューを作成します。日本語だとチケットでしょうか。内容は適当に設定します。
- タイプ: イシュー
- タイトル: 機能追加:加算
- 説明: 最初の機能を実装。内容は加算。
プロジェクトで最初のイシューなのでイシュー番号は1になります。
イシューに基づくブランチを作成
作成したイシューのページに移動します。そして「マージリクエストを作成」をクリック。
- ターゲットブランチ:
main - ソースブランチ名:
feature/1-add(何でもいいのですが、慣例に従いこんなような名前で) - ドラフトとしてマーク
- 担当者: 自分をアサインする
- マージリクエストが承認されたときにソースブランチを削除します
git checkout
コードをpullして、上記で作成したブランチをチェックアウトします。
git pull
git checkout feature/1-add
プログラムとテストコードを作成
実験なので簡単なプログラムにします。何でもいいのですが、例えば下記を。
import os
from dotenv import load_dotenv
def add(a, b):
return a + b
if __name__ == "__main__":
load_dotenv()
print(os.getenv("EXAMPLE_ENV") + ":" + str(add(1, 2)))
対応するテストコードはこちらになります。
from add import add
import pytest
def test_add():
assert add(1, 2) == 3
開発環境で実行してみます。
python3 add.py
テストも実行してみます。
pytest
テストの方は、成功したらOKです。
git commitとgit push
作成したadd.pyとtest_add.pyをGitLabにpushします。
git add .
git commit -m "add function"
git push
マージリクエストの処理
先程作成しておいたマージリクエストがドラフトの状態である事を確認し、準備済みとしてマークします。
マージリクエストにレビュアーを割り当てます。メンテナーと言う事でtaroを割り当てます。
そしてhanakoはGitLabからサインアウト。
次にtaroとしてGitLabにサインインします。
GitLabの画面上で上記マージリクエストを承認。承認はしなくても良いようですが。
それからマージします。するとコミット内容がfeature/1-addブランチからmainブランチにマージされ、feature/1-addブランチが削除され、イシュー#1がクローズされます。
そしてしばらく待つとパイプラインが成功します。
ステージング
mainブランチの内容をpre-productionブランチへマージします。
- 新しいマージリクエストを作成
- ソースブランチ:
main - ターゲットブランチ:
pre-production - 担当者: 自分をアサインする
- レビュアー: hanako
- ソースブランチ:
- 承認してマージ
マージが完了したらプログラムはステージング環境へ自動的にrsyncされます。結果を確認します。
cd /srv/exampleproject
ls -al
テストを実行してみます。
sudo su runner
cd
. venv/bin/activate
cd /srv/exampleproject
pytest
exit
本番リリース
pre-productionブランチの内容をproductionブランチへマージします。
- 新しいマージリクエストを作成
- ソースブランチ:
pre-production - ターゲットブランチ:
production - 担当者: 自分をアサイン
- レビュアー: hanako
- ソースブランチ:
- 承認してマージ
マージが出来たらプログラムは本番環境へ自動的にrsyncされます。結果を確認します。
ls -al /srv/exampleproject
本番環境ではテストしません。当たり前ですが。実際にtest_add.pyファイルが存在しない事を確認してください。rsyncで除外するようにしているのでデプロイされていないはずです。
プログラムadd.pyの実行は可能です。
sudo su runner
cd /srv/exampleproject
python3 add.py
exit
デプロイ用鍵ペアを削除
デプロイできる事を確認したら、鍵ペアは削除しておきましょう。
cd
cd cicd
rm deploy-key*
仕舞い
個人的に念願だった GitLab CI/CD を、個人的に満足できる形で試す事が出来ました。やったね![]()
セキュリティその他で突っ込み所があれば、遠慮無くコメントください。