導入(問題提起)
開発はゴールではなくスタートです。Webシステム開発を終えて公開しても、保守運用が弱いと「気づけない障害」「戻せない変更」「守れないデータ」によって事業が止まります。特にExcel Web化で業務システムを内製・半内製した小さな会社では、担当者の離任や多忙が直撃リスクです。本記事は、最小のチームでも回る「紙1枚の運用設計書」に落とし込む実務手順を、Pythonベースの小規模Webアプリ(Bottle/FastAPI+SQLite/MySQL)を前提に解説します。
課題の詳細説明
現場でよくある“穴”を具体化します。
- バックアップは取っているが、復元(リストア)訓練をしていないため、いざという時に戻せない。
- 障害連絡が担当者の個人チャットに流れ、休日や退職で連絡不能になる。
- 権限が増え続け「誰が何に触れるか」を説明できない。監査ログも散在。
- 仕様変更や小修正が口頭で実施され、変更履歴・ロールバック手順がない。
- 表示速度やエラー率の計測がなく、「遅い」「落ちる」の主観議論になる。
- Excel運用からWeb化したのに、操作マニュアルやFAQが未整備でサポートが属人化。
これらは、役割と閾値、手順と記録、そして定例レビューという“運用の三点セット”が欠けていることが原因です。
解決方法(運用設計書の骨子)
運用設計書はA4一枚からで十分です。次の章立てを最小セットとして明文化します。
- 組織体制と連絡先
- 稼働監視と通知
- データ保護(バックアップ/リストア)
- 変更管理(リリース/ロールバック)
- セキュリティ(権限/RBAC/監査/パスワード)
- サポート/ナレッジ
- 役割: オーナー(最終責任)/運用担当/代行/開発(内製or外注) - 連絡: 平時の窓口(メール/Chat)/緊急連絡(電話)/ベンダー窓口 - 可用性: 対応時間帯/休日対応方針/エスカレーション基準
- 監視項目: 死活/応答時間/エラー率/バッチ結果/ディスク残容量 - 閾値: 警告/致命の数値基準と継続時間 - 通知: チャネル(Chat/メール)/誰に/夜間の扱い
- 対象: DB(本番/ステージング)/添付ファイル/設定 - 世代: 日次N世代+週次M世代 - リストア: 四半期に一度の訓練(手順書・所要時間の記録)
- フロー: 申請→レビュー→リリース→検証→クローズ - ブルーグリーンやDBマイグレーションの手順/チェックリスト - 緊急変更の扱いと翌営業日の事後承認
- 最小権限/RBACロール設計/四半期の権限棚卸 - 監査ログの不可逆保存と検索手順 - 秘匿情報(APIキー/DBパス)の管理とローテーション
- FAQ/操作手順書/障害対応テンプレート - 週次/隔週の運用レビューでの改善サイクル
具体例(ケース別の落とし穴と処方箋)
- 小売の在庫システム:深夜バッチが稀に失敗し翌朝在庫が0表示→「ジョブ結果を監視し、失敗時はメール+チャット通知。手動再実行ボタンと手順を用意」。
- 予約システム:カレンダー連携のWebhookがタイムアウト→「リトライとデッドレタキュー、手動再配送の管理画面を整備」。
- 請求管理:月末にPDF出力が集中して応答低下→「事前レンダリングのスケジューラ、ピークシフト設定、CPU/IO監視の閾値調整」。
- 社内ポータル:管理者が誤って全社員に閲覧権限を付与→「RBACのロール上限と二段承認、権限変更の監査通知」。
それぞれの根っこは「計測できていない」「戻せない」「誰が何をしたか不明」です。運用設計書に“測る/戻す/記録する”を埋め込むのが解決です。
技術的な解説(Python×小規模Webシステム実装例)
ここではBottle(またはFastAPI)+SQLite/MySQLで、監視・バックアップ・RBAC・監査の最小構成を例示します。
1) データモデル(抜粋)
-- 監査ログ:不可逆追記。誰が・いつ・何を・どのデータに
CREATE TABLE audit_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
actor TEXT NOT NULL,
action TEXT NOT NULL,
entity TEXT NOT NULL,
entity_id TEXT,
detail TEXT
);
-- RBACロールと付与
CREATE TABLE roles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL
);
CREATE TABLE user_roles (
user_id INTEGER NOT NULL,
role_id INTEGER NOT NULL,
UNIQUE(user_id, role_id)
);
-- バッチ実行履歴(成功/失敗/所要時間)
CREATE TABLE job_runs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
job_name TEXT NOT NULL,
started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
finished_at TIMESTAMP,
status TEXT CHECK(status IN ('success','failure')),
message TEXT
);
2) 監査の記録と通知(Bottleの例)
from bottle import Bottle, request, response
import sqlite3, json, time
app = Bottle()
def db():
return sqlite3.connect('app.db')
def audit(actor, action, entity, entity_id=None, detail=None):
con = db(); cur = con.cursor()
cur.execute('INSERT INTO audit_logs(actor,action,entity,entity_id,detail) VALUES (?,?,?,?,?)',
(actor, action, entity, entity_id, json.dumps(detail or {})))
con.commit(); con.close()
@app.post('/admin/users/<uid:int>/roles')
def add_role(uid):
actor = request.headers.get('X-User','system')
role = request.json.get('role')
con = db(); cur = con.cursor()
cur.execute('INSERT OR IGNORE INTO user_roles(user_id, role_id) SELECT ?, id FROM roles WHERE name=?', (uid, role))
con.commit(); con.close()
audit(actor, 'grant_role', 'user', str(uid), {'role': role})
# 重要な権限変更は通知(メール/Chat)にフック
return {'ok': True}
3) バックアップ/リストア運用(疑似コード)
import shutil, datetime as dt, subprocess
def backup_sqlite(db_path, dst_dir):
ts = dt.datetime.now().strftime('%Y%m%d-%H%M')
shutil.copy2(db_path, f'{dst_dir}/app-{ts}.sqlite3')
def backup_mysql(dsn, dst_dir):
ts = dt.datetime.now().strftime('%Y%m%d-%H%M')
subprocess.run(['mysqldump', dsn, '--single-transaction', f'> {dst_dir}/app-{ts}.sql'], shell=True, check=True)
def restore_sqlite(src, db_path):
shutil.copy2(src, db_path)
4) 監視(アプリ内ヘルスチェック+ジョブ監視)
@app.get('/healthz')
def healthz():
t0 = time.time()
con = db(); con.execute('SELECT 1'); con.close()
latency_ms = int((time.time()-t0)*1000)
status = 'ok' if latency_ms < 300 else 'degraded'
return {'status': status, 'db_latency_ms': latency_ms}
@app.post('/jobs/<name>/done')
def job_done(name):
payload = request.json or {}
con = db(); cur = con.cursor()
cur.execute('INSERT INTO job_runs(job_name, finished_at, status, message) VALUES (?, CURRENT_TIMESTAMP, ?, ?)',
(name, payload.get('status','success'), payload.get('message','')))
con.commit(); con.close()
return {'ok': True}
導入の流れ(最短4週間プラン)
- 週1の運用レビューを設定(出席者・議事録・改善タスク管理を確立)
- 監視の最小セットを実装(/healthz・エラー率・バッチ失敗通知)
- バックアップの世代管理と復元訓練を実施(手順書と所要時間を記録)
- RBACの棚卸と監査ログの可視化(検索/CSV出力)
- 変更管理の申請→レビュー→リリース→ロールバック手順を紙1枚で定義
- “数値で見る”運用ダッシュボードをトップリンクに設置(運用KPI:エラー率、MTTR、失敗バッチ、復元所要時間)
まとめ
- 運用は「人」に寄せず「仕組み」に寄せる。役割・閾値・手順を紙1枚に落とすことが出発点。
- Pythonで最小の業務システムを作るなら、監査とバックアップ、ヘルスチェックを最初から入れると後が楽。
- Excel Web化後こそ、数字で回す保守運用へ。計測→改善→定着のサイクルを週次でまわす。
問い合わせ導線
保守運用の立ち上げ・改善の伴走支援はお問い合わせへ。Webシステム開発/業務システムの安定運用をご一緒に設計します。
Webシステム開発のご相談は monou まで