0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GitLab(18.9.0)でCI/CDをやってみた、セキュリティにも気を配ってみた

0
Posted at

能書き

おうちサーバー構築報告:予告からのおうちサーバー構築です。

先日は GitLab Runner をインストールしてGitLabサーバーと連係した訳ですが。今回はこれを使用してCI/CDを試してみました。折角なので、セキュリティにも気を配った手順を追及してみました。

前提

目標

  • GitLab Flowを意識する
  • ステージング環境と本番環境を用意する
  • 開発環境→ステージング環境→本番環境の GitLab CI/CD を試行する

参考文献

GitLab Flow とはこんな概念のようです。

CI/CDについては全く知識の無い私ですが、主に下記の記事で学習させていただきました。

用意する環境については私自身が大昔こんな記事を書いたりしました。しかし今回は GitLab Flow を意識した事もあり、世間で一般的と思われるステージング環境と本番環境だけ用意する事にします。

Ansibleのplaybookについては下記記事を参考にしました。

今回の実験ではUbuntu上でPythonを使ってみます。

環境ごとに異なる設定値を管理する事に関する記事です。要するに.envファイルを使うという事です。

.gitlab-ci.ymlを作成するパイプラインエディタと言う機能がGitLabには備わっていました。

各環境へデプロイするにはssh経由でrsyncする訳ですが、そこで必要になるアレコレ。

ssh鍵のGitLabへの登録については一捻り必要なようです。

rootユーザー禁止に関するアレコレ。

小ネタとして、Pythonに関するアレコレ。

そもそもの前提として、我が家ではProxmoxVE上で下記のように環境(システムコンテナと仮想マシン)を用意しています。

環境構築

自動デプロイと運用保守の為、2つのくちが環境ごとに必要です。そして運用保守は踏み台経由とする方針にします。

踏み台の公開鍵を確認

ProxmoxVE上で最初にサーバー体系を構築した際のAnsibleコントロールノード(IPアドレス: 172.16.1.100)を踏み台とします。まずはそれのssh公開鍵を確認します。公開鍵を表示してメモしておきます。

踏み台(172.16.1.100):ユーザー ansible
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ユーザーでパスワード無しのログインが出来る事を確認しておきます。

踏み台(172.16.1.100):ユーザー ansible
ssh root@172.16.1.106
踏み台(172.16.1.100):ユーザー ansible
ssh root@172.16.1.107

保守用ssh接続の準備

保守用接続に使用するssh鍵は踏み台の鍵を使用します。先程メモした踏み台の公開鍵をまた利用します。

デプロイ用ssh接続の準備

デプロイ時に使用するssh鍵を作成します。

踏み台(172.16.1.100):ユーザー ansible
cd
mkdir cicd
cd cicd
ssh-keygen -t ed25519 -P "" -f deploy-key -C runner@GitLabRunner

この公開鍵deploy-key.pubと、秘密鍵deploy-keyの内容もメモしておきます。
秘密鍵は後程GitLabに登録するのですが、その都合で、空白文字や改行コードが無い状態にする必要があります。そのためbase64 -w0コマンドで変換します。

踏み台(172.16.1.100):ユーザー ansible
cat deploy-key.pub
base64 -w0 deploy-key

Ansible

まずはAnsibleのインベントリを設定します。

踏み台(172.16.1.100):ユーザー ansible
cd
cd cicd
cat <<___ >inventory.ini
[servers]
172.16.1.106
172.16.1.107

[servers:vars]
ansible_user=root
___

インベントリの確認の為にpingします。

踏み台(172.16.1.100):ユーザー ansible
ansible all -m ping -i inventory.ini

成功したらplaybook.yamlを作ります。内容は下記の通り。

  • 毎度お馴染みのapt update&&apt upgrade
  • デプロイ兼実行ユーザーrunnerの作成(sshログインはrsyncコマンド実行専用とする)
  • 保守ユーザーmaintainerの作成(sudoersに追加)
  • プロジェクトを置くディレクトリを/srv/exampleprojectとし、そのパーミッションを整える
  • Pythonのインストールと関連設定
  • rootユーザーのログイン禁止とsshdの再起動
