Sebuah pod GPU (Jupyter) saya terus masuk CrashLoopBackOff dengan exit code 127:
/bin/bash: line 1: /usr/sbin/sshd: No such file or directory
Punca: image jupyter/minimal-notebook:aarch64-notebook-7.0.0 tidak mempunyai OpenSSH server. Arahan deployment menyambung SSH dan Jupyter dengan &&, jadi apabila sshd gagal, Jupyter pun tidak pernah bermula.
Persekitaran: node GPU arm64 (NVIDIA GB10 Blackwell), CUDA 13.0, pemacu 580.142.
Satu perangkap: arahan deployment lama mengandungi kunci awam yang kelihatan sah, tetapi ia kunci tiruan tanpa pasangan kunci peribadi di mana-mana. Jangan percaya kunci yang tertanam dalam arahan lama; pastikan anda mempunyai kunci peribadinya sebelum menguji SSH.
Reka bentuk: image tidak menyimpan sebarang kunci
Image tidak mengandungi kunci statik. Apabila container bermula, docker-entrypoint.sh menerima kelayakan daripada pengguna melalui lima sumber, mengikut apa yang platform sediakan:
| # | Sumber | Cara pengguna menggunakannya |
|---|---|---|
| 1 | Env SSH_PUBLIC_KEY | Pengguna tetapkan kunci awam semasa provision |
| 2 | Env SSH_PASSWORD | Pengguna tetapkan kata laluan semasa provision |
| 3 | Secret yang dimount di /run/secrets/ssh-public-key | Platform suntik kunci (Docker/K8s secret) |
| 4 | Kunci sedia ada dalam authorized_keys | Platform tulis sendiri; tidak dipadam |
| 5 | Fallback | Tiada apa-apa → kata laluan rawak dijana dan dicetak ke log |
Kenapa ENTRYPOINT, bukan CMD?
Banyak platform cloud menimpa CMD image semasa spawn (contohnya dengan menyuntik arahan sendiri). Jika SSH hanya dimulakan dalam CMD, ia hilang. Dengan ENTRYPOINT sebagai wrapper, sshd pasti bermula dahulu, kemudian exec "$@" menjalankan apa sahaja arahan yang platform berikan.
Container GPU juga perlu berjalan sebagai root untuk akses peranti NVIDIA; pengguna jovyan tidak mempunyai kebenaran itu.
Langkah 1 — Dockerfile
FROM jupyter/minimal-notebook:aarch64-notebook-7.0.0
USER root
# OpenSSH server dipasang semasa build, bukan semasa runtime
RUN apt-get update -qq \
&& apt-get install -y -qq --no-install-recommends openssh-server \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p /run/sshd /root/.ssh && chmod 700 /root/.ssh
COPY sshd_config /etc/ssh/sshd_config
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
EXPOSE 22 8888
USER root
# ENTRYPOINT = wrapper: sshd tetap bermula walaupun platform menimpa CMD
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
CMD ["/bin/bash", "-c", "jupyter lab --ip=0.0.0.0 --port=8888 --no-browser --allow-root --NotebookApp.token=\"${JUPYTER_TOKEN:?set JUPYTER_TOKEN}\""]
(Versi asal saya menanam token Jupyter statik dalam CMD. Di sini ia dibaca daripada JUPYTER_TOKEN supaya tiada token tertanam dalam image.)
sshd_config
Port 22
PermitRootLogin yes
PasswordAuthentication yes
PubkeyAuthentication yes
KbdInteractiveAuthentication no
AuthorizedKeysFile .ssh/authorized_keys
UsePAM no
Ini memudahkan penggunaan dalam container (login root, kata laluan dan kunci), tetapi bukan konfigurasi yang kukuh. Jika instance terdedah di internet, lebih selamat matikan PasswordAuthentication dan hanya benarkan kunci.
docker-entrypoint.sh
#!/bin/bash
set -e
# 1. Host key (boot pertama)
[ -f /etc/ssh/ssh_host_ed25519_key ] || ssh-keygen -A >/dev/null 2>&1 || true
# 2. Kata laluan daripada env
if [ -n "$SSH_PASSWORD" ]; then
echo "root:$SSH_PASSWORD" | chpasswd
echo "[ssh-init] OK root password set daripada env SSH_PASSWORD"
fi
# 3. Kunci awam daripada env (append; kunci lain tidak dipadam)
if [ -n "$SSH_PUBLIC_KEY" ]; then
mkdir -p /root/.ssh && chmod 700 /root/.ssh
echo "$SSH_PUBLIC_KEY" >> /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys
echo "[ssh-init] OK public key ditambah daripada env SSH_PUBLIC_KEY"
fi
# 4. Kunci awam daripada secret yang dimount
if [ -f /run/secrets/ssh-public-key ]; then
mkdir -p /root/.ssh && chmod 700 /root/.ssh
cat /run/secrets/ssh-public-key >> /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys
echo "[ssh-init] OK public key ditambah daripada /run/secrets/ssh-public-key"
fi
# 5. Fallback: tiada kata laluan dan tiada kunci → kata laluan rawak, cetak ke log
if [ -z "$SSH_PASSWORD" ] && [ ! -s /root/.ssh/authorized_keys ]; then
GEN_PW=$(head -c 24 /dev/urandom | base64 | tr -dc 'A-Za-z0-9' | head -c 16)
echo "root:$GEN_PW" | chpasswd
echo "============================================================"
echo " [ssh-init] TIADA SSH_PASSWORD / SSH_PUBLIC_KEY ditetapkan"
echo " Kata laluan root sementara: $GEN_PW"
echo " Guna sekali sahaja, kemudian tetapkan kunci sendiri"
echo "============================================================"
fi
# 6. Mulakan sshd; jika gagal, JANGAN bunuh pod (Jupyter tetap berjalan)
if /usr/sbin/sshd -p 22 >/dev/null 2>&1; then
echo "[ssh-init] OK sshd bermula di port 22"
else
echo "[ssh-init] WARNING: sshd gagal bermula, semak log" >&2
fi
# 7. Teruskan ke arahan sebenar (CMD image ATAU arahan override platform)
exec "$@"
Dua keputusan reka bentuk yang penting: kunci ditambah, bukan menggantikan (supaya kunci yang platform tulis sendiri tidak hilang), dan kegagalan sshd tidak membunuh pod, jadi Jupyter tetap tersedia walaupun SSH bermasalah.
Langkah 2 — Build (pada node arm64)
ssh <gpu-node>
mkdir -p /tmp/gpu-image && cd /tmp/gpu-image
# letak Dockerfile, sshd_config dan docker-entrypoint.sh di sini
docker build -t jupyter-gpu-ssh:latest /tmp/gpu-image/
Langkah 3 — Import ke containerd (jika tiada registry)
docker save jupyter-gpu-ssh:latest | gzip > /tmp/jupyter-gpu-ssh.tar.gz
gunzip -c /tmp/jupyter-gpu-ssh.tar.gz | ctr -n k8s.io images import -
crictl images | grep jupyter-gpu-ssh
Langkah 4 — Deploy (pengguna tetapkan kelayakan semasa provision)
Melalui env:
env:
- name: SSH_PUBLIC_KEY
value: "ssh-ed25519 AAAAC3... user@laptop"
- name: JUPYTER_TOKEN
valueFrom:
secretKeyRef: { name: jupyter-token, key: token }
Untuk kata laluan, gunakan valueFrom.secretKeyRef juga, jangan letakkannya terus dalam YAML. Atau mount secret:
volumeMounts:
- name: ssh-key-secret
mountPath: /run/secrets/ssh-public-key
subPath: ssh-public-key
readOnly: true
Langkah 5 — Sahkan
kubectl get pods -n <namespace> -w
kubectl logs -n <namespace> deployment/<name> -c <container>
# log mesti ada: [ssh-init] OK sshd bermula di port 22
Akses SSH daripada sisi pengguna
| Kaedah | Persediaan | Login |
|---|---|---|
| Kunci SSH | Env SSH_PUBLIC_KEY | ssh -i ~/.ssh/<key> -p <port> root@<host> |
| Kata laluan | Env SSH_PASSWORD | ssh -p <port> root@<host> dan taip kata laluan |
| Secret mount | Platform suntik kunci ke /run/secrets/ssh-public-key | Sama seperti kunci biasa |
| Fallback | Tiada apa-apa ditetapkan | Kata laluan rawak dalam log pod |
kubectl exec | Tidak perlu SSH | kubectl exec -it <pod> -- /bin/bash |
Troubleshooting
| Simptom | Semak |
|---|---|
Exit 127, sshd tiada | Image tidak dibina dengan openssh-server |
Permission denied (publickey,password) | SSH_PASSWORD / SSH_PUBLIC_KEY tidak ditetapkan, atau kunci salah |
sshd tidak bermula walaupun image betul | Platform menimpa CMD tanpa wrapper; gunakan docker-entrypoint.sh di atas |
| Exit 137 (OOMKilled) | Naikkan had memori |
| GPU tidak dikesan | Container tidak berjalan sebagai root |
| Image pull gagal | Guna imagePullPolicy: Never (sudah ada pada node) atau IfNotPresent |
Untuk cara instance seperti ini boleh dicapai dari luar melalui nama awam, lihat SSH Awam ke Pod GPU Kubernetes melalui Pangolin.
Leave a Comment / Ask a Question
Have thoughts or questions on this article? Drop a message below and Faiez will receive an instant notification.