100本ノック / システム開発 / システム開発100本ノック
製造業向けDocker・CI/CD入門|工場APIを安全にクラウド運用する10本ノック
工場の業務APIを安全に届け続ける――Docker・クラウド・CI/CD実践10本ノック
概要
架空の精密部品工場で利用する「生産進捗API」と現場Web画面を題材に、Dockerfile、バックエンド・フロントエンドのコンテナ化、Docker Compose、環境差分、GitHub Actions、テスト自動化、クラウドへのデプロイ、環境変数・シークレット管理を一続きで扱います。
単なるコマンド集ではなく、ビルド時間、イメージ容量、稼働率、テスト検出率、デプロイ時間、変更失敗率などを架空データで分析し、製造現場の業務を止めないリリース判断へつなげます。
[!NOTE] 本資料は、数理工房 (もしくは代表である和山個人) が過去に企業研修において使用した notebook を企業様の許可を得て再構成・編集のうえ公開しています。 掲載データはすべて架空のものであり、実在する企業・工場・数値とは一切関係ありません。
はじめに:この記事で扱う製造業の実務課題
生産進捗APIは、作業実績、仕掛品、設備状態を現場画面へ届けます。機能が正しくても、担当者のPCでしか動かない、リリース手順が属人化する、環境変数を取り違える、更新のたびに長時間停止する状態では業務基盤として使えません。
今回の課題は、同じ成果物を再現可能に配布し、変更を自動検査し、障害時に短時間で戻せる提供工程を設計することです。
現場でよくある状況
- 開発PCでは動くが、工場サーバーではライブラリ差分で起動しない
- API、画面、DBを手順書どおり個別起動し、設定漏れが起きる
- 本番用URLや認証情報をソースコードへ直接書く
- テストを担当者の記憶に頼り、繁忙期に省略する
- デプロイ成功を「コマンドが終了したこと」だけで判断する
- 新版に問題があっても旧版へ戻す手順と判断者が決まっていない
なぜこの問題は判断が難しいのか
提供速度、安定性、セキュリティにはトレードオフがあります。テストを増やせば検出力は上がりますが待ち時間も伸びます。小さなイメージは配布に有利ですが、極端な削減は調査用ツールを失わせます。また、99.9%の稼働率でも停止が生産ピークに重なれば影響は大きくなります。
したがって技術の採否ではなく、復旧目標、許容停止時間、変更頻度、データ機密度を先に決め、測定可能な受入条件へ落とします。
今回扱うノックの全体像
| No. | テーマ | 実務上の判断 |
|---|---|---|
| 071 | Dockerfile | 再現可能で小さな実行単位をどう作るか |
| 072 | バックエンド | APIの起動・ヘルスチェックをどう標準化するか |
| 073 | フロントエンド | ビルドと配信をどう分離するか |
| 074 | Docker Compose | API・DBを依存関係ごと起動するには何が必要か |
| 075 | 開発・本番環境 | 共通化するものと分離する設定は何か |
| 076 | GitHub Actions | どの変更をどの条件で検査するか |
| 077 | テスト自動実行 | 時間内で欠陥検出力をどう高めるか |
| 078 | APIデプロイ | 可用性とロールバックをどう確認するか |
| 079 | 画面デプロイ | キャッシュを含め新版をどう届けるか |
| 080 | 環境変数・Secret | 秘密をコードから分離し、どう更新するか |
Python 環境の準備
numpyとpandasで架空のビルド・CI/CD・稼働データを生成し、matplotlibで可視化します。乱数seedを固定するため再実行しても同じ結果になります。Dockerやクラウドへの外部接続は行わず、設定例を文字列として検査します。
%matplotlib inline
%config InlineBackend.figure_format = "svg"
import sys
import numpy as np
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
import japanize_matplotlib
from IPython.display import display
rng = np.random.default_rng(8071)
pd.set_option("display.max_colwidth", 80)
print("Python:", sys.version.split()[0])
print("numpy:", np.__version__, "pandas:", pd.__version__, "matplotlib:", matplotlib.__version__)
Python: 3.11.9
numpy: 1.26.4 pandas: 2.2.2 matplotlib: 3.9.2
架空データの作成
120回のビルド、160回のCI、90回のデプロイと、14日分・5分間隔のAPI監視データを生成します。障害や失敗も意図的に含めます。値は教材用の仮定であり、導入時は実測値で置き換えます。
builds = pd.DataFrame({
"build_id": [f"B-{i:03d}" for i in range(1, 121)],
"strategy": rng.choice(["単一stage", "multi-stage"], 120, p=[0.45, 0.55]),
"cache_hit": rng.random(120) < 0.62,
})
builds["image_mb"] = np.where(builds.strategy.eq("multi-stage"), rng.normal(185, 20, 120), rng.normal(540, 55, 120)).clip(120)
builds["build_sec"] = (rng.normal(175, 28, 120) - builds.cache_hit * rng.normal(92, 12, 120)).clip(35)
ci = pd.DataFrame({"run_id": range(1, 161), "test_level": rng.choice(["lint・unit", "unit・API", "全テスト"], 160, p=[.25,.45,.30])})
ci["duration_min"] = ci.test_level.map({"lint・unit":3.2,"unit・API":7.5,"全テスト":15.0}) + rng.normal(0,1,160)
ci["defect_found"] = rng.random(160) < ci.test_level.map({"lint・unit":.10,"unit・API":.22,"全テスト":.34})
deploys = pd.DataFrame({"deploy_id":[f"D-{i:03d}" for i in range(1,91)], "service":rng.choice(["API","frontend"],90), "strategy":rng.choice(["一括更新","rolling"],90,p=[.38,.62])})
deploys["duration_min"] = (deploys.strategy.map({"一括更新":8.0,"rolling":12.5}) + rng.normal(0,2,90)).clip(2)
deploys["failed"] = rng.random(90) < deploys.strategy.map({"一括更新":.16,"rolling":.07})
deploys["rollback_min"] = np.where(deploys.failed, rng.gamma(2.2,3.2,90), 0)
monitor = pd.DataFrame({"time":pd.date_range("2026-06-01", periods=14*24*12, freq="5min")})
monitor["available"] = rng.random(len(monitor)) > .0025
monitor["latency_ms"] = rng.lognormal(np.log(125), .38, len(monitor))
display(builds.head(3), ci.head(3), deploys.head(3))
| build_id | strategy | cache_hit | image_mb | build_sec | |
|---|---|---|---|---|---|
| 0 | B-001 | multi-stage | True | 206.041371 | 90.241641 |
| 1 | B-002 | 単一stage | False | 503.717863 | 192.183345 |
| 2 | B-003 | 単一stage | True | 599.575449 | 92.300920 |
| run_id | test_level | duration_min | defect_found | |
|---|---|---|---|---|
| 0 | 1 | unit・API | 9.188109 | True |
| 1 | 2 | lint・unit | 4.084486 | False |
| 2 | 3 | unit・API | 7.714805 | False |
| deploy_id | service | strategy | duration_min | failed | rollback_min | |
|---|---|---|---|---|---|---|
| 0 | D-001 | frontend | 一括更新 | 8.793852 | False | 0.0 |
| 1 | D-002 | API | rolling | 12.297614 | False | 0.0 |
| 2 | D-003 | frontend | rolling | 11.813427 | False | 0.0 |
No.071:Dockerfileを作成する
実務での意味
コンテナイメージは、アプリと実行依存をまとめた配布単位です。Dockerfileを版管理すると、「誰のPCで作ったか」ではなく同じ手順から同じ環境を再構築できます。ベースイメージの固定、非root実行、不要ファイルの除外も品質要件です。
分析・モデル化の考え方
レイヤーキャッシュは変更の少ない依存関係を先に配置して再利用します。multi-stage buildはビルド用ツールを最終イメージへ持ち込まず、転送時間と攻撃面を減らします。ここでは方式別の容量と時間を比較します。
Pythonで確認する
dockerfile = """FROM python:3.13-slim AS runtime
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app ./app
USER 10001
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
"""
summary = builds.groupby("strategy").agg(平均容量MB=("image_mb","mean"), 平均build秒=("build_sec","mean")).round(1)
display(summary)
ax = summary["平均容量MB"].plot(kind="bar", color=["#607d8b","#1976d2"])
ax.set_title("Docker build方式別の平均イメージ容量"); ax.set_xlabel("build方式"); ax.set_ylabel("容量 (MB)"); ax.grid(axis="y", alpha=.3); plt.tight_layout(); plt.show()
print(dockerfile)
| 平均容量MB | 平均build秒 | |
|---|---|---|
| strategy | ||
| multi-stage | 190.6 | 122.1 |
| 単一stage | 536.6 | 117.7 |
FROM python:3.13-slim AS runtime
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app ./app
USER 10001
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
結果の読み取り
multi-stage相当の方式では容量が小さく、配布時間と脆弱性調査の対象を減らせます。実務では容量だけでなく、固定したベースイメージの更新頻度、SBOM、脆弱性スキャン、再現ビルドを受入条件にします。
No.072:バックエンドをコンテナ化する
実務での意味
APIコンテナは、ポート、起動コマンド、実行ユーザー、ヘルスチェック、終了シグナルを契約として持ちます。プロセスが動くだけでなく、リクエスト受付可能かをオーケストレーターへ伝える必要があります。
分析・モデル化の考え方
稼働率は で測ります。ただし生存確認と準備完了確認は分け、DB未接続のAPIへ通信を流さない設計にします。
Pythonで確認する
availability = monitor["available"].mean()
p95 = monitor["latency_ms"].quantile(.95)
api_kpi = pd.DataFrame({"KPI":["稼働率","p95応答時間"],"値":[f"{availability:.3%}",f"{p95:.1f} ms"],"暫定基準":[">= 99.9%","< 250 ms"]})
display(api_kpi)
daily = monitor.set_index("time").resample("D").agg(稼働率=("available","mean")) * 100
ax=daily.plot(marker="o",legend=False,color="#1976d2"); ax.set_title("生産進捗APIの日別稼働率"); ax.set_xlabel("日付"); ax.set_ylabel("稼働率 (%)"); ax.grid(alpha=.3); plt.tight_layout(); plt.show()
| KPI | 値 | 暫定基準 | |
|---|---|---|---|
| 0 | 稼働率 | 99.653% | >= 99.9% |
| 1 | p95応答時間 | 239.5 ms | < 250 ms |
結果の読み取り
全期間平均だけでは短時間の障害やピーク時影響を隠します。日別推移、p95応答、エラー率を併記し、/liveと/readyを分離します。基準未達なら台数追加の前に、DB待ちや重い処理をトレースします。
No.073:フロントエンドをコンテナ化する
実務での意味
React等のフロントエンドはNode.jsでビルドし、生成された静的ファイルだけをWebサーバーで配信できます。ビルド環境と実行環境を分けると、本番に開発依存を持ち込まずに済みます。
分析・モデル化の考え方
配信量は初期表示時間に影響します。圧縮後bundle容量、キャッシュヒット率、Largest Contentful Paintなどを測ります。ここでは資産分割による転送量を試算します。
Pythonで確認する
assets=pd.DataFrame({"資産":["app.js","vendor.js","styles.css","icons"],"分割前KB":[820,0,115,92],"分割後KB":[310,360,82,58],"再訪時転送KB":[310,0,0,0]})
display(assets)
totals=assets[["分割前KB","分割後KB","再訪時転送KB"]].sum()
ax=totals.plot(kind="bar",color=["#d32f2f","#1976d2","#388e3c"]); ax.set_title("フロントエンド資産の転送量比較"); ax.set_xlabel("配信方式"); ax.set_ylabel("転送量 (KB)"); ax.grid(axis="y",alpha=.3); plt.tight_layout(); plt.show()
| 資産 | 分割前KB | 分割後KB | 再訪時転送KB | |
|---|---|---|---|---|
| 0 | app.js | 820 | 310 | 310 |
| 1 | vendor.js | 0 | 360 | 0 |
| 2 | styles.css | 115 | 82 | 0 |
| 3 | icons | 92 | 58 | 0 |
結果の読み取り
分割と長期キャッシュにより再訪時の転送量を抑えられます。HTMLは短く、ハッシュ付きJS/CSSは長くキャッシュし、新旧ファイルを一定期間併存させます。画面とAPIの互換性も同時に確認します。
No.074:DBをDocker Composeで起動する
実務での意味
ComposeはAPI、画面、DBを一つの構成として起動し、研修・開発環境の再現性を高めます。depends_onは起動順を示すだけの場合があるため、DBの準備完了確認とAPI側の再試行が必要です。
分析・モデル化の考え方
サービス依存を有向グラフとして整理し、永続化が必要なDBだけvolumeを持たせます。パスワードはCompose本文へ固定せず、環境変数やsecretから渡します。
Pythonで確認する
services=pd.DataFrame({"service":["frontend","api","db"],"依存先":["api","db","なし"],"healthcheck":["HTTP /","HTTP /ready","pg_isready"],"永続volume":[False,False,True],"公開port":[True,True,False]})
display(services)
checks=pd.DataFrame({"検査":["healthcheckあり","DB外部非公開","DB永続化","固定passwordなし"],"判定":[services.healthcheck.ne("").all(),not services.loc[services.service.eq("db"),"公開port"].iloc[0],services.loc[services.service.eq("db"),"永続volume"].iloc[0],True]})
display(checks)
| service | 依存先 | healthcheck | 永続volume | 公開port | |
|---|---|---|---|---|---|
| 0 | frontend | api | HTTP / | False | True |
| 1 | api | db | HTTP /ready | False | True |
| 2 | db | なし | pg_isready | True | False |
| 検査 | 判定 | |
|---|---|---|
| 0 | healthcheckあり | True |
| 1 | DB外部非公開 | True |
| 2 | DB永続化 | True |
| 3 | 固定passwordなし | True |
結果の読み取り
DBを外部公開せず、永続volumeと準備完了確認を持つ構成になっています。本番はComposeをそのまま拡大するのではなく、マネージドDB、バックアップ、暗号化、接続数、マイグレーションの責任分界を決めます。
No.075:開発環境と本番環境の違いを整理する
実務での意味
開発では変更の速さと調査容易性、本番では安全性、可用性、追跡可能性を優先します。一方、ライブラリ版や基本構成まで別物にすると、本番だけで起きる不具合が増えます。
分析・モデル化の考え方
同じイメージを使い、設定だけを外部化するのが基本です。差分項目を分類し、危険な設定が本番へ混入しないか機械検査します。
Pythonで確認する
envs=pd.DataFrame({"項目":["debug","replicas","log_level","DB","TLS","自動reload"],"開発":[True,1,"DEBUG","local container",False,True],"本番":[False,3,"INFO","managed DB",True,False],"共通化":["不可","設定化","設定化","接続先のみ","不可","不可"]})
display(envs)
risk_checks={"debug無効":envs.loc[envs.項目.eq("debug"),"本番"].iloc[0] == False,"TLS有効":envs.loc[envs.項目.eq("TLS"),"本番"].iloc[0] == True,"複数replica":envs.loc[envs.項目.eq("replicas"),"本番"].iloc[0] >= 2}
display(pd.Series(risk_checks,name="本番設定検査").to_frame())
| 項目 | 開発 | 本番 | 共通化 | |
|---|---|---|---|---|
| 0 | debug | True | False | 不可 |
| 1 | replicas | 1 | 3 | 設定化 |
| 2 | log_level | DEBUG | INFO | 設定化 |
| 3 | DB | local container | managed DB | 接続先のみ |
| 4 | TLS | False | True | 不可 |
| 5 | 自動reload | True | False | 不可 |
| 本番設定検査 | |
|---|---|
| debug無効 | True |
| TLS有効 | True |
| 複数replica | True |
結果の読み取り
本番固有の安全設定を保ちながら、アプリ本体は同じイメージを使えます。環境名で大きくコード分岐するより、型付き設定と起動時検証を用い、不足・矛盾があれば起動を失敗させます。
No.076:GitHub ActionsでCIを作成する
実務での意味
CIはpushやPull Requestを契機に同じ検査を実行し、レビュー前に基本不良を検出します。重要なのはYAMLを書くことではなく、必須チェック、対象ブランチ、権限、キャッシュ、失敗通知を定義することです。
分析・モデル化の考え方
CI時間の平均だけでなくp95、失敗率、待ち時間を確認します。権限は最小化し、外部からのPRで本番secretへアクセスさせません。
Pythonで確認する
ci_summary=ci.groupby("test_level").agg(実行数=("run_id","size"),平均分=("duration_min","mean"),p95分=("duration_min",lambda x:x.quantile(.95)),欠陥検出率=("defect_found","mean")).round(3)
display(ci_summary)
ax=ci_summary["p95分"].plot(kind="bar",color="#1976d2"); ax.set_title("CI構成別のp95実行時間"); ax.set_xlabel("検査構成"); ax.set_ylabel("p95時間 (分)"); ax.grid(axis="y",alpha=.3); plt.tight_layout(); plt.show()
| 実行数 | 平均分 | p95分 | 欠陥検出率 | |
|---|---|---|---|---|
| test_level | ||||
| lint・unit | 42 | 3.169 | 4.806 | 0.119 |
| unit・API | 72 | 7.629 | 9.219 | 0.167 |
| 全テスト | 46 | 15.057 | 16.737 | 0.283 |
結果の読み取り
全テストは検出率が高い一方、所要時間も長くなります。PRでは高速検査を必須化し、重いE2Eは並列化または定期実行するなど、フィードバック速度と検出力を設計します。
No.077:テストを自動実行する
実務での意味
自動実行は「テストがある」状態を「変更のたびに検査される」状態へ変えます。単体、API、統合、E2Eは検出できる欠陥と時間が異なるため、層を組み合わせます。
分析・モデル化の考え方
期待損失を とし、各テストが減らす欠陥流出確率と実行時間を比較します。ここでは架空の欠陥候補100件に対する検出数を示します。
Pythonで確認する
tests=pd.DataFrame({"層":["lint・型","unit","API","E2E"],"実行分":[1.2,3.8,6.5,14.0],"候補欠陥":[18,35,29,18],"検出率":[.94,.86,.79,.72]})
tests["期待検出数"]=(tests["候補欠陥"]*tests["検出率"]).round(1); tests["1分当たり検出"]=(tests["期待検出数"]/tests["実行分"]).round(2)
display(tests)
ax=tests.set_index("層")["1分当たり検出"].plot(kind="bar",color="#388e3c"); ax.set_title("テスト層別の時間当たり期待検出数"); ax.set_xlabel("テスト層"); ax.set_ylabel("期待検出数 / 分"); ax.grid(axis="y",alpha=.3); plt.tight_layout(); plt.show()
| 層 | 実行分 | 候補欠陥 | 検出率 | 期待検出数 | 1分当たり検出 | |
|---|---|---|---|---|---|---|
| 0 | lint・型 | 1.2 | 18 | 0.94 | 16.9 | 14.08 |
| 1 | unit | 3.8 | 35 | 0.86 | 30.1 | 7.92 |
| 2 | API | 6.5 | 29 | 0.79 | 22.9 | 3.52 |
| 3 | E2E | 14.0 | 18 | 0.72 | 13.0 | 0.93 |
結果の読み取り
高速な静的検査とunitを入口に置き、API・E2Eで境界と主要業務を守る構成が現実的です。効率だけでE2Eを削らず、「実績登録から進捗反映」のような停止影響の大きい経路を少数厳選します。
No.078:クラウドにAPIをデプロイする
実務での意味
APIデプロイはイメージを起動するだけでなく、ヘルスチェック、段階的切替、DB migration、監視、ロールバックを含む業務変更です。生産中の更新では停止時間を特に管理します。
分析・モデル化の考え方
代表KPIはデプロイ頻度、変更リードタイム、変更失敗率、復旧時間です。ここでは一括更新とrolling更新を比較します。
Pythonで確認する
dep=deploys[deploys.service.eq("API")].groupby("strategy").agg(回数=("deploy_id","size"),平均所要分=("duration_min","mean"),変更失敗率=("failed","mean"),平均rollback分=("rollback_min",lambda x:x[x>0].mean())).round(2)
display(dep)
ax=(dep["変更失敗率"]*100).plot(kind="bar",color=["#d32f2f","#1976d2"]); ax.set_title("APIデプロイ方式別の変更失敗率"); ax.set_xlabel("更新方式"); ax.set_ylabel("変更失敗率 (%)"); ax.grid(axis="y",alpha=.3); plt.tight_layout(); plt.show()
| 回数 | 平均所要分 | 変更失敗率 | 平均rollback分 | |
|---|---|---|---|---|
| strategy | ||||
| rolling | 30 | 12.05 | 0.07 | 10.20 |
| 一括更新 | 19 | 7.63 | 0.37 | 4.61 |
結果の読み取り
rolling更新は所要時間が長くても失敗影響を限定できる傾向です。ただし教材上の架空値なので方式の優劣を断定せず、本番では少量トラフィックでの確認、旧版互換DB変更、自動ロールバック条件を整えます。
No.079:フロントエンドをデプロイする
実務での意味
画面はCDN等へ静的資産を配置できますが、ブラウザキャッシュに旧版が残ります。ファイル名へ内容ハッシュを付け、HTMLと資産のキャッシュ期間を分けると安全に切り替えられます。
分析・モデル化の考え方
更新直後の利用者を、HTML新版率と資産キャッシュヒット率でシミュレーションします。旧HTMLと削除済み資産の組合せを避けるため、新旧資産を併存させます。
Pythonで確認する
minutes=np.arange(0,61,5); rollout=pd.DataFrame({"更新後分":minutes}); rollout["新版HTML率"]=(1-np.exp(-minutes/12))*100; rollout["新版asset率"]=(1-np.exp(-minutes/20))*100
display(rollout.iloc[[0,3,6,12]].round(1))
ax=rollout.plot(x="更新後分",y=["新版HTML率","新版asset率"],marker="o"); ax.set_title("フロントエンド新版の浸透シミュレーション"); ax.set_xlabel("デプロイ後 (分)"); ax.set_ylabel("新版利用率 (%)"); ax.grid(alpha=.3); plt.tight_layout(); plt.show()
| 更新後分 | 新版HTML率 | 新版asset率 | |
|---|---|---|---|
| 0 | 0 | 0.0 | 0.0 |
| 3 | 15 | 71.3 | 52.8 |
| 6 | 30 | 91.8 | 77.7 |
| 12 | 60 | 99.3 | 95.0 |
結果の読み取り
HTMLとassetの浸透速度は一致しません。新旧API互換性を保ち、旧資産を即時削除しないことが重要です。エラー率を版番号で監視し、問題時はHTML参照先を前版へ戻せるようにします。
No.080:環境変数とシークレットを安全に管理する
実務での意味
接続先やログレベルは環境変数へ、DBパスワードやAPIキーはsecret管理基盤へ分離します。秘密は暗号化保存だけでなく、閲覧権限、ログへの非表示、ローテーション、失効まで管理します。
分析・モデル化の考え方
曝露リスクを単純化して と置き、保管方法を比較します。数値は優先順位付け用の仮定であり、正式なリスク評価ではありません。
Pythonで確認する
secret_risk=pd.DataFrame({"保管方法":["ソース直書き","共有.env","CI secret","クラウドsecret管理"],"曝露確率":[.35,.18,.06,.025],"影響度":[5,5,5,5],"有効期間日":[365,180,90,30]}); secret_risk["相対リスク"]=(secret_risk["曝露確率"]*secret_risk["影響度"]*secret_risk["有効期間日"]).round(1)
display(secret_risk)
ax=secret_risk.set_index("保管方法")["相対リスク"].plot(kind="bar",color="#7b1fa2"); ax.set_title("Secret保管・更新方式の相対リスク試算"); ax.set_xlabel("保管方法"); ax.set_ylabel("相対リスク (仮定値)"); ax.grid(axis="y",alpha=.3); plt.tight_layout(); plt.show()
| 保管方法 | 曝露確率 | 影響度 | 有効期間日 | 相対リスク | |
|---|---|---|---|---|---|
| 0 | ソース直書き | 0.350 | 5 | 365 | 638.8 |
| 1 | 共有.env | 0.180 | 5 | 180 | 162.0 |
| 2 | CI secret | 0.060 | 5 | 90 | 27.0 |
| 3 | クラウドsecret管理 | 0.025 | 5 | 30 | 3.8 |
結果の読み取り
集中管理と短い有効期間で相対リスクを下げられます。ただし管理基盤へ移すだけでは不十分です。サービス単位の権限、定期更新、退職・委託終了時の失効、漏えい時の緊急交換、ログのマスキングを運用手順にします。
対象ノックを通して見える実務上の示唆
- コンテナは再現可能性の契約である:Dockerfile、固定版、非root実行、healthcheckまで含めてレビューする必要があります。
- 環境差分はコードではなく設定として管理する:同一成果物を昇格させ、本番固有の安全設定を起動時に検証します。
- CI/CDは速度と安定性を同時に測る:ビルド時間だけでなく、変更失敗率、復旧時間、欠陥流出を継続観測します。
- デプロイは互換性を保つ段階的な業務変更である:API・DB・画面の新旧が一時的に混在する前提で設計します。
- Secret管理は保管場所ではなくライフサイクルである:発行、利用、監査、更新、失効、緊急交換までを責任分担します。
実務導入する場合に必要なこと
- 業務影響の定義:停止許容時間、繁忙時間帯、復旧目標(RTO)、データ損失許容(RPO)、リリース承認者を決める
- 成果物の供給網管理:依存版固定、SBOM、イメージ署名、脆弱性検査、信頼できるregistryを整える
- 段階的リリース:開発・検証・本番への昇格、承認、migration、healthcheck、rollbackを自動化する
- 観測可能性:ログ、メトリクス、トレースへ版番号と相関IDを付け、アラートから原因調査へつなげる
- セキュリティ運用:最小権限、secret更新、監査ログ、インシデント対応訓練を継続する
最初から大規模基盤を目指すのではなく、重要なAPI一つを対象に「PR検査→イメージ作成→検証環境→承認→本番→監視→ロールバック」の一本道を作り、実測KPIから改善します。
まとめ
No.071〜No.080では、生産進捗APIと現場画面を安全に届け続けるため、Dockerfileからバックエンド・フロントエンドのコンテナ化、Compose、環境分離、GitHub Actions、テスト自動化、クラウドデプロイ、secret管理までを確認しました。
価値はDockerやクラウドを導入すること自体ではありません。同じ成果物を再現し、変更を早く検査し、影響を限定して公開し、異常時に根拠を持って戻せることです。製造現場では停止の時刻と影響が重要なため、技術KPIを生産計画・品質・保全の判断と結び付けて運用します。
法人向けのご相談
数理工房では、製造業向けWebシステム・AI/APIのコンテナ化、CI/CD設計、クラウド移行、テスト・監視・運用KPI設計、内製化研修まで支援しています。
「担当者PCで動く分析を業務システム化したい」「リリースの属人化を解消したい」「工場稼働への影響を抑えてクラウドへ移行したい」といった構想段階から、現場制約を踏まえてご相談いただけます。
📩 お問い合わせ: surikobo.co.jp/contact まずはお気軽にご相談ください。