پرش به محتویات

۱۵. Docker و کانتینرها

کانتینر «ماشین مجازی سبک» نیست؛ فرایندی روی همان هسته است. دو خطر اصلی: داده (حذف volume) و امنیت (دسترسی root به میزبان، دور زدن فایروال). این فصل هر دو را پوشش می‌دهد.

راهنمای سریع

فرمان کاربرد ریسک
docker ps -a / docker logs / docker stats --no-stream / docker inspect مشاهده بدون تغییر
docker system df / docker compose config / compose ps ظرفیت و اعتبارسنجی بدون تغییر
docker exec -it ورود به کانتینر تغییر (ممکن است داخل داده بنویسید)
docker compose up -d / restart / pull استقرار تغییر
docker rm / compose down حذف کانتینر/شبکه تغییر (داده: volume)
docker compose down -v / volume rm / system prune -a --volumes حذف داده مخرب

وضعیت و منابع

docker version
docker info
docker ps
docker ps -a
docker images
docker stats --no-stream        # بدون --no-stream، تا Ctrl+C ادامه می‌دهد
docker system df
docker system df -v | head -40

لاگ و ورود به کانتینر

docker logs --tail 200 CONTAINER
docker logs --since 10m -t CONTAINER
docker logs -f CONTAINER
docker exec -it CONTAINER sh
docker inspect CONTAINER
docker top CONTAINER
docker cp CONTAINER:/var/log/app.log ./app.log
docker events --since 10m

در imageهای مینیمال ممکن است bash وجود نداشته باشد؛ sh را امتحان کنید. تغییری که داخل کانتینر (با exec) می‌دهید با ساخت دوبارهٔ کانتینر از بین می‌رود؛ پیکربندی را در image/compose/volume بگذارید.

چرخه عمر

docker start CONTAINER
docker stop CONTAINER
docker restart CONTAINER
docker rm CONTAINER
docker pull nginx:stable
docker run --rm -it alpine:3.20 sh      # کانتینر یک‌بارمصرف (بعد از خروج حذف می‌شود)

docker rm کانتینر را حذف می‌کند، نه لزوماً volumeهای مرتبط (volumeهای ناشناس با -v حذف می‌شوند: docker rm -v). قبل از حذف، وضعیت داده‌ها و سیاست پشتیبان را بررسی کنید.

Docker Compose

docker compose ps
docker compose logs -f --tail=200
docker compose config            # اعتبارسنجی و نمایش پیکربندی نهایی (متغیرها جایگزین شده)
docker compose pull
docker compose up -d
docker compose up -d --build --remove-orphans
docker compose up -d --force-recreate SERVICE
docker compose exec SERVICE sh
docker compose restart SERVICE
docker compose down

docker compose down کانتینرها و شبکه‌های پروژه را حذف می‌کند؛ volumeهای نام‌گذاری‌شده معمولاً باقی می‌مانند مگر -v اضافه شود. down -v می‌تواند داده را حذف کند. compose config ممکن است رمزهای .env را نشان دهد؛ خروجی را در تیکت نچسبانید.

نمونهٔ سرویس با سیاست‌های عملیاتی (نمونه):

services:
  app:
    image: registry.example.com/team/app:1.4.2     # tag ثابت، نه latest
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:8080"                       # فقط لوکال؛ proxy جلوی آن است
    env_file: ./app.env                              # مجوز 600
    read_only: true
    tmpfs: [/tmp]
    cap_drop: [ALL]
    security_opt: ["no-new-privileges:true"]
    mem_limit: 512m
    cpus: 1.0
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8080/healthz"]
      interval: 30s
      timeout: 3s
      retries: 3
    logging:
      driver: json-file
      options: { max-size: "10m", max-file: "3" }
    volumes:
      - app-data:/var/lib/app
volumes:
  app-data:

عیب‌یابی شبکه و volume

docker network ls
docker network inspect NETWORK
docker volume ls
docker volume inspect VOLUME
docker inspect -f '{{json .NetworkSettings.Networks}}' CONTAINER
docker exec CONTAINER getent hosts other-service      # نام سرویس در شبکهٔ compose

کانتینرها در یک شبکهٔ تعریف‌شده (user-defined / compose) با نام سرویس هم را پیدا می‌کنند؛ localhost داخل کانتینر خود همان کانتینر است، نه میزبان یا کانتینر دیگر.

دام امنیتی Docker: فایروال UFW را دور می‌زند

Docker برای پورت‌های منتشرشده (-p 8080:80) قوانین iptables را قبل از زنجیرهٔ UFW درج می‌کند. نتیجه: حتی اگر ufw deny 8080 بزنید و ufw status ببندد نشان دهد، پورت از اینترنت باز است.

sudo ss -lntp | grep docker-proxy        # چه پورت‌هایی منتشر شده؟
sudo iptables -t nat -L DOCKER -n | head

راه‌های کاهش (به ترتیب ترجیح):

  1. فقط به لوکال منتشر کنید و reverse proxy جلو بگذارید: -p 127.0.0.1:8080:80.
  2. قوانین خود را در زنجیرهٔ DOCKER-USER بگذارید (Docker آن را دست نمی‌زند)، مثلاً فقط یک شبکه:
