LFCS — Linux Foundation認定システム管理者
systemd タイマーから podman まで — サービスとネットワークを実際に作る
目標
systemdのサービスとタイマー、nginxのリバースプロキシ、iptablesのDNAT/DROP、dummyインターフェースの上のブリッジ・ボンドとnetplan、chronyとタイムゾーン、gitのマージ、レジストリなしのpodmanイメージ、永続的なsysctl、負荷の指標。LFCSのOperationsとNetworkingドメインで、Podが塞いでいたことを、Ubuntu 24.04のVMで実際に実行します。
なぜ重要なのか
LFCSは「説明」ではなく「結果」を見ます。ユニットファイルをうまく書いても、systemctl is-enabledがdisabledなら0点で、nginxの設定が文法的に合っていても、デフォルトのサイトが80を掴んでいれば、プロキシは動作しません。このラボの採点ツールは、ファイルだけを見ず、systemctl is-active、curl、iptables -S、/sys/class/net、sysctl -n、podman inspectで、今のシステムの状態を見ます。
3つの原則が繰り返されます。1つ目、enableとstartは別の問いです(起動時に有効にするか / 今有効になっているか)。2つ目、ファイルを書いたことと、カーネルやデーモンが知っていることは別です(sysctl --system、daemon-reload、nginx reload)。3つ目、戻る道を残します。iptablesのINPUTのデフォルトポリシーをDROPに変えたり、8899・22を塞いだりすると、採点エージェントが切れて、次のステップから原因不明の失敗になります。
セッションは60分で始まり、最大180分まで延長でき、VMはセッションが終わると消えます。このラボは85分で設定されているので、開始するときに一度延長しておいてください。
ステップ
/usr/local/bin/lfcs-heartbeat.shが現在時刻を/var/log/lfcs-heartbeat.logに1行追記するようにし、これをType=oneshotのlfcs-heartbeat.serviceと、1分ごとにそのサービスを呼び出すlfcs-heartbeat.timerとして、/etc/systemd/system/に作成してください。タイマーはenableとstartの両方がされている必要があり、サービスを一度直接起動して、ログに最低1行ある必要があります。/srv/backend/index.htmlにbackend okという文言を入れ、python3 -m http.server 8000 --directory /srv/backendをsystemdサービスlfcs-backend.serviceにして、enable・startしてください。そのあと、nginxがポート80でこのバックエンドへリバースプロキシするように、/etc/nginx/sites-available/lfcs-proxyを作成して有効化(デフォルトのサイトは無効化)してください。curl http://127.0.0.1/がbackend okを返す必要があります。- iptablesで2つのルールを入れてください。(1)
natテーブルのPREROUTINGで、tcp 8080に入ってきたものを80へ送るDNAT(またはREDIRECT)。(2)filterテーブルのINPUTで、tcp 9999を、すべてのインターフェースでDROP。INPUTのデフォルトポリシーはACCEPTのままにして、8899と22には手を触れないでください(採点エージェントとSSH)。最後に、iptables-saveの出力を/root/lfcs/net/rules.v4に保存してください。 - ダミーインターフェース
dummy0、dummy1、dummy2を作成してください。ブリッジbr0にdummy0を入れ、br0に192.0.2.1/24を付けてください。ボンドbond0をactive-backupモードで作成して、dummy1とdummy2を入れてください(eth0のような実際のインターフェースには触れません)。同じ構成をnetplanの文法で/root/lfcs/net/99-lab.yamlに書きますが、適用はしないでください。 /etc/chrony/chrony.confにserver time.cloudflare.com iburstの行を追加して、chronyを再起動してください。タイムゾーンをAsia/Seoulに変更してください。そして、chronyc sourcesを/root/lfcs/time/sources.txtに、chronyc trackingを/root/lfcs/time/tracking.txtに、timedatectlを/root/lfcs/time/timedatectl.txtに保存してください。(このVMはUDP 123が塞がれているので、同期自体はできない場合があります。設定と状態が採点の対象です。)/root/lfcs/repoにgitリポジトリを作成し(デフォルトブランチはmain)、README.mdをコミットしてください。ブランチfeatureでfeature.txtを追加してコミットしたあと、mainに戻って、マージコミットが残るように(--no-ff)マージしてください。作業ツリーはクリーンである必要があります。/bin/busybox(busybox-static)1つだけを含むルートファイルシステムをtarにまとめて、podman importでlocalhost/lfcs-busybox:1イメージを作成してください(レジストリからは取得しません)。そのイメージで、名前がbb1のコンテナを--network noneで実行して、hello-from-busyboxを出力させ、その出力を/root/lfcs/bb/run.txtに保存してください。コンテナは削除しないでください(終了コードが0である必要があります)。/etc/sysctl.d/90-lfcs.confに、net.ipv4.ip_forward = 1、vm.swappiness = 10、net.core.somaxconn = 1024の3つの値を書き、今のカーネルにも適用してください。適用後、3つの値をsysctlで読み取って、/root/lfcs/sysctl/runtime.txtに保存してください。uptimeの出力を/root/lfcs/perf/uptime.txtに、top -bn1の先頭15行を/root/lfcs/perf/top.txtに保存してください。そして、/root/lfcs/perf/load.txtを2行で作成してください:NPROC=<이 VM 의 CPU 개수>、LOAD1=<1분 로드 평균(/proc/loadavg 첫 값)>(プレースホルダーは順に、このVMのCPUの数、/proc/loadavgの最初の値である1分間のロードアベレージです)。
参考
- ユニットを書いたり変更したりしたあとは、
systemctl daemon-reload。systemctl list-timers --allで、タイマーが認識されているかを見ます。 - nginx:
nginx -t→systemctl reload nginx。sites-enabled/defaultを削除しないと、default_serverが2つになります。 - iptables:
iptables -t nat -S PREROUTING、iptables -S INPUT。-Cで、ルールがすでにあるかを尋ねられます。 - ボンドのスレーブは、
ip link set dummy1 downのあとにmaster bond0。モードの確認はcat /sys/class/net/bond0/bonding/mode。 - このVMは、外へはDNSと80/443だけが開いています。
chronyc sourcesの?は正常で、podmanはレジストリから取得しません。 - よくある間違い1: タイマーを
startだけして、enableを書き漏らす場合です。再起動すると消えます。 - よくある間違い2:
lvextendのように、sysctlもファイルだけを書いて、sysctl --systemを抜かし、カーネルの値がそのままの場合です。 - よくある間違い3: マージをfast-forwardで行って、マージコミットがない場合です。
サービスユニットとタイマー
/usr/local/bin/lfcs-heartbeat.shが現在時刻を/var/log/lfcs-heartbeat.logに1行追記するようにし、これをType=oneshotのlfcs-heartbeat.serviceと、1分ごとにそのサービスを呼び出すlfcs-heartbeat.timerとして、/etc/systemd/system/に作成してください。タイマーはenableとstartの両方がされている必要があり、サービスを一度直接起動して、ログに最低1行ある必要があります。
タイマーには、[Timer]のOnBootSec/OnUnitActiveSecと、[Install]のWantedBy=timers.targetが必要です。ユニットファイルを書いたあとは、daemon-reloadが先です。is-enabledとis-activeは、別の問いです。
nginxのリバースプロキシ
/srv/backend/index.htmlにbackend okという文言を入れ、python3 -m http.server 8000 --directory /srv/backendをsystemdサービスlfcs-backend.serviceにして、enable・startしてください。そのあと、nginxがポート80でこのバックエンドへリバースプロキシするように、/etc/nginx/sites-available/lfcs-proxyを作成して有効化(デフォルトのサイトは無効化)してください。curl http://127.0.0.1/がbackend okを返す必要があります。
serverブロックにlisten 80 default_serverと、location / { proxy_pass http://127.0.0.1:8000; }が核心です。sites-enabled/defaultを削除しないと、2つのdefault_serverが衝突します。nginx -tで確認してから、reloadします。
iptablesでDNATとDROP
iptablesで2つのルールを入れてください。(1)natテーブルのPREROUTINGで、tcp 8080に入ってきたものを80へ送るDNAT(またはREDIRECT)。(2)filterテーブルのINPUTで、tcp 9999を、すべてのインターフェースでDROP。INPUTのデフォルトポリシーはACCEPTのままにして、8899と22には手を触れないでください(採点エージェントとSSH)。最後に、iptables-saveの出力を/root/lfcs/net/rules.v4に保存してください。
DNATは、-t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination <このVMのIP>:80(または-j REDIRECT --to-ports 80)です。DROPは、-A INPUT -p tcp --dport 9999 -j DROPです。自分自身に送るパケットはPREROUTINGを通らないので、DNATはルールの存在で、DROPは127.0.0.1:9999への接続が「拒否」ではなく「タイムアウト」になるかで、確認します。
dummyの上のブリッジとボンド、そしてnetplan
ダミーインターフェースdummy0、dummy1、dummy2を作成してください。ブリッジbr0にdummy0を入れ、br0に192.0.2.1/24を付けてください。ボンドbond0をactive-backupモードで作成して、dummy1とdummy2を入れてください(eth0のような実際のインターフェースには触れません)。同じ構成をnetplanの文法で/root/lfcs/net/99-lab.yamlに書きますが、適用はしないでください。
ip link add <名前> type dummy|bridge|bond。スレーブは、ip link set master 。ボンドのスレーブは、入れる前にdownである必要があります。ボンドのモードは、/sys/class/net/bond0/bonding/modeで確認します。
chronyとタイムゾーン
/etc/chrony/chrony.confにserver time.cloudflare.com iburstの行を追加して、chronyを再起動してください。タイムゾーンをAsia/Seoulに変更してください。そして、chronyc sourcesを/root/lfcs/time/sources.txtに、chronyc trackingを/root/lfcs/time/tracking.txtに、timedatectlを/root/lfcs/time/timedatectl.txtに保存してください。(このVMはUDP 123が塞がれているので、同期自体はできない場合があります。設定と状態が採点の対象です。)
timedatectl set-timezone Asia/Seoul。Ubuntuのchronyのサービス名はchronyです。chronyc sourcesの最初の列が?なのは、まだ応答を受け取れていないという意味で、ここでは正常です。
git基礎: ブランチとマージ
/root/lfcs/repoにgitリポジトリを作成し(デフォルトブランチはmain)、README.mdをコミットしてください。ブランチfeatureでfeature.txtを追加してコミットしたあと、mainに戻って、マージコミットが残るように(--no-ff)マージしてください。作業ツリーはクリーンである必要があります。
git init -b main。コミットするには、user.name/user.emailが必要です(git config)。マージコミットは、git log --mergesで見えます。fast-forwardでマージすると、マージコミットがありません。
レジストリなしでpodmanイメージを作って実行
/bin/busybox(busybox-static)1つだけを含むルートファイルシステムをtarにまとめて、podman importでlocalhost/lfcs-busybox:1イメージを作成してください(レジストリからは取得しません)。そのイメージで、名前がbb1のコンテナを--network noneで実行して、hello-from-busyboxを出力させ、その出力を/root/lfcs/bb/run.txtに保存してください。コンテナは削除しないでください(終了コードが0である必要があります)。
rootfs/bin/busyboxをコピーして、tar -C rootfs -cf bb.tar . → podman import bb.tar localhost/lfcs-busybox:1。実行は、podman run --name bb1 --network none localhost/lfcs-busybox:1 /bin/busybox echo hello-from-busybox。このラボのpodmanはrootで動きます。
カーネルパラメーターを永続的に
/etc/sysctl.d/90-lfcs.confに、net.ipv4.ip_forward = 1、vm.swappiness = 10、net.core.somaxconn = 1024の3つの値を書き、今のカーネルにも適用してください。適用後、3つの値をsysctlで読み取って、/root/lfcs/sysctl/runtime.txtに保存してください。
sysctl --systemが/etc/sysctl.d/*.confを順に読み込んで適用します。ファイルだけを書くと、再起動までカーネルは知りません。sysctl -n <キー>で、今の値を見ます。
負荷指標を残す
uptimeの出力を/root/lfcs/perf/uptime.txtに、top -bn1の先頭15行を/root/lfcs/perf/top.txtに保存してください。そして、/root/lfcs/perf/load.txtを2行で作成してください: NPROC=<이 VM 의 CPU 개수>、LOAD1=<1분 로드 평균(/proc/loadavg 첫 값)>(プレースホルダーは順に、このVMのCPUの数、/proc/loadavgの最初の値である1分間のロードアベレージです)。
top -bn1はバッチモードで1回だけ出力します。/proc/loadavgの最初のフィールドが1分平均で、CPUの数はnprocです。ロードアベレージは、CPUの数と比べて初めて意味があります。