Bỏ qua đến nội dung
DevOps Lab

systemd Deep Dive

Từ init process đến cgroup v2 — kiến trúc, vòng đời service, journal logging và kỹ thuật nâng cao cho SRE/DevOps.

Linux • systemd • DevOps Deep Dive

Mastering
systemd

Từ init process đến cgroup v2 — toàn bộ kiến trúc, vòng đời service, journal logging và các kỹ thuật nâng cao dành cho SRE/DevOps.

11
unit types
PID 1
init process
cgroup
v2 unified
D-Bus
IPC layer
// 01 — OVERVIEW

systemd là gì?

systemd là init system và service manager thay thế SysV init, được dùng trên hầu hết các distro hiện đại: Ubuntu, Debian, CentOS, RHEL, Arch, Fedora...

Kiến trúc tổng quan

systemd không chỉ quản lý service — đây là một hệ sinh thái hoàn chỉnh gồm nhiều daemon và công cụ tích hợp chặt chẽ.

systemd PID 1 — init process D-Bus (IPC) systemctl CLI control journald log collection networkd networking logind login mgmt Linux Kernel cgroups v2 namespaces seccomp/SELinux *.service *.timer *.socket *.target *.mount *.path Unit Files — /etc/systemd/system/ | /lib/systemd/system/

So sánh với SysV init

FeatureSysV initsystemd
Khởi động song song✗ sequential✓ parallel
Dependency management✗ manual✓ automatic
Socket activation
Resource control✓ cgroups
Log managementsyslogjournald
On-demand activation
Snapshot/rollback
Config formatShell scriptsINI-style units
// PATHS QUAN TRỌNG
/lib/systemd/system/ — unit files của distro
/etc/systemd/system/ — override của admin (ưu tiên cao hơn)
/run/systemd/system/ — runtime units (tạm thời)
// 02 — BOOT PROCESS

Quá trình khởi động

Từ khi bấm nút nguồn đến lúc login prompt xuất hiện — mỗi giai đoạn systemd chịu trách nhiệm gì.

01
BIOS / UEFI

POST → tìm bootloader (GRUB2) trên disk

~1-3s
02
GRUB2 / Bootloader

Load kernel + initramfs vào RAM, truyền kernel params

~0.5s
03
Kernel initializes

Hardware init, mount tmpfs root, decompress initramfs

~0.5s
04
initramfs systemd

Early systemd: mount real root fs, handle LUKS/LVM/RAID

~1s
05
systemd PID 1

Switch root → /sbin/init → systemd starts, reads units

~0.2s
06
default.target

Activate target graph (multi-user.target hoặc graphical.target)

~2-5s
07
Userland services

SSH, networking, DB, app services khởi động song song

~1-10s

Phân tích boot time

Dùng systemd-analyze để debug boot bottleneck:

SHELL
# Tổng thời gian boot systemd-analyze # Chi tiết từng unit systemd-analyze blame # Critical chain (longest path) systemd-analyze critical-chain # Export SVG timeline systemd-analyze plot > boot.svg # Verify unit file syntax systemd-analyze verify myapp.service

Kernel command line params

GRUB
# /etc/default/grub GRUB_CMDLINE_LINUX="quiet splash" # Debug boot — verbose systemd systemd.log_level=debug # Boot vào emergency shell systemd.unit=emergency.target # Skip specific unit rd.systemd.mask=plymouth.service
// PRO TIP — SRE
Boot time target: server < 30s. Dùng systemd-analyze blame tìm các unit > 2s để optimize hoặc disable. NetworkManager-wait-online.service thường là culprit lớn nhất trên cloud instances.
// 03 — UNIT FILES

Unit Files

systemd quản lý mọi thứ qua "unit" — file cấu hình dạng INI. Có 11 loại unit khác nhau.

.service
Service
Daemon, process management
.socket
Socket
Socket activation
.target
Target
Group / sync point
.timer
Timer
Scheduled jobs (cron replacement)
.mount
Mount
Filesystem mount points
.automount
Automount
On-demand mounting
.path
Path
Filesystem path watching
.device
Device
Kernel device (udev)
.slice
Slice
cgroup resource group
.scope
Scope
External process group
.swap
Swap
Swap space management

Anatomy của một Service Unit File

