← DevOps← DevOps
DevOpsDevOps18 Th7, 2026Jul 18, 202619 phút đọc16 min read

Hệ điều hànhOperating Systems

Thuộc bộ kiến thức DevOps Roadmap.

Tổng quan

Hệ điều hành là tầng nền mà mọi DevOps engineer đứng trên đó. Container thực chất là process Linux với namespace và cgroup; node Kubernetes là máy Linux; CI runner, database, load balancer đều sống hay chết theo hành vi ở tầng OS — áp lực bộ nhớ, giới hạn file descriptor, quyết định của scheduler, disk I/O. Khi một ứng dụng chết “một cách ngẫu nhiên”, câu trả lời thường nằm ở OS: OOM killer, ổ đĩa đầy, hết inode, hoặc một systemd unit cấu hình sai.

Linux thống trị server và cloud, nên nó xứng đáng nhận phần lớn sự chú ý của bạn. Nhưng môi trường thực tế luôn hỗn hợp: bạn sẽ gặp Windows Server trong doanh nghiệp (Active Directory, IIS, workload .NET), và các hệ BSD trong thiết bị mạng và lưu trữ (pfSense, FreeNAS/TrueNAS). Hiểu khái niệm OS — process, memory, file system, I/O, socket — có thể chuyển giao giữa tất cả các hệ, và bền vững hơn nhiều so với việc thuộc lòng đặc thù của một distro.

Với DevOps, kiến thức OS xuất hiện hằng ngày: chọn kích cỡ instance, tune kernel parameter, viết systemd unit, đọc output top/vmstat giữa sự cố, hiểu vì sao container bị OOM-kill, và phân biệt “connection refused” với “connection timed out” khi debug. Chủ đề lặp lại là: các abstraction đều “rò rỉ”: container không phải một VM nhẹ, nó là một process Linux bị rào lại, và khi nó lỗi thì cái rào (namespace, cgroup, kernel dùng chung) là nơi bạn nhìn vào. Mọi tầng phía trên — Docker, Kubernetes, cloud console — rốt cuộc đều đang phát system call xuống kernel, và khi mọi thứ vỡ ở quy mô lớn, góc nhìn của kernel mới là sự thật.

Kiến thức nền tảng

Bức tranh các bản phân phối Linux

NhánhDistroTrình quản lý góiThường gặp ở đâu
Gốc DebianDebian, Ubuntuapt / .debCloud VM, container, server đa dụng
Gốc Red HatRHEL, CentOS Stream, Rocky, AlmaLinux, Fedoradnf/yum / .rpmDoanh nghiệp, môi trường cần hỗ trợ dài hạn
Tối giản / containerAlpine, Flatcar, Bottlerocketapk, dạng imageBase image container, node Kubernetes
SUSEopenSUSE, SLESzypper / .rpmDoanh nghiệp châu Âu, workload SAP
KhácArch, Amazon Linux, Gentoopacman, dnfWorkstation, AWS, build từ source

Khác biệt thực dụng cần nhớ: trình quản lý gói và nhịp phát hành (Ubuntu LTS mỗi 2 năm, hỗ trợ 5 năm; RHEL vòng đời ~10 năm), module bảo mật (SELinux trên nhánh RHEL vs AppArmor trên Ubuntu/SUSE), vị trí file mặc định, và stack init/network mặc định. Alpine dùng musl libc thay vì glibc và BusyBox thay vì GNU coreutils — nguồn gốc của nhiều bug container khó hiểu (khác biệt về phân giải DNS, thiếu bash, hành vi date/getopt khác).

Một cách hình dung về distro “immutable” và tối ưu cho container (Flatcar, Bottlerocket, Talos): OS ở dạng read-only và được cập nhật như một image nguyên khối thay vì từng gói. Điều này loại bỏ configuration drift và ngày càng là cách các image node Kubernetes được dựng.

Unix (BSD) và Windows

Kernel vs. user space và ranh giới system call