踏み台(172.16.1.100):ユーザー ansible
MAINTAINER_PUB_KEY="≪メモしておいた、保守用ssh接続の為の公開鍵≫"
RUNNER_PUB_KEY="≪メモしておいた、デプロイ用ssh接続の為の公開鍵≫"
踏み台(172.16.1.100):ユーザー ansible
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コマンドを指定して、他のコマンドを実行できないようにします。

踏み台(172.16.1.100):ユーザー ansible
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
___

文法チェック。

踏み台(172.16.1.100):ユーザー ansible
ansible-playbook -i inventory.ini playbook.yaml --syntax-check

問題無ければ実行しましょう。

踏み台(172.16.1.100):ユーザー ansible
ansible-playbook -i inventory.ini playbook.yaml

環境毎の手動設定

下記項目を手動設定します。

  • maintainerユーザーのパスワード
    • 上記のAnsibleの設定だとinitial passwordになっている
    • 環境毎に異なる強固なパスワードをpasswdコマンドで手動設定(パスワードはsudo時に必要)
  • .envを作成
    • 今回はダミーデータを格納しておく
  • ステージング環境には、Python仮想環境を作成してpytest(及び必要なPythonモジュール)をインストール

まずは本番環境から。まぁどちらが先でもいいんですが。

踏み台(172.16.1.100):ユーザー ansible
ssh maintainer@172.16.1.106
ExampleProject-PRD(172.16.1.106):ユーザー maintainer
passwd
cd /srv/exampleproject
cat <<___ | sudo tee -a .env >/dev/null
EXAMPLE_ENV=production
___
sudo chown runner:runner .env
sudo chmod 444 .env
exit

次にステージング環境。

踏み台(172.16.1.100):ユーザー ansible
ssh maintainer@172.16.1.107
ExampleProject-STG(172.16.1.107):ユーザー maintainer
passwd
sudo apt install -y python3.12-venv
sudo su runner
ExampleProject-STG(172.16.1.107):ユーザー 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
ExampleProject-STG(172.16.1.107):ユーザー runner
exit
ExampleProject-STG(172.16.1.107):ユーザー maintainer
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.yml
    • README.md
    • .venv
    • test*.pyは除外せずにデプロイする)
  • 本番環境への自動デプロイにおいては下記ファイルをデプロイから除外(rsyncの設定)
    • .env
    • .git
    • .gitlab-ci.yml
    • README.md
    • test*.py

具体的には下記の通りです。

こういう場合、imageはalpine:latestを使うものだと思いますが。rsyncコマンドのバージョンが合わなかった(2026年5月9日現在でaplineのrsyncはubuntuの物より新し過ぎて、しかも古いバージョンを指定してのインストールが出来なかった)ので、ubuntu:24.04を指定しました。

.gitlab-ci.yml
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などもテンプレート内に含まれてますね。

.gitignore
__pycache__/
.venv
.env
などなど、テンプレート内容をそのままコミット

そうしたらエディタ右上の「変更をコミットする」ボタンをクリックして、現在のmainブランチにコミットします。mainブランチは保護されてますが、プロジェクトのオーナーならコミットできます。

ブランチの作成と保護

最初は下記ブランチのみ存在し、デフォルトで保護されています。そしてこれがデフォルトブランチです。

  • main

mainブランチから下記ブランチを作成して保護し、デベロッパーが直接コミットできないようにします。

  • pre-production
  • production

ここまで出来たらtaroはGitLabからサインアウトします。

開発環境の構築

私の場合、普段使いのノートパソコンがWindows11です。ここにWSL2(Ubuntu)で環境を作っています。

今回はPythonですので、一応、仮想環境を用意します

まずはvenvをインストール。

普段使いのWindows11:WSL2(Ubuntu)
sudo apt install python3.12-venv

それからPythonの仮想環境を作成。

普段使いのWindows11:WSL2(Ubuntu)
cd
mkdir ExampleProject
cd ExampleProject
python3 -m venv .venv
source .venv/bin/activate

基本的なライブラリやフレームワークを、Python仮想環境内にインストールします。

普段使いのWindows11:WSL2(Ubuntu)
pip install pytest pytest-cov python-dotenv

ここからのGitLab操作はhanakoアカウントで実施する事にします。

sshでgit cloneできるように、鍵を作ってhanakoに登録します。まずは鍵の作成。下記コマンドを実行します。