/etc/systemd/system/myapp.service
# ─── [Unit] ─── metadata và dependencies [Unit] Description=My Application Service Documentation=https://docs.myapp.com After=network.target postgresql.service Requires=postgresql.service Wants=redis.service # ─── [Service] ─── cách chạy process [Service] Type=simple # | forking | notify | oneshot | idle | exec User=myapp Group=myapp WorkingDirectory=/opt/myapp EnvironmentFile=/etc/myapp/env ExecStart=/usr/bin/myapp --config /etc/myapp/config.yml ExecReload=/bin/kill -HUP $MAINPID ExecStop=/bin/kill -TERM $MAINPID Restart=on-failure # | always | on-abnormal | never RestartSec=5s TimeoutStartSec=30s TimeoutStopSec=20s StandardOutput=journal StandardError=journal SyslogIdentifier=myapp # ─── [Install] ─── enable/disable target [Install] WantedBy=multi-user.target
/etc/systemd/system/myapp-hardened.service
[Unit] Description=Hardened Production Service After=network-online.target Wants=network-online.target [Service] Type=notify User=myapp ExecStart=/usr/bin/myapp # ── Resource Limits ── LimitNOFILE=65536 LimitNPROC=4096 MemoryMax=512M CPUQuota=200% # 2 cores max TasksMax=512 # ── Security Hardening ── NoNewPrivileges=yes PrivateTmp=yes # isolated /tmp PrivateDevices=yes ProtectSystem=strict # ro bind-mount /usr /boot /etc ProtectHome=yes ReadWritePaths=/var/lib/myapp /var/log/myapp ProtectKernelTunables=yes ProtectKernelModules=yes ProtectControlGroups=yes RestrictRealtime=yes RestrictSUIDSGID=yes LockPersonality=yes MemoryDenyWriteExecute=yes SystemCallFilter=@system-service SystemCallErrorNumber=EPERM CapabilityBoundingSet=CAP_NET_BIND_SERVICE AmbientCapabilities=CAP_NET_BIND_SERVICE # ── Restart Policy ── Restart=always RestartSec=5 StartLimitInterval=60s StartLimitBurst=3 [Install] WantedBy=multi-user.target
/etc/systemd/system/db-migrate.service
[Unit] Description=Database Migration After=postgresql.service Requires=postgresql.service ConditionPathExists=!/var/lib/myapp/.migrated [Service] Type=oneshot # chạy 1 lần, không background RemainAfterExit=yes # giữ "active" sau khi exit 0 User=myapp ExecStart=/opt/myapp/bin/migrate up ExecStart=/bin/touch /var/lib/myapp/.migrated ExecStartPost=/bin/echo "Migration complete" [Install] WantedBy=multi-user.target
/etc/systemd/system/worker@.service
[Unit] Description=Worker Instance %i After=network.target [Service] Type=simple User=worker Environment=WORKER_ID=%i ExecStart=/usr/bin/worker --id %i --config /etc/worker/%i.conf Restart=on-failure [Install] WantedBy=multi-user.target # ─── Sử dụng ─── # Khởi động worker instance 1, 2, 3 systemctl start worker@1.service systemctl start worker@{1..5}.service systemctl enable worker@1 worker@2 # Specifiers hay dùng: # %i = instance name (worker@redis → redis) # %n = full unit name # %H = hostname # %u = user name # %m = machine ID

Drop-in Override (systemctl edit)

SHELL
# Tạo drop-in override systemctl edit nginx.service # Tạo: /etc/systemd/system/nginx.service.d/override.conf # Override toàn bộ unit file systemctl edit --full nginx.service # Reload sau khi edit systemctl daemon-reload
/etc/systemd/system/nginx.service.d/override.conf
[Service] # QUAN TRỌNG: muốn override ExecStart # phải clear trước rồi set lại ExecStart= ExecStart=/usr/sbin/nginx -g 'daemon off;' LimitNOFILE=100000 MemoryMax=1G
// 04 — SERVICE LIFECYCLE

Vòng đời Service

State machine của một systemd service — từ lúc được load đến khi fail.

inactive dead / not running activating ExecStartPre/ExecStart active running deactivating ExecStop running failed exit code != 0 reloading ExecReload (SIGHUP) start success failed stop done crash Restart=on-failure reload Arrows: ─── = normal ╌╌╌ = auto-restart running transition failure

systemctl — Bảng lệnh đầy đủ

