2025年10月31日金曜日

Gitlab CI で service を使って Web アプリを起動し簡単なテストを行う

Gitlab CI で service を使って Web アプリを起動し簡単なテストを行う

概要

イメージをビルドしビルドしたイメージからアプリを起動しそのアプリに対してテストするみたいな流れを GItlab CI でやってみます

今回 Web アプリは Python を使っていますが好きな言語のアプリで OK です

環境

  • Gitlab 18.2.6
  • Gitlab Runner 18.2.2
  • Python 3.12.11

Pipfile

[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
flask = "*"

[dev-packages]

[requires]
python_version = "3.12"

app.py

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return "Hello, World!"

if __name__ == '__main__':
    app.run(debug=True, host="0.0.0.0")

Dockerfile

FROM python:3.12.11-slim-bookworm

# 作業ディレクトリを設定
WORKDIR /app

# 必要なファイルをコンテナにコピー
COPY Pipfile Pipfile
COPY Pipfile.lock Pipfile.lock
COPY app.py app.py

# 必要なPythonパッケージをインストール
RUN pip install pipenv
RUN pipenv install

# アプリケーションを起動
CMD ["pipenv", "run", "python", "app.py"]

.gitlab-ci.yml

stages:
  - build  # ビルドステージ
  - test   # テストステージ

build:
  stage: build
  image:
    name: moby/buildkit:rootless  # BuildKit を使用するための Docker イメージ
    entrypoint: [""]  # デフォルトのエントリーポイントを無効化
  variables:
    BUILDKITD_FLAGS: --oci-worker-no-process-sandbox  # BuildKit の設定
  before_script:
    # Docker 認証情報を設定
    - mkdir -p ~/.docker
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > ~/.docker/config.json
  script:
    # BuildKit を使用して Docker イメージをビルドし、GitLab Container Registry にプッシュ
    - |
      buildctl-daemonless.sh build \
        --frontend dockerfile.v0 \
        --local context=. \
        --local dockerfile=. \
        --output type=image,name=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA,push=true

app:
  stage: test
  services:
    # ビルドした Docker イメージをサービスとして起動
    - name: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
      alias: testapp  # サービスに "testapp" というホスト名を割り当て
  script:
    # サービスが起動するまで待機
    - echo "Waiting for the service to be ready..."
    - >
      for i in {1..30}; do
        curl -s http://testapp:5000 && break || sleep 1;  # サービスが応答するまで最大 30 秒間リトライ
      done
    - echo "Service is ready."  # サービスが起動したことを確認

validate:
  stage: test
  image: curlimages/curl:latest  # curl を使用するための軽量イメージ
  services:
    # ビルドした Docker イメージを再度サービスとして起動
    - name: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
      alias: testapp  # サービスに "testapp" というホスト名を割り当て
  script:
    # サービスに対してレスポンスを検証
    - echo "Running validation tests..."
    - |
      RESPONSE=$(curl -s http://testapp:5000)  # サービスにリクエストを送信しレスポンスを取得
      if [ "$RESPONSE" = "Hello, World!" ]; then
        echo "Validation passed!"  # レスポンスが期待通りの場合
      else
        echo "Validation failed: Expected 'Hello, World!' but got '$RESPONSE'"  # レスポンスが期待と異なる場合
        exit 1  # ジョブを失敗させる
      fi

最後に

Gitlab には Ultimate ライセンスを使っていると DAST を使えるのですがそうじゃないライセンスだと使えません
そんな場合に自分で DAST 的なことをする必要があるので今回のような流れで実装できます

次回は nikto を組み合わせてセキュリティテストしてみます

2025年10月26日日曜日

LetsEncrypt で 0001 や 0002 などの証明書が取得できてしまった場合の解決方法

LetsEncrypt で 0001 や 0002 などの証明書が取得できてしまった場合の解決方法

概要

前の設定が残っている状態で新規に証明書を発行すると連番が付いてしまいます
こうなると /etc/letsencrypt/live/ 配下で証明書を管理するディレクトリにも 0001 などが付き色々と面倒なので削除する方法を紹介します

なお過去に紹介した docker compose バージョンの certbot を使います

環境

  • Ubuntu 22.04
  • certbot 5.1.0

削除する

サフィックスが付いている証明書および付いていないやつも削除しましょう

  • docker compose run --rm certbot delete --cert-name your-domain.com-0001
  • docker compose run --rm certbot delete --cert-name your-domain.com-0002
  • docker compose run --rm certbot delete --cert-name your-domain.com

これで再度取得すればサフィックスなし版が取得できます
なおサフィックス版があるかないかは certificates サブコマンドを使って確認しましょう

  • docker compose run --rm certbot certificates

最後に

ホスト側の証明書をコンテナにマウントして使う場合にサフィックスがあるといろいろ面倒なのでなし版で取得するようにしましょう

参考サイト

2025年10月25日土曜日

Microsoft Graph API を使って個人の OneDrive を操作する方法

Microsoft Graph API を使って個人の OneDrive を操作する方法

概要

前回 OneDrive にアクセスできるトークンの取得まで行いました
今回はそのトークンを使って実際に OneDrive にアクセスします

環境

  • macOS 15.7.1
  • Python 3.12.11

サンプルコード

import json
import os

import requests

TENANT_ID = "xxx"
CLIENT_ID = "xxx"
CLIENT_SECRET = "xxx"
REDIRECT_URI = "http://localhost:8080"
TOKEN_FILE = "onedrive_token.json"
GRAPH_API_BASE = "https://graph.microsoft.com/v1.0"


# =========================
# トークン管理
# =========================
def load_tokens():
    """保存済みトークンを読み込む"""
    if not os.path.exists(TOKEN_FILE):
        raise FileNotFoundError(f"{TOKEN_FILE} が存在しません。先にトークン取得を実行してください。")
    with open(TOKEN_FILE, "r", encoding="utf-8") as f:
        return json.load(f)


def save_tokens(tokens):
    """トークンを保存"""
    with open(TOKEN_FILE, "w", encoding="utf-8") as f:
        json.dump(tokens, f, indent=2)


def refresh_access_token(refresh_token, client_id, client_secret, redirect_uri):
    """リフレッシュトークンからアクセストークンを更新"""
    token_url = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"
    data = {
        "client_id": client_id,
        "client_secret": client_secret,
        "redirect_uri": redirect_uri,
        "grant_type": "refresh_token",
        "refresh_token": refresh_token
    }
    res = requests.post(token_url, data=data)
    res.raise_for_status()
    return res.json()


# =========================
# OneDrive 操作
# =========================
def get_headers(access_token):
    return {"Authorization": f"Bearer {access_token}"}


def upload_file(access_token, local_path, remote_name):
    """小さなファイルをアップロード"""
    url = f"{GRAPH_API_BASE}/me/drive/root:/{remote_name}:/content"
    with open(local_path, "rb") as f:
        res = requests.put(url, headers=get_headers(access_token), data=f)
    if res.status_code in (200, 201):
        print(f"✅ アップロード成功: {remote_name}")
    else:
        print(f"❌ アップロード失敗 ({res.status_code}): {res.text}")


def list_files(access_token):
    """ルートディレクトリのファイル一覧取得"""
    url = f"{GRAPH_API_BASE}/me/drive/root/children"
    res = requests.get(url, headers=get_headers(access_token))
    if res.status_code == 200:
        items = res.json().get("value", [])
        print("📄 OneDrive ファイル一覧:")
        for item in items:
            print(f" - {item['name']}")
    else:
        print(f"❌ 取得失敗 ({res.status_code}): {res.text}")


def delete_file(access_token, remote_name):
    """ファイル削除"""
    url = f"{GRAPH_API_BASE}/me/drive/root:/{remote_name}"
    res = requests.delete(url, headers=get_headers(access_token))
    if res.status_code in (204, 200):
        print(f"🗑️ 削除成功: {remote_name}")
    else:
        print(f"❌ 削除失敗 ({res.status_code}): {res.text}")


# =========================
# メイン
# =========================
if __name__ == "__main__":
    tokens = load_tokens()
    access_token = tokens["access_token"]

    # 必要に応じてリフレッシュ
    tokens = refresh_access_token(tokens["refresh_token"], CLIENT_ID, CLIENT_SECRET, REDIRECT_URI)
    access_token = tokens["access_token"]
    save_tokens(tokens)

    # サンプルファイル作成
    sample_file = "test.txt"
    with open(sample_file, "w", encoding="utf-8") as f:
        f.write("Hello OneDrive via Graph API!")

    # アップロード
    upload_file(access_token, sample_file, "test.txt")

    # ファイル一覧
    list_files(access_token)

    # 削除(必要であればコメント解除)
    delete_file(access_token, "test.txt")

ポイント

  • トークンが切れた場合にリフレッシュトークを使って再度トークンを取得します
  • 毎回行っても問題ないので実際は毎回リフレッシュトークを使って問い合わせたほうがいいです
  • トークンをファイルで保存していますが漏洩すると大変なので管理には注意しましょう

最後に

Python で OneDrive の操作をしてみました
認証が面倒ですが一度認証してしまえばあとはスクリプトを実行するだけです

アプリのシークレットに期限があるのでその期限がすぎたら再度ブラウザを開いてトークンファイルを更新する必要があります

2025年10月24日金曜日

Microsoft Graph API を使って個人の OneDrive にアクセスする方法

Microsoft Graph API を使って個人の OneDrive にアクセスする方法

概要

前回 Graph API に入門しました
今回は個人用の OneDrive アクセスしてみます
なそアプリーケーション権限は使えず「委任されたアクセス許可」に変更する必要があるので注意してください

簡単に言うと API をコールするためのアクセストークンを一度ブラウザを開いて取得しなければなりません

環境

  • macOS 15.7.1
  • Python 3.12.11

サポートされているアカウントの種類

  • 任意の組織ディレクトリ内のアカウント (任意の Microsoft Entra ID テナント - マルチテナント) と個人用の Microsoft アカウント (Skype、Xbox など)

もしくは

  • 個人用 Microsoft アカウントのみ

権限の設定

  • 管理 -> API のアクセス許可 -> アクセス許可の追加 -> Microsoft Graph -> 委任されたアクセス許可
    • Files.ReadWrite.All
    • Sites.ReadWrite.All
    • User.Read
    • offline_access
  • 既定のディレクトリに管理者の権限を与えますを選択

コールバックURLの設定

  • 管理 -> 認証 -> URLの追加 -> http://localhost:8080

トークンの取得

これは一度だけ実行します
表示された URL にブラウザでアクセスしログインします

  • vim ./fetch_token.py
import http.server
import json
import socketserver
import urllib.parse
import webbrowser

import requests

# ======== 設定 ========
TENANT_ID = "xxx"
CLIENT_ID = "xxx"
CLIENT_SECRET = "xxx"
REDIRECT_URI = "http://localhost:8080"
SCOPES = ["Files.ReadWrite.All", "Sites.ReadWrite.All", "User.Read", "offline_access"]
TOKEN_FILE = "onedrive_token.json"
# =======================


def start_local_server():
    """localhostで認可コードを受け取るための簡易HTTPサーバー"""
    class Handler(http.server.SimpleHTTPRequestHandler):
        def do_GET(self):
            query = urllib.parse.urlparse(self.path).query
            params = urllib.parse.parse_qs(query)
            if "code" in params:
                setattr(self.server, "auth_code", params["code"][0])
                self.send_response(200)
                self.end_headers()
                self.wfile.write(b"Authorization successful. You can close this window.")
            else:
                self.send_response(400)
                self.end_headers()
                self.wfile.write(b"Missing authorization code.")

    with socketserver.TCPServer(("localhost", 8080), Handler) as httpd:
        setattr(httpd, "auth_code", None)
        print("🌐 ローカルサーバー起動中 (http://localhost:8080)")
        httpd.handle_request()
        return getattr(httpd, "auth_code", None)


def get_tokens(auth_code):
    """認可コードからトークンを取得"""
    token_url = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"
    data = {
        "client_id": CLIENT_ID,
        "redirect_uri": REDIRECT_URI,
        "client_secret": CLIENT_SECRET,
        "code": auth_code,
        "grant_type": "authorization_code"
    }
    res = requests.post(token_url, data=data)
    res.raise_for_status()
    return res.json()


def save_tokens(tokens):
    """トークンをローカルに保存"""
    with open(TOKEN_FILE, "w", encoding="utf-8") as f:
        json.dump(tokens, f, indent=2)
    print(f"💾 トークンを保存しました → {TOKEN_FILE}")


def main():
    # 1️⃣ 認可URLを生成
    params = {
        "client_id": CLIENT_ID,
        "response_type": "code",
        "redirect_uri": REDIRECT_URI,
        "response_mode": "query",
        "scope": " ".join(SCOPES)
    }
    auth_url = f"https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?{urllib.parse.urlencode(params)}"

    print("\n🔗 以下のURLを開いてMicrosoftアカウントでサインインしてください:")
    print(auth_url)
    webbrowser.open(auth_url)

    # 2️⃣ 認可コード受け取り
    auth_code = start_local_server()
    if not auth_code:
        print("❌ 認可コードを取得できませんでした。")
        return

    print(f"✅ 認可コード取得成功: {auth_code[:20]}...")

    # 3️⃣ トークン取得
    tokens = get_tokens(auth_code)
    print("✅ アクセストークン・リフレッシュトークン取得成功")
    save_tokens(tokens)

    print("\n🧾 トークン内容:")
    print(json.dumps(tokens, indent=2))


if __name__ == "__main__":
    main()

動作確認

  • pipenv run python ./app.py

でブラウザが開いたらMicrosoft アカウントでログインしアプリに許可を与えます
成功するとトークン情報が onedrive_token.json に保存されるので今後はこれを使って OneDrive にアクセスします

最後に

Python から OneDrive にアクセスするためのトークンの取得まで行いました
次回は実際にファイルのアップロードや取得などを行います

Onedrive for bussiness だとアプリケーションの許可の権限で操作できるようです

2025年10月23日木曜日

Microsoft Graph API を Python で使ってみる

Microsoft Graph API を Python で使ってみる

概要

Microsoft365 の API である Microsoft Graph API を Python から利用してみました

環境

  • macOS 15.7.1
  • Python 3.12.11

アプリの作成

  • https://portal.azure.com にログイン
  • 左メニューから「Microsoft Entra ID」を選択
  • 「アプリの登録」->「新規登録」をクリック
    • 名前 -> 何でも OK
    • サポートされているアカウントの種類 -> この組織ディレクトリのみに含まれるアカウント (既定のディレクトリ のみ - シングル テナント)
    • リダイレクト URI -> とりあえず空でOK
  • テナントID、クライアントID をコピーしておく

シークレットの登録

  • 「証明書とシークレット」->「新しいクライアント シークレット」から発行
  • 「値」をコピーしておく

API アクセスの許可

  • アプリ登録ページ左メニューから「API のアクセス許可」を選択
  • 「+ アクセス許可の追加」->「Microsoft Graph」->「アプリケーションの許可」を選択
    • Sites.ReadWrite.All
    • Files.ReadWrite.All
  • アクセス許可の追加」->「管理者の同意を与える」をクリック(管理者権限が必要)

ライブラリインストール

  • pipenv install msal requests

アクセストークンを取得するサンプルコード

とりあえず作成したアプリがアクセスできる認証情報だけ取得してみます

  • vim ./app.py
import pprint

from msal import ConfidentialClientApplication

TENANT_ID = "xxx"
CLIENT_ID = "xxx"
CLIENT_SECRET = "xxx"

app = ConfidentialClientApplication(
    CLIENT_ID,
    authority=f"https://login.microsoftonline.com/{TENANT_ID}",
    client_credential=CLIENT_SECRET,
)

result = app.acquire_token_for_client(scopes=["https://graph.microsoft.com/.default"])

pprint.pprint(result)
  • pipenv run python ./app.py

以下のようにトークンが取得できれば OK です

{'access_token': 'xxx',
 'expires_in': 3599,
 'ext_expires_in': 3599,
 'token_source': 'identity_provider',
 'token_type': 'Bearer'}

sharepoint のサイト一覧を取得する

  • vim ./app.py
import requests
from msal import ConfidentialClientApplication

TENANT_ID = "xxx"
CLIENT_ID = "xxx"
CLIENT_SECRET = "xxx"

app = ConfidentialClientApplication(
    CLIENT_ID,
    authority=f"https://login.microsoftonline.com/{TENANT_ID}",
    client_credential=CLIENT_SECRET,
)
token = app.acquire_token_for_client(scopes=["https://graph.microsoft.com/.default"])
if token is None:
    raise Exception("トークンの取得に失敗しました。")
headers = {"Authorization": f"Bearer {token['access_token']}"}

# サイト一覧を検索(すべて表示)
url = "https://graph.microsoft.com/v1.0/sites?search=*"
response = requests.get(url, headers=headers)
print(response.json())
sites = response.json()

for s in sites.get("value", []):
    print(f"名前: {s['name']}")
    print(f"URL:  {s['webUrl']}")
    print(f"ID:   {s['id']}")
    print("---")

sharepoint は当然ですがライセンスがないと使えないので「Tenant does not have a SPO license.」のエラーになります

最後に

Python から MicrosoftGraphAPIをコールしてみました
無料アカウントでもここまではいけます
次回は実際に Microsoft365 上にあるリソースにアクセスしてみます

参考サイト

2025年10月22日水曜日

journal ログをすべて削除する方法

journal ログをすべて削除する方法

概要

忘れるのでメモ

環境

  • Ubuntu 22.04

全削除

前

sudo du -sh /var/log/
3.3G    /var/log/
  • sudo journalctl --vacuum-time=1s

削除ログ

Vacuuming done, freed 0B of archived journals from /run/log/journal.
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/user-1002@cb5cf46ac4024d6ab8ac0f7039ee62f0-000000000279f7c3-00063ffa5ec9cd0c.journal (8.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-00000000027b400b-00064004588b93b5.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-00000000027d6843-000640152308d284.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-00000000027f9176-00064025f94a8e93.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-000000000281b863-00064036e00660b8.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-000000000283e0cf-00064047f2873c97.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-00000000028609f5-00064058f8e381d0.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-00000000028834bc-0006406a1746446c.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-00000000028a5e91-0006407b42809cd3.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-00000000028c873b-0006408c87b6d808.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/user-1002@cb5cf46ac4024d6ab8ac0f7039ee62f0-00000000028e2463-0006409965aaafa8.journal (8.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-00000000028eb3bd-0006409dca5e037c.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-000000000290dc16-000640ae8e9c7381.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/user-1002@cb5cf46ac4024d6ab8ac0f7039ee62f0-0000000002912098-000640b0a2d36d3b.journal (8.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-0000000002930478-000640bf5eb5c522.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/user-1002@cb5cf46ac4024d6ab8ac0f7039ee62f0-0000000002936af1-000640c27aa7db32.journal (8.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-0000000002952cde-000640d027fee751.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-000000000297559b-000640e0f3a72cf5.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-0000000002997e37-000640f1d327fe5d.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-00000000029ba58e-00064102c1378bd0.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-00000000029dce0e-00064113bde6de0a.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-00000000029ff7c5-00064124cc9e6f43.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-0000000002a2215b-00064135ea1f4111.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-0000000002a44b4b-000641471cc25b72.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-0000000002a6736b-00064158657fee95.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-0000000002a89d70-00064169c011da77.journal (128.0M).
Vacuuming done, freed 2.7G of archived journals from /var/log/journal/95b2c59cdf7701096ea7d3386e92e124.
Vacuuming done, freed 0B of archived journals from /var/log/journal.

後

sudo du -sh /var/log/
443M    /var/log/

微妙に残ります
インスタンス自体を再起動すると更に減ります

du 以外にも journalctl 専用のコマンドでログの使用量を確認することもできます

  • sudo journalctl --disk-usage
Archived and active journals take up 136.0M in the file system.

そもそも保存させる容量を調整する

  • sudo vim /etc/systemd/journald.conf
[Journal]
SystemMaxUse=100M
  • sudo systemctl restart systemd-journald

ちなみに SystemMaxUse のデフォルトはディスクサイズの10%なので30GBのディスクであれば3.0GBほど保存します

最後に

だいたい dockerd のログでうまります

参考サイト

2025年10月21日火曜日

buildah 超入門

buildah 超入門

概要

buildah を使ってみました
基本は docker のように使えますが rootless 実行するのがかなり面倒そうです

環境

  • Ubuntu 24.04
  • buildah 1.33.7
  • docker 28.5.1

インストール

  • sudo apt -y update
  • sudo apt -y install buildah

apt の公式リポジトリからインストールできますが最新版はインストールできません

イメージのビルド (rootモード)

  • sudo buildah build -f Dockerfile -t lab .

sudo 付きは root モードなのであまりお勧めできません

イメージのビルド (rootless モード)

不明

Error during unshare(CLONE_NEWUSER): Permission denied
ERRO[0000] parsing PID "": strconv.Atoi: parsing "": invalid syntax
ERRO[0000] (Unable to determine exit status)

になり実行できませんでした unshare --user --map-root-user --mount あたりが実行できないと rootless 実行は厳しいです

確認

  • sudo buildah images
REPOSITORY                 TAG                     IMAGE ID       CREATED          SIZE
localhost/lab              latest                  6b467d5adf55   31 seconds ago   129 MB
docker.io/library/python   3.12.11-slim-bookworm   688a685f6a1f   2 months ago     129 MB

イメージのプッシュ

  • sudo buildah login your-container-registry-address
  • sudo buildah tag localhost/lab your-container-registry-address/username/reponame
  • sudo buildah push your-container-registry-address/username/reponame

最後に

基本は docker コマンドと同様に使えます
rootless 実行が良い点なのですが rootless 実行できるようにするまでがかなり大変そうなのでそこが何ともといった感じです

当然ですが docker デーモンがなくても buildah を使ってビルドはできるのでそれだけでも kaniko の代用にはなりそうです

  • sudo systemctl stop docker
  • sudo systemctl stop docker.socket

参考サイト