Kernel quản lý phần cứng, bộ nhớ, scheduling, và cưỡng chế cô lập; ứng dụng chạy ở user space và yêu cầu các dịch vụ đặc quyền qua system call (open, read, write, fork, execve, mmap, socket). Ranh giới này là mental model quan trọng bậc nhất trong công việc OS: một chương trình không thể chạm trực tiếp vào phần cứng hay bộ nhớ của process khác — nó phải hỏi kernel, và kernel quyết định. strace cho bạn nhìn ranh giới này theo thời gian thực và thường là cách nhanh nhất để trả lời “process này thực sự đang làm gì / chờ gì?”

strace -f -e trace=openat,connect -p <pid>   # đang chạm vào file/socket nào?
strace -c ./program                          # tóm tắt số lần + thời gian mỗi syscall (tìm syscall nóng)

Process, thread và scheduling

Khái niệm chính

Quản lý bộ nhớ và virtual memory

Mỗi process nhìn thấy một address space ảo riêng; kernel ánh xạ các page (thường 4 KB, tùy chọn “huge page” 2 MB/1 GB) vào RAM vật lý khi cần qua MMU, và một page chưa được chạm tới không tốn gì cho tới lần truy cập đầu tiên (demand paging). Những hệ quả bạn sẽ gặp trên production:

free -h                        # tổng quan bộ nhớ (chú ý "available", không phải "free")
vmstat 1                       # cột si/so = hoạt động swap-in/swap-out
cat /proc/<pid>/status | grep -E 'VmRSS|VmSwap|VmHWM'
cat /proc/<pid>/oom_score      # pid này "hấp dẫn" với OOM killer tới mức nào
dmesg -T | grep -i 'killed process'   # nạn nhân của OOM killer kèm timestamp

Quản lý process và signal

ps aux --sort=-%mem | head     # process ngốn RAM nhất
ps -ef --forest                # cây process (quan hệ cha/con)
pgrep -af nginx                # tìm PID theo tên, in cả command đầy đủ
kill -TERM <pid>               # lịch sự: yêu cầu tự tắt (signal mặc định)
kill -KILL <pid>               # phương án cuối: không thể bị bắt hay bỏ qua
kill -HUP <pid>                # quy ước: reload cấu hình
nice -n 10 ./batch-job         # khởi động với độ ưu tiên CPU thấp
renice -n 5 -p <pid>           # đổi độ ưu tiên của process đang chạy

Cần thuộc các signal thông dụng và, quan trọng nhất, cái nào bắt được: SIGTERM (15, tắt êm — thứ Kubernetes và systemctl stop gửi trước, bắt được), SIGKILL (9, tức thì, không thể bắt hay chặn), SIGHUP (1, ngắt terminal, quy ước “reload config”), SIGINT (2, Ctrl-C), SIGSTOP/SIGCONT (tạm dừng/tiếp tục, cũng không bắt được), SIGCHLD (một process con đổi trạng thái). Việc shutdown “êm” — hoàn tất request đang xử lý, flush buffer, đóng kết nối — phụ thuộc hoàn toàn vào việc ứng dụng cài handler cho SIGTERM. Kubernetes gửi SIGTERM, chờ terminationGracePeriodSeconds (mặc định 30), rồi SIGKILL.

File system và I/O