CommandMô tả
systemctl start <unit>Khởi động unit
systemctl stop <unit>Dừng unit
systemctl restart <unit>Restart unit
systemctl reload <unit>Reload config (SIGHUP)
systemctl reload-or-restartReload nếu có thể
systemctl enable <unit>Enable autostart
systemctl disable <unit>Disable autostart
systemctl mask <unit>Hoàn toàn chặn start
systemctl unmask <unit>Bỏ chặn
CommandMô tả
systemctl status <unit>Xem trạng thái chi tiết
systemctl is-active <unit>active / inactive
systemctl is-enabled <unit>enabled / disabled
systemctl is-failed <unit>Kiểm tra failed
systemctl list-unitsList tất cả units
systemctl list-unit-filesList unit files
systemctl cat <unit>Xem nội dung file
systemctl show <unit>Tất cả properties
systemctl daemon-reloadReload unit files
// 05 — TARGETS & DEPENDENCIES

Targets & Dependency Graph

Target thay thế runlevel của SysV. Dependency graph quyết định thứ tự khởi động.

Target hierarchy

graphical.target runlevel 5 (GUI) multi-user.target runlevel 3 (server) basic.target sysinit + sockets sysinit.target mounts, swap sockets.target socket units local-fs.target mount local disks swap.target swap activation Wants After

Dependency keywords

KeywordÝ nghĩa
Requires=Bắt buộc — nếu dep fail thì unit cũng fail
Wants=Optional — dep fail không ảnh hưởng
BindsTo=Như Requires + nếu dep stop thì unit cũng stop
PartOf=Propagate stop/restart từ parent
Upholds=Liên tục restart dep nếu nó stop (systemd 249+)
After=Thứ tự khởi động (không phải dependency)
Before=Ngược của After=
Conflicts=Không thể chạy cùng nhau
Condition*=Điều kiện có/không start
Assert*=Như Condition nhưng fail thay vì skip
// QUAN TRỌNG
After=Requires= là độc lập nhau! Nếu chỉ có Requires=B mà không có After=B, service A và B có thể khởi động song song — dù A depends on B.
SHELL
# Xem dependency graph systemctl list-dependencies nginx.service # Reverse: ai depends on unit này systemctl list-dependencies --reverse nginx.service # Chuyển giữa targets (runlevel) systemctl isolate multi-user.target # Set default target systemctl set-default multi-user.target
// 06 — JOURNALD & LOGGING

Journal Logging

journald thu thập logs từ kernel, systemd, services và syslog — tất cả trong một binary format có cấu trúc, có thể query theo nhiều chiều.

journalctl — Query cheat sheet

SHELL
# Follow logs realtime (giống tail -f) journalctl -fu nginx.service # Logs của service cụ thể journalctl -u nginx.service # Khoảng thời gian journalctl --since "2024-01-15 10:00" --until "2024-01-15 12:00" journalctl --since "1 hour ago" # Priority: emerg(0) alert crit err warning notice info(6) debug(7) journalctl -p err journalctl -p warning..err # Theo PID, UID, GID journalctl _PID=1234 journalctl _UID=1000 # Boot sessions journalctl --list-boots journalctl -b -1 # boot trước # Output format journalctl -u myapp -o json-pretty journalctl -u myapp -o short-iso journalctl -u myapp -o cat # Kernel messages journalctl -k # Số lượng journalctl -n 50 -u nginx # Grep pattern journalctl -u myapp -g "ERROR|WARN"

Cấu hình journald

/etc/systemd/journald.conf
[Journal] Storage=persistent # auto|volatile|persistent|none Compress=yes SystemMaxUse=2G # max disk usage SystemKeepFree=1G # minimum free space SystemMaxFileSize=128M # size per journal file MaxRetentionSec=1month MaxFileSec=1week RateLimitInterval=30s RateLimitBurst=10000 ForwardToSyslog=no # nếu dùng rsyslog ForwardToWall=no

Disk usage & cleanup

SHELL
# Xem disk usage journalctl --disk-usage # Xóa logs cũ hơn 2 ngày journalctl --vacuum-time=2d # Giữ tối đa 500MB journalctl --vacuum-size=500M # Giữ tối đa 5 journal files journalctl --vacuum-files=5
// SRE TIP — Structured Logging
App log ra KEY=value format → journald index theo field. Query cực nhanh: journalctl MY_FIELD=value thay vì grep qua text.
// 07 — CGROUPS V2 & RESOURCE CONTROL

cgroups v2 Integration

systemd sử dụng cgroups v2 để giới hạn và theo dõi tài nguyên của mỗi service.

cgroup hierarchy

