Обезбедете го вашиот Docker-домаќин: без root, зајакнат и надградлив
Зајакнат контејнерски хост не е едноставно обичен сервер со инсталиран Docker. Границата на привилегии на даемонот, објавените порти, акредитивите, дневниците, резервните копии и патеката за надградба стануваат дел од продукцискиот дизајн.
Ова упатство гради намерно мал, но комплетен систем: rootless Docker даемон извршува автентицирана Nginx услуга на портата 8080, додека nftables го ограничува пристапот до одобрени мрежи. Оптоварувањето има ограничувања на ресурси, ротирани дневници, здравствени проверки, заштитена тајна, конзистентни резервни копии и тестирана патека за враќање назад.
Предуслови и работни претпоставки
Користете посебен Debian или Ubuntu хост базиран на systemd, со cgroup v2, стабилна IP адреса, работна синхронизација на времето и административен пристап преку конзола или постоечка SSH сесија. Примерите претпоставуваат дека официјалното складиште на Docker пакети е веќе конфигурирано.
Ќе ви требаат два реални мрежни опсега:
- Административна мрежа на која ѝ е дозволено да достигне SSH.
- Услужна мрежа или мрежа на reverse proxy на која ѝ е дозволено да достигне порта 8080.
Примерите користат 10.20.0.0/16 и 10.30.0.0/16. Заменете ги пред да го примените заштитниот ѕид. Портата 8080 пренесува обичен HTTP, затоа изложете ја само на доверлива приватна мрежа, VPN или reverse proxy што завршува TLS. Не ја објавувајте директно на недоверлива интернет-патека.
Оставете ја тековната административна сесија отворена додека ги менувате правилата на заштитниот ѕид. Синтаксичката проверка не може да открие неточна изворна мрежа.
Архитектура и компромиси
Docker работи како непривилегираниот корисник appsvc. Root-корисникот во контејнерот е мапиран во подредениот опсег на кориснички ID-и на таа сметка, наместо во root-корисникот на хостот. Затоа, пробив од контејнерот не станува веднаш root-компромитација на хостот.
Rootless режимот има цена. Неговата мрежна патека во корисничкиот простор може да троши повеќе CPU од изворното bridge-вмрежување, портите со ниски броеви бараат дополнителен дизајн, а ограничувањата на ресурсите зависат од делегирање на cgroup v2. Ова се разумни компромиси за интерно веб-оптоварување, но направете benchmark на услуги со висок проток или чувствителни на латентност пред да го стандардизирате овој образец.
Распоредот на хостот е намерно експлицитен:
/home/appsvc/edge-site/
├── compose.yaml
├── nginx/
│ └── nginx.conf
└── site/
└── index.html
/home/appsvc/.config/edge-site/
└── htpasswd
/home/appsvc/backups/
└── edge-site-TIMESTAMP.tar.zst
Инсталирајте го rootless runtime-от
Извршете го следново како root на хостот. Посебната сметка нема лозинка и не треба да добие широк sudo пристап.
apt-get update
apt-get install -y \
docker-ce-cli docker-ce-rootless-extras docker-compose-plugin \
uidmap dbus-user-session slirp4netns fuse-overlayfs \
nftables openssl zstd curl
adduser --disabled-password --gecos "" appsvc
loginctl enable-linger appsvc
install -d -o appsvc -g appsvc -m 0750 \
/home/appsvc/edge-site \
/home/appsvc/edge-site/nginx \
/home/appsvc/edge-site/site
install -d -o appsvc -g appsvc -m 0700 \
/home/appsvc/.config/edge-site \
/home/appsvc/backups
Потврдете дека appsvc има записи и во /etc/subuid и во /etc/subgid. Не измислувајте рачно преклопувачки опсези; користете го механизмот за управување со сметки што е соодветен за дистрибуцијата ако недостасува кој било запис.
Стартувајте го постојаниот кориснички менаџер, потоа инсталирајте Docker како appsvc. Овие команди ги издава root, но алатката за поставување и даемонот работат под услужната сметка:
app_uid="$(id -u appsvc)"
test -n "$app_uid"
test -S "/run/user/$app_uid/bus"
sudo -u appsvc \
XDG_RUNTIME_DIR="/run/user/$app_uid" \
dockerd-rootless-setuptool.sh check
sudo -u appsvc \
XDG_RUNTIME_DIR="/run/user/$app_uid" \
dockerd-rootless-setuptool.sh install
sudo -u appsvc \
XDG_RUNTIME_DIR="/run/user/$app_uid" \
systemctl --user enable --now docker
Ако bus socket-от не е присутен, стартувајте го корисничкиот менаџер на appsvc преку loginctl или креирајте реална сесија за најава, па повторете ја проверката. Не преминувајте на Docker socket во сопственост на root само за да заобиколите проблем со корисничка сесија.
Изградете го оптоварувањето
Креирајте ја тајната за автентикација
Извршете ја оваа и сите последователни Docker команди како appsvc. Лозинката се чита без ехо во терминалот и се предава на OpenSSL преку стандарден влез наместо како аргумент на командната линија.
cd /home/appsvc/edge-site
umask 077
IFS= read -r -s -p "Operator password: " site_password
printf '\n'
test -n "$site_password"
password_hash="$(
printf '%s\n' "$site_password" | openssl passwd -6 -stdin
)"
unset site_password
printf 'operator:%s\n' "$password_hash" \
> /home/appsvc/.config/edge-site/htpasswd
unset password_hash
chmod 0600 /home/appsvc/.config/edge-site/htpasswd
Compose тајна не е vault и не е шифрирана само затоа што се појавува под /run/secrets. Нејзината вредност е што акредитивот останува надвор од сликата, околината, Compose-датотеката и аргументите на процесот. Заштитете ја нејзината датотека на хостот и секоја резервна копија што ја содржи.
Конфигурирајте Nginx
Креирајте /home/appsvc/edge-site/nginx/nginx.conf со оваа конфигурација:
user nginx;
worker_processes auto;
error_log /dev/stderr info;
pid /run/nginx/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /dev/stdout combined;
sendfile on;
keepalive_timeout 30;
server_tokens off;
server {
listen 8080;
root /usr/share/nginx/html;
location = /healthz {
access_log off;
default_type text/plain;
return 200 "ok\n";
}
location / {
auth_basic "Restricted";
auth_basic_user_file /run/nginx/htpasswd;
try_files $uri $uri/ =404;
}
}
}
Креирајте site/index.html што содржи едноставен пасус, како <p>Rootless service is healthy.</p>. Во реално распоредување, оваа директорија може да содржи генерирана документација или друга страница само за читање.
Дефинирајте го контејнерот
Креирајте /home/appsvc/edge-site/compose.yaml:
services:
site:
image: docker.io/library/nginx:stable-alpine
container_name: edge-site
restart: unless-stopped
ports:
- "0.0.0.0:8080:8080"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./site:/usr/share/nginx/html:ro
secrets:
- basic_auth
command:
- /bin/sh
- -ec
- |
cp /run/secrets/basic_auth /run/nginx/htpasswd
chmod 0444 /run/nginx/htpasswd
exec nginx -g 'daemon off;'
read_only: true
tmpfs:
- /run/nginx:size=1m,mode=0755
- /var/cache/nginx:size=16m,mode=0755
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- CHOWN
- SETGID
- SETUID
pids_limit: 100
mem_limit: 256m
cpus: 1.0
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8080/healthz"]
interval: 30s
timeout: 3s
retries: 3
start_period: 10s
logging:
driver: local
options:
max-size: "10m"
max-file: "5"
secrets:
basic_auth:
file: /home/appsvc/.config/edge-site/htpasswd
Тесно ограничените способности на сликата постојат внатре во rootless корисничкиот namespace. Тие му дозволуваат на Nginx да иницијализира директориуми и да ги намали привилегиите, но не доделуваат соодветни способности на хостот. Root-датотечниот систем само за читање и ограничените привремени датотечни системи ги намалуваат и можностите за перзистенција и неконтролираниот раст на дискот.
Стартувајте ја услугата:
cd /home/appsvc/edge-site
docker compose config --quiet
docker compose pull
docker compose up -d
docker compose ps
Наметнете го заштитниот ѕид на хостот
Rootless Docker не обезбедува замена за политиката за влезен сообраќај на хостот. Креирајте /etc/nftables.d/edge-host.nft како root, откако ќе ги замените двете пример-мрежи:
table inet edge_host {
chain input {
type filter hook input priority 0;
policy drop;
ct state invalid drop
ct state established,related accept
iifname "lo" accept
ip protocol icmp accept
ip6 nexthdr ipv6-icmp accept
ip saddr 10.20.0.0/16 tcp dport 22 accept
ip saddr 10.30.0.0/16 tcp dport 8080 accept
}
chain forward {
type filter hook forward priority 0;
policy drop;
}
chain output {
type filter hook output priority 0;
policy accept;
}
}
Проверете го постојното управување со nftables, firewalld или UFW пред да продолжите. Повеќе управувачи со заштитен ѕид не треба да се натпреваруваат за истите hooks. Потврдете го фрагментот пред да го вчитате:
nft --check --file /etc/nftables.d/edge-host.nft
nft --file /etc/nftables.d/edge-host.nft
nft list table inet edge_host
За перзистентност, осигурете се дека /etc/nftables.conf содржи точно една линија include "/etc/nftables.d/*.nft", потврдете ја целосната датотека со nft --check --file /etc/nftables.conf и овозможете nftables.service. Тестирајте втора SSH-врска од одобрената административна мрежа пред да ја затворите изворната сесија.
Тестирајте ги безбедносните патеки и патеките на откажување
Од машина на дозволената услужна мрежа, потврдете ги здравјето, одбивањето и успешната автентикација:
curl --fail --silent --show-error --max-time 2 \
http://HOST_ADDRESS:8080/healthz
test "$(
curl --silent --output /dev/null \
--write-out '%{http_code}' \
--max-time 2 \
http://HOST_ADDRESS:8080/
)" = "401"
curl --fail --user operator \
--max-time 5 \
http://HOST_ADDRESS:8080/
Тестирајте и од мрежа надвор од дозволениот опсег; TCP-врската треба да истече или да биде отфрлена. Запрете го корисничкиот даемон и потврдете дека услугата станува недостапна, потоа рестартирајте го со systemctl --user start docker. Конечно, рестартирајте го хостот и потврдете дека lingering ги стартува и rootless даемонот и контејнерот управуван со restart.
Дневници, набљудливост и капацитет
Користете docker compose logs --since 15m site за пристапниот и излезот за грешки, и journalctl --user -u docker --since today за настаните на даемонот. Локалниот двигател за евидентирање ги ротира дневниците на контејнерот, но ротацијата не е централизирано задржување. Препраќајте избрани дневници на хостот и апликацијата до оддалечен собирач ако истрагата на инциденти мора да преживее губење на хостот.
Следете го здравјето на контејнерот, бројот на рестартирања, меморијата, бројот на процеси, користењето на датотечниот систем и корисничката услуга на rootless даемонот. docker stats --no-stream обезбедува корисна моментална проверка, а не трајно следење.
Ако ограничувањата на меморијата или CPU изгледаат неефикасни, потврдете дека хостот користи cgroup v2 и дека корисничкиот менаџер делегира контролери. Не отстранувајте ги ограничувањата тивко: третирајте го недостапното спроведување како неуспех при распоредувањето или документирајте експлицитна алтернатива за контрола на капацитетот.
Креирајте и тестирајте конзистентни резервни копии
Примерот е статичен, но постапката за резервна копија го запира оптоварувањето за да воспостави јасна граница на конзистентност. Услугите поддржани од база на податоци обично треба да го користат нивниот изворен механизам за snapshot или dump наместо да копираат датотеки со активни податоци.
cd /home/appsvc/edge-site
umask 077
backup_dir=/home/appsvc/backups
stamp="$(date -u +%Y%m%dT%H%M%SZ)"
archive="$backup_dir/edge-site-$stamp.tar.zst"
test -d "$backup_dir"
test ! -e "$archive"
docker compose stop
trap 'docker compose start' EXIT
tar --create --zstd --file="$archive" \
compose.yaml nginx site \
-C /home/appsvc .config/edge-site
sha256sum "$archive" > "$archive.sha256"
docker compose start
trap - EXIT
restore_dir="$(mktemp -d /home/appsvc/restore-test.XXXXXX)"
tar --extract --zstd --file="$archive" --directory="$restore_dir"
test -f "$restore_dir/compose.yaml"
test -f "$restore_dir/.config/edge-site/htpasswd"
Проверете ја директоријата за враќање и отстранете ја токму таа директорија кога ќе завршите. Копирајте ги шифрираните резервни копии во одделно складиште со независни акредитиви и задржување. Резервна копија што останува само на Docker хостот не е заштита од губење на хостот.
Надградете со автоматско враќање назад
Променливата ознака stable-alpine ги прави контролираните ажурирања практични, додека претходниот локален ID на сликата обезбедува цел за враќање назад. Извршувајте надградби од проектната директорија:
set -eu
cd /home/appsvc/edge-site
image_ref=docker.io/library/nginx:stable-alpine
old_image="$(docker inspect --format '{{.Image}}' edge-site)"
test -n "$old_image"
docker compose pull site
if ! docker compose up -d --force-recreate site; then
docker tag "$old_image" "$image_ref"
docker compose up -d --pull never --force-recreate site
exit 1
fi
healthy=false
for attempt in $(seq 1 30); do
if curl --fail --silent --output /dev/null --max-time 2 \
http://127.0.0.1:8080/healthz; then
healthy=true
break
fi
sleep 2
done
if test "$healthy" != true; then
docker compose logs --tail 100 site
docker tag "$old_image" "$image_ref"
docker compose up -d --pull never --force-recreate site
exit 1
fi
docker image inspect "$old_image" > /dev/null
docker compose ps
За поголем кластер, промовирајте тестирани image digest-и низ околини наместо да дозволувате секој хост независно да повлекува променлива ознака. Задржете ја претходната слика додека не поминат тестовите на апликацијата и периодот на набљудување.
Вообичаени неуспеси
- Rootless даемонот исчезнува по одјавувањето: потврдете дека
loginctl show-user appsvcизвестува дека lingering е овозможен и дека корисничката услуга е овозможена. - Контејнерот е здрав локално, но недостижен оддалеку: проверете ја објавената адреса со
docker port edge-site, потоа испитајте ги nftables броячите и изворната адреса на клиентот. - Nginx постојано се рестартира: проверете ги неговите дневници. Типични причини се нечитлива тајна, неправилно форматирана конфигурација или недостасувачка запишлива tmpfs патека.
- Ограничувањата се игнорираат или одбиваат: проверете ги cgroup v2 и делегирањето на корисничката услуга наместо да претпоставувате дека rootless режимот може да спроведе контролери што не се достапни за сметката.
- Надградбата не успева пред тестирањето на здравјето: зачувајте го стариот ID на сликата и извршете го документираното враќање назад. Не чистете слики како дел од трансакцијата за надградба.
Конечна контролна листа за потврда
- Docker даемонот работи како
appsvc, не како root, и преживува рестартирање. - Само одобрените мрежи можат да достигнат SSH и порта 8080.
- Неавтентицираните барања до апликацијата враќаат HTTP 401.
- Крајната точка за здравје успева со ограничен timeout.
- Контејнерот има root-датотечен систем само за читање, минимални способности и ограничувања на ресурси.
- Дневниците се ротираат локално, а важните настани имаат план за надворешно задржување.
- Постои контролна сума за резервната копија и враќањето беше успешно распакувано.
- Претходната слика останува достапна и враќањето назад е испробано.
Зајакнувањето најдобро функционира кога ги менува рутинските операции, а не само стандардните поставки при инсталацијата. Rootless даемонот ги ограничува привилегиите, но заштитниот ѕид ја ограничува изложеноста; дисциплината за тајни го ограничува протекувањето; ротацијата го ограничува притисокот врз дискот; резервните копии ја ограничуваат загубата; а увежбаното враќање назад го ограничува ризикот од надградба. Трајниот резултат не е неранлив хост. Тоа е хост чии неуспеси се ограничени, видливи и поправливи.