df -h && df -i                 # dung lượng VÀ inode — kiểm tra cả hai
lsof +D /var/log | head        # ai đang giữ file mở trong một đường dẫn
lsof -p <pid>                  # mọi thứ một process đang mở (file, socket, pipe)
iostat -xz 1                   # latency (await) và mức sử dụng theo từng thiết bị
mount | column -t | head       # cái gì được mount ở đâu, với option nào
du -sh /var/* | sort -h        # tìm cái gì đang ngốn đĩa

Pseudo-filesystem /proc và /sys

/proc/sys là kernel được phơi ra dưới dạng file — không cần công cụ đặc biệt, chỉ cần cat. Đây là nơi diễn ra phần lớn việc introspection sâu: /proc/<pid>/ chứa status, cmdline, environ, fd/, limits, cgroup của process; /proc/meminfo, /proc/loadavg, /proc/mounts, /proc/net/ phơi ra trạng thái hệ thống. /sys phơi cây thiết bị và kernel-object. Kernel tunable nằm dưới /proc/sys và được đặt bằng sysctl (lưu lâu dài trong /etc/sysctl.d/):

sysctl vm.swappiness                       # đọc một tunable
sysctl -w net.core.somaxconn=1024          # đặt lúc runtime (mất khi reboot)
cat /proc/1/cgroup                          # PID 1 thuộc cgroup nào

Socket và kiến thức mạng cơ bản

Socket là một endpoint truyền thông xác định bởi protocol + IP + port. TCP hướng kết nối (bắt tay ba bước, retransmission, đảm bảo thứ tự, điều khiển luồng/tắc nghẽn); UDP phi kết nối “bắn rồi quên” (không đảm bảo giao, ít overhead — dùng bởi DNS, QUIC, VXLAN). Hệ quả thực tế bạn sẽ chẩn đoán liên tục:

ss -tlnp                       # socket TCP đang listen + process sở hữu
ss -tan state established      # các kết nối đã established
ss -s                          # thống kê số lượng theo trạng thái socket

Khởi động hệ thống và quản lý service: init và systemd

PID 1 là process user-space đầu tiên kernel khởi động, và là tổ tiên của mọi process (nó cũng reap các zombie mồ côi). Trước đây SysV init chạy các shell script theo thứ tự trong /etc/init.d với runlevel dạng số. Linux hiện đại dùng systemd, quản lý các unit — service, timer, mount, socket, target — với dependency khai báo, khởi động song song, giám sát process dựa trên cgroup, socket activation, và log tập trung qua journald.

# /etc/systemd/system/myapp.service
[Unit]
Description=My App
After=network-online.target
Wants=network-online.target

[Service]
User=myapp
ExecStart=/usr/local/bin/myapp --config /etc/myapp.yaml
Restart=on-failure
RestartSec=5
# giới hạn tài nguyên (cùng cơ chế cgroup mà container dùng)
MemoryMax=512M
CPUQuota=50%
LimitNOFILE=65536
# hardening dạng sandbox
NoNewPrivileges=true
ProtectSystem=strict
PrivateTmp=true

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload           # đọc lại unit file sau khi sửa
sudo systemctl enable --now myapp      # chạy ngay VÀ tự chạy khi boot
systemctl status myapp                 # trạng thái, log gần đây, PID chính, cgroup
systemctl list-units --failed          # cái gì đang hỏng ngay lúc này
journalctl -u myapp -f --since "10 min ago"   # theo dõi log của unit này
journalctl -p err -b                   # thông điệp mức error, trong lần boot này
systemd-analyze blame                  # các unit chậm nhất khi boot
systemctl mask <unit>                  # cưỡng chế ngăn một unit khởi động

Khái niệm liên quan: target (multi-user.target, graphical.target) thay thế runlevel; systemd timer là lựa chọn quan sát được và hiểu dependency hơn cron (chúng log vào journal và có thể “bắt” các lần chạy bị lỡ với Persistent=true); socket activation khởi động service khi có kết nối đầu tiên; và policy Restart= thay thế các script supervisor tự chế. Giới hạn MemoryMax/CPUQuota dùng chính cơ chế cgroups v2 mà container dùng — ở tầng này, systemd và Kubernetes đang làm cùng một việc.

Container là tính năng của OS, không phải phép màu

Đáng nói thẳng vì nó giải ảo rất nhiều: một container Linux là một process bình thường được kernel cô lập bằng namespace (tách biệt view về PID, mount, network interface, user, hostname, IPC) và ràng buộc bằng cgroup (giới hạn và tính toán CPU, memory, I/O, số PID). Không có đối tượng “container” nào trong kernel — Docker và containerd chỉ điều phối clone() với các cờ namespace, dựng root overlayfs, áp giới hạn cgroup, và (thường) một profile seccomp/AppArmor. Đó là lý do mọi khái niệm OS ở trên áp dụng trực tiếp bên trong container, và vì sao nsenter, /proc/<pid>/ns, và đọc file cgroup của một container là kỹ năng debug cốt lõi.

Best Practices

  1. Học một distro của mỗi nhánh lớn (Ubuntu + một bản clone RHEL như Rocky/Alma). Quản lý gói, công cụ bảo mật (SELinux vs AppArmor), và giá trị mặc định khác nhau; khái niệm nền tảng thì chuyển giao hoàn toàn.
  2. Chẩn đoán từ OS đi lên, không phải từ app đi xuống. Khi có sự cố, kiểm tra bốn thứ cơ bản — CPU, memory, disk, network (top, free -h, df -h/df -i, ss) — trước khi lao vào log ứng dụng. OS thường đã biết câu trả lời.
  3. Coi bộ nhớ “available”, không phải “free”, là headroom thật. Page cache có thể thu hồi khi cần; cảnh báo dựa trên “free” tạo báo động giả và che giấu leak thật.
  4. Luôn gửi SIGTERM trước SIGKILL, và xây ứng dụng biết shutdown êm khi nhận SIGTERM — Kubernetes, systemd và docker stop đều dựa vào giao ước này. Thiếu handler SIGTERM nghĩa là mất request và hỏng state mỗi lần deploy.
  5. Chạy service dưới systemd (hoặc supervisor của platform), đừng bao giờ nohup ... &. Bạn có sẵn restart tự động, thu log, sắp thứ tự dependency, giới hạn tài nguyên, và sandbox — cùng một process sống sót sau khi session SSH của bạn kết thúc.
  6. Đặt giới hạn tài nguyên tường minh (ulimit/LimitNOFILE, MemoryMax, request/limit của container) để một process mất kiểm soát không làm đói hay kéo sập cả host. “Không giới hạn” là một quyết định, thường là quyết định sai.
  7. Giám sát inode và file descriptor, không chỉ dung lượng đĩa. Hết inode (df -i) và cạn fd làm hệ thống chết trong khi df -h và dashboard vẫn xanh.
  8. Vá hệ thống theo lịch cố định — bật cập nhật bảo mật tự động khi an toàn, theo dõi chiến lược reboot kernel (hoặc dùng livepatch/kexec), và biết mức phơi nhiễm CVE. Kernel chưa vá là con đường leo thang đặc quyền phổ biến nhất.
  9. Đừng tắt SELinux/AppArmor theo phản xạ. Học audit2allow/aa-logprof để sửa đúng cái bị chặn. Tắt LSM là một bước lùi bảo mật vĩnh viễn đổi lấy một phiền toái nhất thời.
  10. Dùng journalctldmesg như công cụ xử lý sự cố hạng nhất. OOM kill, lỗi đĩa, segfault, và cảnh báo kernel nằm ở đó, không nằm trong log ứng dụng. Dùng dmesg -T để có timestamp dễ đọc.
  11. Hiểu file system trước khi chọn nó. Snapshot và checksum (ZFS/Btrfs), hành vi với file lớn và I/O song song (XFS), và ngữ nghĩa bền vững của fsync quyết định trực tiếp độ an toàn của database và backup.
  12. Tune kernel parameter có chủ đích và lưu lâu dài. Giới hạn file descriptor, vm.swappiness, net.core.somaxconn, net.ipv4.tcp_tw_reuse, dải ephemeral port — đặt trong /etc/sysctl.d/ (không chỉ lúc runtime) kèm comment giải thích vì sao.
  13. Right-size trước khi scale-out. Đọc vmstat/iostat/sar để biết workload nghẽn CPU, memory hay I/O; nút thắt quyết định loại instance và liệu thêm replica có giúp ích không.
  14. Ưu tiên node immutable, dạng image (Bottlerocket/Flatcar/Talos, hoặc golden AMI) thay vì các “pet” sống lâu vá tại chỗ. Hạ tầng thay thế được sẽ loại bỏ configuration drift.
  15. Hiểu nền tảng namespace/cgroup của container. Khi một pod bị OOM-kill, bị throttle, hay “không thấy” mạng, câu trả lời nằm ở primitive của OS, không phải ở abstraction của Kubernetes.
  16. Luyện tập trên VM dùng xong vứt. Chủ động phá hỏng — làm đầy đĩa, kích hoạt OOM killer, làm cạn file descriptor, mask một unit, làm bão hòa CPU — để lần đầu bạn thấy “chữ ký” của lỗi không phải trên production.

Tài liệu tham khảo

Part of the DevOps Roadmap knowledge base.

Overview

The operating system is the layer every DevOps engineer ultimately stands on. Containers are Linux processes with namespaces and cgroups; Kubernetes nodes are Linux machines; CI runners, databases, and load balancers all live or die by OS-level behavior — memory pressure, file descriptor limits, scheduler decisions, disk I/O. When an application “randomly” dies, the answer is usually in the OS: the OOM killer, a full disk, an exhausted inode table, or a misconfigured systemd unit.

Linux dominates servers and the cloud, so it deserves most of your attention. But real environments are heterogeneous: you will meet Windows Server in enterprise shops (Active Directory, IIS, .NET workloads), and BSD systems in networking appliances and storage (pfSense, FreeNAS/TrueNAS). Understanding OS concepts — processes, memory, file systems, I/O, sockets — transfers across all of them and is far more durable than memorizing any single distro’s quirks.

For DevOps specifically, OS knowledge shows up daily: sizing instances, tuning kernel parameters, writing systemd units, reading top/vmstat output during incidents, understanding why a container was OOM-killed, and debugging “connection refused” versus “connection timed out.” The recurring theme is that abstractions leak: a container is not a lightweight VM, it is a fenced-off Linux process, and when it misbehaves the fence (namespaces, cgroups, the shared kernel) is where you look. Every layer above — Docker, Kubernetes, the cloud console — is ultimately issuing system calls to a kernel, and when things break at scale, the kernel’s view is the truth.

Fundamentals

The Linux distribution landscape

FamilyDistrosPackage managerWhere you’ll see it
Debian-basedDebian, Ubuntuapt / .debCloud VMs, containers, general servers
Red Hat-basedRHEL, CentOS Stream, Rocky, AlmaLinux, Fedoradnf/yum / .rpmEnterprise, long-term-support environments
Minimal / containerAlpine, Flatcar, Bottlerocketapk, image-basedContainer base images, Kubernetes nodes
SUSEopenSUSE, SLESzypper / .rpmEuropean enterprises, SAP workloads
OthersArch, Amazon Linux, Gentoopacman, dnfWorkstations, AWS, source-based

Key practical differences: package manager and release cadence (Ubuntu LTS every 2 years with 5 years support, RHEL ~10-year lifecycle), security module (SELinux on the RHEL family vs AppArmor on Ubuntu/SUSE), default file locations, and default init/network stacks. Alpine uses musl libc instead of glibc and BusyBox instead of GNU coreutils — a common source of subtle container bugs (DNS resolution differences, missing bash, different date/getopt behavior).

A practical framing of “immutable” and container-optimized distros (Flatcar, Bottlerocket, Talos): the OS is read-only and updated as an atomic image rather than package-by-package. This eliminates configuration drift and is increasingly how Kubernetes node images are built.

Unix (BSD) and Windows

Kernel vs. user space and the system call boundary

The kernel manages hardware, memory, scheduling, and enforces isolation; applications run in user space and request privileged services via system calls (open, read, write, fork, execve, mmap, socket). This boundary is the single most important mental model in OS work: a program cannot touch hardware or another process’s memory directly — it asks the kernel, which decides. strace shows you this boundary live and is often the fastest way to answer “what is this process actually doing / waiting on?”

strace -f -e trace=openat,connect -p <pid>   # what files/sockets is it touching?
strace -c ./program                          # syscall count + time summary (find the hot syscall)

Processes, threads, and scheduling

Key Concepts

Memory management and virtual memory

Each process sees a private virtual address space; the kernel maps pages (typically 4 KB, optionally 2 MB/1 GB “huge pages”) to physical RAM on demand via the MMU, and a page not yet touched costs nothing until first access (demand paging). Consequences you will meet in production:

free -h                        # memory overview (watch "available", not "free")
vmstat 1                       # si/so columns = swap-in/swap-out activity
cat /proc/<pid>/status | grep -E 'VmRSS|VmSwap|VmHWM'
cat /proc/<pid>/oom_score      # how attractive this pid is to the OOM killer
dmesg -T | grep -i 'killed process'   # OOM killer victims with timestamps

Process management and signals

ps aux --sort=-%mem | head     # top memory consumers
ps -ef --forest                # process tree (parent/child relationships)
pgrep -af nginx                # find PIDs by name, show the full command
kill -TERM <pid>               # polite: ask to shut down (default signal)
kill -KILL <pid>               # last resort: cannot be caught or ignored
kill -HUP <pid>                # convention: reload configuration
nice -n 10 ./batch-job         # start with lower CPU priority
renice -n 5 -p <pid>           # change priority of a running process

Know the common signals and, critically, which can be caught: SIGTERM (15, graceful stop — what Kubernetes and systemctl stop send first, catchable), SIGKILL (9, immediate, uncatchable and unblockable), SIGHUP (1, terminal hangup, conventionally “reload config”), SIGINT (2, Ctrl-C), SIGSTOP/SIGCONT (pause/resume, also uncatchable), SIGCHLD (a child changed state). Graceful shutdown — finishing in-flight requests, flushing buffers, closing connections — depends entirely on your application installing a SIGTERM handler. Kubernetes sends SIGTERM, waits terminationGracePeriodSeconds (default 30), then SIGKILL.

File systems and I/O

df -h && df -i                 # space AND inode usage — check both
lsof +D /var/log | head        # who holds files open under a path
lsof -p <pid>                  # everything a process has open (files, sockets, pipes)
iostat -xz 1                   # per-device I/O latency (await) and utilization
mount | column -t | head       # what is mounted where, with which options
du -sh /var/* | sort -h        # find what is eating disk space

The /proc and /sys pseudo-filesystems

/proc and /sys are the kernel exposed as files — no special tools required, just cat. This is where much of the deep introspection happens: /proc/<pid>/ holds a process’s status, cmdline, environ, fd/, limits, and cgroup; /proc/meminfo, /proc/loadavg, /proc/mounts, and /proc/net/ expose system state. /sys exposes device and kernel-object trees. Kernel tunables live under /proc/sys and are set with sysctl (persisted in /etc/sysctl.d/):

sysctl vm.swappiness                       # read a tunable
sysctl -w net.core.somaxconn=1024          # set at runtime (lost on reboot)
cat /proc/1/cgroup                          # which cgroups PID 1 belongs to

Sockets and networking basics

A socket is a communication endpoint identified by protocol + IP + port. TCP is connection-oriented (three-way handshake, retransmission, ordering, flow/congestion control); UDP is connectionless “fire-and-forget” (no delivery guarantee, lower overhead — used by DNS, QUIC, VXLAN). Practical implications you will diagnose constantly:

ss -tlnp                       # listening TCP sockets + owning process
ss -tan state established      # established connections
ss -s                          # socket state summary counts

Startup and service management: init and systemd

PID 1 is the first user-space process the kernel starts, and the ancestor of everything (it also reaps orphaned zombies). Historically SysV init ran ordered shell scripts in /etc/init.d with numeric runlevels. Modern Linux uses systemd, which manages units — services, timers, mounts, sockets, targets — with declarative dependencies, parallel startup, cgroup-based process supervision, socket activation, and centralized logging via journald.

# /etc/systemd/system/myapp.service
[Unit]
Description=My App
After=network-online.target
Wants=network-online.target

[Service]
User=myapp
ExecStart=/usr/local/bin/myapp --config /etc/myapp.yaml
Restart=on-failure
RestartSec=5
# resource limits (same cgroup mechanism containers use)
MemoryMax=512M
CPUQuota=50%
LimitNOFILE=65536
# sandboxing hardening
NoNewPrivileges=true
ProtectSystem=strict
PrivateTmp=true

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload           # re-read unit files after editing
sudo systemctl enable --now myapp      # start now AND on boot
systemctl status myapp                 # state, recent logs, main PID, cgroup
systemctl list-units --failed          # what is broken right now
journalctl -u myapp -f --since "10 min ago"   # follow this unit's logs
journalctl -p err -b                   # error-priority messages, this boot
systemd-analyze blame                  # slowest units at boot
systemctl mask <unit>                  # forcibly prevent a unit from starting

Related concepts: targets (multi-user.target, graphical.target) replace runlevels; systemd timers are a more observable, dependency-aware alternative to cron (they log to the journal and can catch missed runs with Persistent=true); socket activation starts a service on first connection; and Restart= policies replace hand-rolled supervisor scripts. The MemoryMax/CPUQuota limits use the same cgroups v2 mechanism that containers use — systemd and Kubernetes are, at this layer, doing the same thing.

Containers are OS features, not magic

It is worth stating explicitly because it demystifies so much: a Linux container is a normal process that the kernel has isolated using namespaces (separate views of PIDs, mounts, network interfaces, users, hostnames, IPC) and constrained using cgroups (limits and accounting for CPU, memory, I/O, PIDs). There is no “container” object in the kernel — Docker and containerd just orchestrate clone() with namespace flags, set up an overlayfs root, apply cgroup limits, and (usually) a seccomp/AppArmor profile. This is why every OS concept above applies directly inside containers, and why nsenter, /proc/<pid>/ns, and reading a container’s cgroup files are core debugging skills.

Best Practices

  1. Learn one distro from each major family (Ubuntu + a RHEL clone like Rocky/Alma). Package management, security tooling (SELinux vs AppArmor), and defaults differ; the underlying concepts transfer completely.
  2. Diagnose from the OS up, not the app down. During incidents check the four fundamentals — CPU, memory, disk, network (top, free -h, df -h/df -i, ss) — before diving into application logs. The OS often already knows the answer.
  3. Treat “available” memory, not “free”, as your headroom. Page cache is reclaimable on demand; alarms on “free” memory create false pages and hide real leaks.
  4. Always send SIGTERM before SIGKILL, and build applications that shut down gracefully on SIGTERM — Kubernetes, systemd, and docker stop all rely on this contract. A missing SIGTERM handler means dropped requests and corrupt state on every deploy.
  5. Run services under systemd (or the platform’s supervisor), never nohup ... &. You get automatic restarts, log capture, dependency ordering, resource limits, and sandboxing for free — and a process that survives your SSH session ending.
  6. Set explicit resource limits (ulimit/LimitNOFILE, MemoryMax, container requests/limits) so one runaway process cannot starve or crash the whole host. Unlimited is a decision, usually the wrong one.
  7. Monitor inodes and file descriptors, not just disk bytes. df -i running out and fd exhaustion take systems down while df -h and dashboards still look green.
  8. Keep systems patched on a schedule — enable unattended security updates where safe, track your kernel-reboot strategy (or use livepatch/kexec), and know your CVE exposure. Unpatched kernels are the most common privilege-escalation vector.
  9. Don’t disable SELinux/AppArmor reflexively. Learn audit2allow/aa-logprof to fix the actual denial. A disabled LSM is a permanent security regression traded for a one-time inconvenience.
  10. Use journalctl and dmesg as first-class incident tools. OOM kills, disk errors, segfaults, and kernel warnings land there, not in application logs. dmesg -T for human timestamps.
  11. Understand your file system before you choose it. Snapshots and checksums (ZFS/Btrfs), large-file and parallel-I/O behavior (XFS), and fsync durability semantics directly determine database and backup safety.
  12. Tune kernel parameters deliberately and persist them. File descriptor limits, vm.swappiness, net.core.somaxconn, net.ipv4.tcp_tw_reuse, ephemeral port range — set them in /etc/sysctl.d/ (not just at runtime) with a comment explaining why.
  13. Right-size before you scale out. Read vmstat/iostat/sar to learn whether a workload is CPU-, memory-, or I/O-bound; the bottleneck dictates the instance type and whether more replicas even help.
  14. Prefer immutable, image-based nodes (Bottlerocket/Flatcar/Talos, or golden AMIs) over long-lived pets you patch in place. Replaceable infrastructure eliminates configuration drift.
  15. Understand the namespace/cgroup basis of containers. When a pod is OOM-killed, throttled, or “cannot see” the network, the answer is in the OS primitives, not in Kubernetes abstractions.
  16. Practice on disposable VMs. Break things deliberately — fill a disk, trigger the OOM killer, exhaust file descriptors, mask a unit, saturate CPU — so the first time you see the failure signature is not in production.

References