/ (cgroup v2 root)
├── system.slice
│ ├── nginx.service
│ │ └── PID 1234 (nginx: master)
│ │ └── PID 1235 (nginx: worker)
│ └── postgresql.service
│ └── PID 5678 (postgres)
├── user.slice
│ └── user-1000.slice
│ └── session-1.scope
│ └── PID 9999 (bash)
└── machine.slice
└── docker-abc.scope
SHELL
# Xem cgroup tree systemd-cgls # Resource usage realtime systemd-cgtop # Xem cgroup của process cat /proc/<PID>/cgroup # Tìm service của PID systemctl status <PID>

Resource Control Directives

SERVICE UNIT — RESOURCE LIMITS
[Service] # ── CPU ── CPUQuota=150% # 1.5 cores CPUWeight=100 # relative weight (default 100) AllowedCPUs=0-3 # pin to CPU 0,1,2,3 # ── Memory ── MemoryMax=512M # hard limit → OOM kill MemoryHigh=400M # soft limit → throttle MemorySwapMax=0 # disable swap MemoryMin=64M # guaranteed memory # ── I/O ── IOWeight=100 IOReadBandwidthMax=/dev/sda 100M IOWriteBandwidthMax=/dev/sda 50M IOReadIOPSMax=/dev/nvme0n1 1000 # ── Tasks (processes) ── TasksMax=512 # ── Network (với BPF) ── IPAddressDeny=any IPAddressAllow=192.168.1.0/24 127.0.0.1
SHELL — RUNTIME RESOURCE SET
# Set limit runtime (không cần daemon-reload) systemctl set-property nginx.service MemoryMax=256M systemctl set-property nginx.service CPUQuota=50% # Xem properties của service systemctl show nginx.service | grep -i memory
// 08 — SOCKET ACTIVATION & TIMERS

Socket Activation & Timers

Hai tính năng mạnh nhất của systemd: khởi động service theo yêu cầu qua socket, và thay thế cron bằng timer.

Socket Activation

systemd lắng nghe socket trước, khi có kết nối mới kích hoạt service. Service chưa chạy → khởi động tự động.

Client connect() systemd socket() Service spawned 1. connect 2. activate 3. pass fd ⚡ Zero downtime restart — connections buffered during restart 🚀 On-demand startup — no idle memory waste
/etc/systemd/system/myapp.socket
[Unit] Description=MyApp Socket [Socket] ListenStream=8080 # TCP port ListenStream=/run/myapp.sock # Unix socket SocketMode=0660 Accept=no # pass socket fd to service Backlog=1024 ReusePort=yes [Install] WantedBy=sockets.target

Systemd Timer (cron replacement)

/etc/systemd/system/backup.timer
[Unit] Description=Daily Backup Timer [Timer] OnCalendar=*-*-* 02:00:00 # daily 2AM OnCalendar=Mon 09:00 # Every Monday 9AM Persistent=true # chạy bù nếu bị missed RandomizedDelaySec=5m # random delay chống stampede AccuracySec=1s [Install] WantedBy=timers.target
/etc/systemd/system/backup.service
[Unit] Description=Backup Service [Service] Type=oneshot ExecStart=/usr/local/bin/backup.sh User=backup
CALENDAR SYNTAX
# OnCalendar examples hourly = *-*-* *:00:00 daily = *-*-* 00:00:00 weekly = Mon *-*-* 00:00:00 monthly = *-*-01 00:00:00 *:0/15 = every 15 minutes Mon,Wed,Fri 18:00 = MWF at 6PM 2024-*-15 12:00 = 15th of every month # Verify syntax systemd-analyze calendar "Mon *-*-* 09:00" # List all timers systemctl list-timers --all
// 09 — ADVANCED FEATURES

Tính năng nâng cao

Condition & Assert

UNIT — CONDITIONS
[Unit] # Skip (không fail) nếu condition sai ConditionPathExists=/etc/myapp/config ConditionFileNotEmpty=/etc/myapp/config ConditionPathIsDirectory=/var/lib/myapp ConditionHost=prod-server-01 ConditionVirtualization=!container ConditionMemory=>4G ConditionCPUs=>2 ConditionEnvironment=ENV=production # Assert: fail nếu sai (thay vì skip) AssertFileExists=/etc/ssl/cert.pem AssertPathIsReadWrite=/var/lib/myapp

ExecStart Variants

UNIT — EXEC PREFIXES
# Prefixes cho ExecStart/ExecStop/etc: # '-' = ignore failure (exit code != 0) ExecStartPre=-/bin/mkdir -p /var/run/myapp # '@' = pass argv[0] (execute as different name) ExecStart=@/usr/bin/myapp myapp-worker # '+' = run with full privileges ExecStart=+/usr/bin/privileged-setup # '!' = not subject to sandboxing ExecStart=!/usr/bin/setup # Multiple exec: run in order ExecStartPre=/usr/bin/check-deps ExecStartPre=/usr/bin/migrate-db ExecStart=/usr/bin/myapp ExecStartPost=/usr/bin/notify-ready