sudo iptables -I DOCKER-USER -i eth0 ! -s 192.0.2.0/24 -p tcp --dport 8080 -j DROP
  1. هرگز "iptables": false را بدون درک کامل (NAT کانتینرها از کار می‌افتد) فعال نکنید.
  2. بعد از هر تغییر، از بیرون تست کنید: nc -vz SERVER_PUBLIC_IP 8080.

لاگ‌ها و پر شدن دیسک

پیش‌فرض json-file بدون محدودیت است؛ یک کانتینر پرگو دیسک را پر می‌کند.

# بزرگ‌ترین لاگ‌ها
sudo du -h /var/lib/docker/containers/*/*-json.log 2>/dev/null | sort -h | tail -5
# اضطراری (فقط برای آزادکردن فضا؛ لاگ از بین می‌رود)
sudo truncate -s 0 /var/lib/docker/containers/ID/ID-json.log
// /etc/docker/daemon.json  — برای کانتینرهای جدید (بعد از restart داکر)
{ "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }

برای کانتینرهای موجود باید دوباره ساخته شوند (up -d --force-recreate).

پاک‌سازی با احتیاط

docker image prune
docker container prune
docker system df

از docker system prune -a --volumes بدون بررسی دقیق استفاده نکنید؛ می‌تواند imageها، منابع بدون استفاده و داده‌های volume (کانتینر متوقف‌شدهٔ فعلی = volume «بدون استفاده») را حذف کند. ترتیب امن: docker system df ← docker image prune (فقط dangling) ← بررسی ← در صورت نیاز docker image prune -a --filter "until=720h".

بکاپ volume

docker run --rm -v VOLUME:/data:ro -v "$PWD":/backup alpine \
  tar czf /backup/VOLUME-$(date +%F).tgz -C /data .

برای دیتابیس، به‌جای کپی volume از dump خود دیتابیس استفاده کنید (پشتیبان‌گیری).

امنیت

  • گروه docker = root. هر عضو می‌تواند با docker run -v /:/host کل میزبان را تغییر دهد (کاربران).
  • /var/run/docker.sock را داخل کانتینر mount نکنید (معادل root روی میزبان).
  • اجرا با کاربر غیر root (USER در Dockerfile)، cap_drop: [ALL]، no-new-privileges، read_only.
  • tag ثابت یا digest (image@sha256:...)؛ از latest در production اجتناب کنید.
  • image ها را اسکن کنید (trivy image IMG / docker scout cves IMG) و منبع آن‌ها را بشناسید.
  • secretها را در Git، Dockerfile یا docker inspect (متغیرهای محیطی) نگذارید؛ از secret manager یا فایل با مجوز محدود استفاده کنید.
  • روی RHEL، podman جایگزین بدون daemon و rootless است (podman ps، podman compose).

خطاهای رایج

نشانه علت احتمالی روش بررسی
Exit code 137 OOM یا SIGKILL docker inspect -f '{{.State.OOMKilled}}' C ، dmesg -T \| grep -i kill
Exit code 139 segfault لاگ، نسخهٔ image
Exit code 143 SIGTERM (توقف عادی) معمولاً بی‌خطر
Exit code 125/126/127 خطای docker / مجوز اجرا / فرمان پیدا نشد docker logs ، ENTRYPOINT
port is already allocated پورت میزبان اشغال sudo ss -lntp \| grep :PORT
کانتینر مدام restart می‌شود کرش در شروع / healthcheck docker logs --tail 100 ، inspect
permission denied روی volume UID داخل کانتینر ≠ مالک پوشه ls -ln ، --user ، chown
دیسک /var/lib/docker پر لاگ/image/volume docker system df -v ، سقف لاگ
بعد از down داده رفت -v یا volume ناشناس بکاپ؛ volume نام‌گذاری‌شده
کانتینر به سرویس دیگر وصل نمی‌شود شبکهٔ متفاوت / localhost docker network inspect ، نام سرویس

نکات عملیاتی

  • نسخه و tag تصویر را ثابت و قابل ردیابی انتخاب کنید؛ در محیط تولیدی از latest اجتناب کنید.
  • secretها را در Git یا Dockerfile قرار ندهید؛ از secret manager یا فایل محیطی با مجوز محدود استفاده کنید.
  • healthcheck، محدودیت منابع، سیاست restart، سقف لاگ و پشتیبان‌گیری از volumeها را متناسب با سرویس تنظیم کنید.
  • ارتقای image: pull ← up -d ← تأیید ← نگه‌داشتن tag قبلی برای rollback.

خلاصهٔ اجرایی

  1. پورت‌ها را روی 127.0.0.1 منتشر کنید؛ UFW کانتینر را نمی‌بندد.
  2. سقف لاگ، tag ثابت، بدون root، بدون docker.sock.
  3. down -v و prune --volumes = حذف داده؛ اول بکاپ.
  4. گروه docker را مثل root مدیریت کنید.