普段使いのWindows11:WSL2(Ubuntu)
ssh-keygen -t ed25519 -P "" -C "hanako@local"

作成した鍵ペアのうち公開鍵の方を表示します。

普段使いのWindows11:WSL2(Ubuntu)
cat ~/.ssh/id_ed25519.pub

これをGitLabのhanakoアカウントのSSHキーとして登録します。GitLab画面右上のユーザーアイコンをクリックしてポップアップメニュー>設定を選択、表示された画面左メニュー>SSHキー、そして新しいキーを追加します。

この操作が完了したらExampleProjectをsshでクローンできるはずです。

普段使いのWindows11:WSL2(Ubuntu)
git clone ≪プロジェクトで指定されるURL≫

そうしたら最初に.envを作ります。

普段使いのWindows11:WSL2(Ubuntu)
cd exampleproject
cat <<___ >.env
EXAMPLE_ENV=develop
___
chmod 444 .env

これで開発環境を構築できました。

プロジェクトを回す

イシューを作成

GitLabは引き続きhanakoで操作します。

まずはGitLabでイシューを作成します。日本語だとチケットでしょうか。内容は適当に設定します。

  • タイプ: イシュー
  • タイトル: 機能追加:加算
  • 説明: 最初の機能を実装。内容は加算。

プロジェクトで最初のイシューなのでイシュー番号は1になります。

イシューに基づくブランチを作成

作成したイシューのページに移動します。そして「マージリクエストを作成」をクリック。

  • ターゲットブランチ: main
  • ソースブランチ名: feature/1-add (何でもいいのですが、慣例に従いこんなような名前で)
  • ドラフトとしてマーク
  • 担当者: 自分をアサインする
  • マージリクエストが承認されたときにソースブランチを削除します

git checkout

コードをpullして、上記で作成したブランチをチェックアウトします。

普段使いのWindows11:WSL2(Ubuntu)
git pull
git checkout feature/1-add

プログラムとテストコードを作成

実験なので簡単なプログラムにします。何でもいいのですが、例えば下記を。

exampleproject/add.py
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)))

対応するテストコードはこちらになります。

exampleproject/test_add.py
from add import add
import pytest

def test_add():
    assert add(1, 2) == 3

開発環境で実行してみます。

普段使いのWindows11:WSL2(Ubuntu)
python3 add.py

テストも実行してみます。

普段使いのWindows11:WSL2(Ubuntu)
pytest

テストの方は、成功したらOKです。

git commitgit push

作成したadd.pytest_add.pyをGitLabにpushします。

普段使いのWindows11:WSL2(Ubuntu)
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されます。結果を確認します。

172.16.1.107:ユーザーmaintainer
cd  /srv/exampleproject
ls -al

テストを実行してみます。

172.16.1.107:ユーザーmaintainer
sudo su runner
172.16.1.107:ユーザーrunner
cd
. venv/bin/activate
cd /srv/exampleproject
pytest
exit

本番リリース

pre-productionブランチの内容をproductionブランチへマージします。

  • 新しいマージリクエストを作成
    • ソースブランチ: pre-production
    • ターゲットブランチ: production
    • 担当者: 自分をアサイン
    • レビュアー: hanako
  • 承認してマージ

マージが出来たらプログラムは本番環境へ自動的にrsyncされます。結果を確認します。

172.16.1.106:ユーザーmaintainer
ls -al /srv/exampleproject

本番環境ではテストしません。当たり前ですが。実際にtest_add.pyファイルが存在しない事を確認してください。rsyncで除外するようにしているのでデプロイされていないはずです。

プログラムadd.pyの実行は可能です。

172.16.1.106:ユーザーmaintainer
sudo su runner
172.16.1.106:ユーザーrunner
cd /srv/exampleproject
python3 add.py
exit

デプロイ用鍵ペアを削除

デプロイできる事を確認したら、鍵ペアは削除しておきましょう。

踏み台(172.16.1.100):ユーザー ansible
cd
cd cicd
rm deploy-key*

仕舞い

個人的に念願だった GitLab CI/CD を、個人的に満足できる形で試す事が出来ました。やったね:thumbsup_tone1:

セキュリティその他で突っ込み所があれば、遠慮無くコメントください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?