Transient Units (Runtime)

SHELL — systemd-run
# Chạy command như service tạm thời systemd-run --unit=my-job /usr/bin/my-script # Với resource limits systemd-run \ --unit=heavy-job \ --property=MemoryMax=1G \ --property=CPUQuota=50% \ /usr/bin/heavy-process # Timer transient systemd-run --on-calendar="*:0/5" /usr/bin/health-check # Chạy trong user session systemd-run --user --scope /usr/bin/myapp # Xem output journalctl -u my-job -f

sd_notify — Service Readiness

SHELL + CODE
# Type=notify: service báo sẵn sàng cho systemd # Trong code (bash) systemd-notify --ready systemd-notify STATUS="Serving 42 clients" systemd-notify WATCHDOG=1 # Watchdog: systemd restart nếu service không ping [Service] Type=notify WatchdogSec=30s NotifyAccess=main # | all | exec | none # Python example # import systemd.daemon # systemd.daemon.notify('READY=1') # systemd.daemon.notify('WATCHDOG=1')

User systemd (Systemd --user)

USER SERVICES
# Path: ~/.config/systemd/user/myservice.service systemctl --user start myservice systemctl --user enable myservice systemctl --user status myservice journalctl --user -u myservice # Enable lingering: user services survive logout loginctl enable-linger $USER # Xem user instance systemctl --user list-units
// 10 — DEBUGGING & TROUBLESHOOTING

Debug & Troubleshooting

SRE playbook — khi mọi thứ bắt đầu cháy.

Bước 1: Xem trạng thái tổng quan
SHELL
systemctl is-system-running # running | degraded | failed systemctl --failed # list tất cả failed units systemctl status # system overview
Bước 2: Xem chi tiết service fail
SHELL
systemctl status myapp.service # recent logs + state journalctl -u myapp.service -n 100 # 100 dòng cuối journalctl -u myapp.service -p err # chỉ errors journalctl -u myapp.service --since "10 minutes ago"
Bước 3: Validate unit file
SHELL
systemd-analyze verify myapp.service # syntax check systemctl cat myapp.service # xem effective config systemctl show myapp.service # tất cả properties # Xem environment variables của service systemctl show myapp -p Environment
Bước 4: Debug boot failures
SHELL
# Boot vào emergency mode (add to GRUB cmdline) systemd.unit=emergency.target rd.break # break vào initramfs # Từ emergency shell journalctl -xb # logs boot hiện tại với explanation journalctl -b -0 -p err # Debug verbose systemd.log_level=debug systemd.log_target=console
Bước 5: Strace / Debug process
SHELL
# Chạy service với strace systemctl edit myapp.service # Trong override.conf: [Service] ExecStart= ExecStart=/usr/bin/strace -f -o /tmp/myapp.strace /usr/bin/myapp # Live strace attach strace -p $(systemctl show myapp -p MainPID --value) # Check open files / sockets lsof -p $(systemctl show myapp -p MainPID --value)

Quick Reference — Common Issues

Triệu chứngLệnh debugFix phổ biến
Service start failedjournalctl -u svc -n 50Check ExecStart path, permissions, deps
Service start loopsystemctl show svc | grep StartTăng StartLimitBurst hoặc fix root cause
OOM killedjournalctl -k -g "oom"Tăng MemoryMax hoặc fix memory leak
Timeout on startsystemctl show svc -p TimeoutStartSecTăng TimeoutStartSec, dùng Type=notify
Unit file not foundsystemctl cat svcdaemon-reload sau khi tạo file mới
Permission deniedjournalctl -u svc -p errCheck User=, ProtectSystem=, paths
Slow bootsystemd-analyze blameDisable unused units, parallel deps
Journal disk fulljournalctl --disk-usagejournalctl --vacuum-size=1G
// ⚠ PRODUCTION WARNINGS
systemctl daemon-reload không restart services đang chạy — chỉ reload unit files vào memory.
systemctl mask tạo symlink đến /dev/null — unit không thể start dù ai cố ý — dùng cẩn thận.
After= không implies Requires= — luôn set cả hai nếu cần dependency thật sự.
Trên container (Docker, K8s), systemd thường không phải PID 1 — cần config đặc biệt hoặc dùng tini.