はじめに
新しいLinuxサーバー(特に最小構成のコンテナ・VM)を立ち上げてアプリの開発環境を整える際、毎回のように出会う小さなつまずきがあります。本記事では、その中でも特によく踏みがちな4つのポイントをまとめます。
Python venv(仮想環境)の基礎と落とし穴
venvとは
Pythonのvenvは、プロジェクトごとに独立したパッケージ環境を作るための標準機能です。システム全体にパッケージをインストールすると、プロジェクトAが必要とするバージョンとプロジェクトBが必要とするバージョンが衝突することがありますが、venvを使えばプロジェクトごとに独立した環境を持てるため、この問題を避けられます。
python3 -m venv venv # venvディレクトリに仮想環境を作成
source venv/bin/activate # 仮想環境を有効化
source venv/bin/activateを実行すると、プロンプトの先頭に(venv)が表示され、以降pip installでインストールしたパッケージやコマンド(例:alembic、uvicorn)はこの仮想環境の中だけで有効になります。
よくある落とし穴:有効化が切れている
venvの有効化は、そのシェルセッション限定の状態です。次のような操作をすると、有効化が解除されます。
- SSHのコンソールを閉じて、再度ログインし直す
- 別のターミナルタブ・別のSSHセッションを開く
有効化が切れた状態で、venv内にインストールしたコマンドを実行するとcommand not foundになります。
$ alembic upgrade head
bash: alembic: command not found
このエラーが出たら、まずsource venv/bin/activateを再実行し、プロンプトに(venv)が付いているかを確認します。
systemdのExecStartではsourceが使えない
venvで動かしているアプリをsystemdサービス化する際、次のようにExecStartにsourceコマンドを含めても動きません。
# ❌ 動かない例
ExecStart=source venv/bin/activate && uvicorn app.main:app
systemdのExecStartはシェルを介さず直接プログラムを実行するため、シェル特有の構文(sourceや&&)は使えません。venv内の実行ファイルをフルパスで直接指定する必要があります。
# ✅ 正しい例
ExecStart=/opt/myapp/venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000
ロケール設定と文字化け
Debian系の最小構成テンプレートは、デフォルトでLANG=C(いわゆる「Cロケール」、多言語対応なしの最小設定)のままになっていることがあります。この状態でnano等を使って設定ファイルに日本語を入力すると、正しく表示・保存できず文字化けすることがあります。
apt install -y locales
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen
locale-gen
update-locale LANG=en_US.UTF-8
/etc/locale.genに必要なロケールを追記し、locale-genでそれをシステムにコンパイルしてから、update-localeでデフォルトのロケールとして設定する、という流れです。設定後は一度ログアウト・再ログインし、localeコマンドでLANG=en_US.UTF-8になっていることを確認します。日本語を扱うコンテナでは、構築の初期段階でこの設定を済ませておくと後々のトラブルを避けられます。
最小構成テンプレートでのcurl未導入
最小構成のコンテナテンプレートには、curlのような一見「当然入っていそうな」コマンドが含まれていないことがあります。
$ curl http://localhost:8000/health
bash: curl: command not found
このエラーは、Python仮想環境(venv)の有効化状態とは無関係です。venvが管理するのはPython関連の実行ファイル(pip、python、alembic等)の参照先だけであり、curlのようなOSレベルのコマンドには影響しません。
apt install -y curl
構築手順書やセットアップスクリプトの初期パッケージ一覧に、あらかじめcurlを含めておくと、動作確認のたびにこのエラーで手が止まることを防げます。
ポート53の競合(systemd-resolved)
DNSサーバーソフトウェアを自前で構築する際、インストール直後に「ポート53は既に使用されている」というエラーに遭遇することがあります。
多くのDebian/Ubuntu系ディストリビューションでは、systemd-resolvedというサービスが、ローカルの127.0.0.53:53でDNSスタブリスナーを立てています。これは、OS自体が名前解決を行うための仕組みですが、自前のDNSサーバーソフトウェアが同じポート53をバインドしようとすると競合します。
mkdir -p /etc/systemd/resolved.conf.d
cat <<EOF > /etc/systemd/resolved.conf.d/no-stub.conf
[Resolve]
DNSStubListener=no
EOF
systemctl restart systemd-resolved
DNSStubListener=noを設定することで、systemd-resolved自体は名前解決の設定管理機能を保ったまま、ポート53のリッスンだけを無効化できます。なお、最小構成のコンテナテンプレートによっては、そもそもsystemd-resolved自体が含まれておらず、この対策自体が不要な場合もあります(systemctl restart systemd-resolvedが「ユニットが見つからない」というエラーになった場合は、対応不要と判断できます)。
まとめ
| 現象 | 原因 | 対処 |
|---|---|---|
venv内コマンドがcommand not found
|
別セッション等でvenvの有効化が切れている |
source venv/bin/activateを再実行 |
| systemdサービスが起動しない |
ExecStartにシェル構文(source等)を書いている |
venv内の実行ファイルをフルパス指定する |
| 日本語入力が文字化けする |
LANG=Cのまま |
locale-genでUTF-8ロケールを生成・設定する |
curl: command not found |
最小構成テンプレートに含まれていない | apt install -y curl |
| DNSサーバーのポート53が使えない |
systemd-resolvedのスタブリスナーと競合 |
DNSStubListener=noを設定する |