~/blog / disk-toldi-tekshiruvback

Disk 100% — tinch boshda ishlatiladigan tekshiruv ro'yxati

9 daqiqa o'qish

Deploy yarmida to'xtadi, Postgres esa logga shuni yozyapti:

text
ERROR:  could not extend file "base/5/16384": No space left on device
HINT:  Check free disk space.

Boshqa dasturlarda xato boshqacha ko'rinadi — touch: cannot touch ..., dd: error writing ..., Python'da OSError: [Errno 28] — lekin sababi bitta: yadro yozish so'roviga ENOSPC qaytardi. Bunday paytda qo'l birinchi bo'lib rm -rf yoki docker system prune -af ga boradi. Ikkalasi ham joy bo'shatishi mumkin, lekin kerakli narsani ham o'chirib yuborishi mumkin.

Quyidagi ro'yxat tartib bilan boradi: avval nima tugaganini aniqlaymiz, keyin joy qayerdaligini topamiz, xavfsiz bo'shatamiz va takrorlanmasligi uchun chegara qo'yamiz. Chiqishlar shu maqola uchun olingan: disk misollari Debian 12 konteyneri ichida atayin to'ldirilgan 64 MB'lik ext4 fayl tizimida (/mnt/demo), Docker misollari toza Docker Engine 27 da, yuqoridagi Postgres xatosi esa kichik tmpfs'dagi postgres:16-alpine da. Raqamlar kichik, format esa serverdagi bilan bir xil.

Ro'yxat bir qarashda#

SavolBuyruq
Nima tugagan?df -h, df -i
Joy qayerda?du -xh --max-depth=1 / | sort -h, find / -xdev -type f -size +500M
O'chirilgan, lekin ochiq fayllarlsof +L1
systemd jurnalijournalctl --disk-usage
Dockerdocker system df, keyin aniq nishonli prune
Konteyner loglaridu -h /var/lib/docker/containers/*/*-json.log
Takrorlanmasinmax-size, SystemMaxUse=, logrotate

1. Nima tugagan: bloklar yoki inode'lar#

Fayl tizimida ikki xil resurs bor: ma'lumot bloklari va inode'lar (har bir fayl, katalog va symlink uchun bittadan yozuv). Qaysi biri tugasa ham xato bir xil — No space left on device. Shuning uchun birinchi ikki buyruq doim birga ishlatiladi:

console
$ df -h /mnt/demo
Filesystem      Size  Used Avail Use% Mounted on
/dev/loop0       59M   70K   54M   1% /mnt/demo
$ df -i /mnt/demo
Filesystem     Inodes IUsed IFree IUse% Mounted on
/dev/loop0       2048  2048     0  100% /mnt/demo

Joy 1% band, lekin yangi fayl yaratib bo'lmaydi: touch: cannot touch 'sess_2035': No space left on device. Faqat df -h ga qaragan odam bu yerda uzoq adashadi. Inode'larni mayda fayllar yeydi: sessiyalar, kesh, hech kim tozalamaydigan navbat katalogi. Qaysi katalog ekanini du --inodes aytadi:

console
$ du --inodes -x --max-depth=1 /mnt/demo/app | sort -n
1	/mnt/demo/app/logs
2035	/mnt/demo/app/sessions
2037	/mnt/demo/app

Serverda / dan boshlang, eng kattasiga kiring va takrorlang. ext4'da inode'lar soni mkfs paytida belgilanadi, fayl tizimini kattalashtirmasdan uni oshirib bo'lmaydi. Demak yechim — fayllarni o'chirish va ularni yaratayotgan narsaga muddat qo'yish. Millionlab faylda rm sess_* Argument list too long bilan to'xtaydi. find /path -type f -mmin +1440 -delete kabi buyruq esa bir kundan eski fayllarni birma-bir o'chiradi — avval uni -delete siz ishga tushirib, ro'yxatga qarang.

2. Joy qayerda: du bilan pastga tushish#

console
$ du -xh --max-depth=1 /mnt/demo/app | sort -h
54K	/mnt/demo/app/sessions
57M	/mnt/demo/app
57M	/mnt/demo/app/logs

Serverda xuddi shu buyruq / bilan, root huquqida ishlatiladi: sudo du -xh --max-depth=1 / 2>/dev/null | sort -h. sudo siz o'qib bo'lmagan kataloglar hisobga kirmaydi, 2>/dev/null esa buni yashiradi. -x muhim, chunki u boshqa fayl tizimlariga (/proc, alohida disklar, Docker'ning overlay mount'lari) o'tmaydi va natija df ko'rsatgan bitta fayl tizimi bo'yicha chiqadi. sort -h esa 54K bilan 57M ni to'g'ri taqqoslaydi. Katta fayllarni to'g'ridan-to'g'ri ham qidirish mumkin:

console
$ find /mnt/demo -xdev -type f -size +20M -exec ls -lh {} +
-rw-r--r-- 1 root root 57M Sep 21 09:07 /mnt/demo/app/logs/api.log

Haqiqiy serverda chegarani kattaroq qo'ying: sudo find / -xdev -type f -size +500M -exec ls -lh {} +. df, du va sort qo'l xotirasiga o'tishi uchun Linux asoslari bosqichi bor.

3. df va du kelishmasa: o'chirilgan, lekin ochiq fayl#

57M lik log topildi va o'chirildi. df esa o'zgarmadi:

console
$ rm /mnt/demo/app/logs/api.log
$ df -h /mnt/demo
Filesystem      Size  Used Avail Use% Mounted on
/dev/loop0       59M   57M     0 100% /mnt/demo
$ du -sh /mnt/demo
69K	/mnt/demo

rm faylning o'zini emas, katalogdagi nomini o'chiradi. Bloklar faylga birorta ham nom qolmaganda va uni hech bir jarayon ochiq tutmaganda bo'shaydi. Log yozayotgan jarayon hali ishlayapti va fayl deskriptori orqali yozishda davom etadi, du esa nomi yo'q faylni ko'rmaydi. Bunday fayllarni lsof +L1 ko'rsatadi (havolalar soni 1 dan kam, ya'ni 0):

console
$ lsof +L1
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NLINK NODE NAME
bash    5636 root    3w   REG    7,0 59637760     0   15 /mnt/demo/app/logs/api.log (deleted)
sleep   5650 root    3w   REG    7,0 59637760     0   15 /mnt/demo/app/logs/api.log (deleted)

Ikki qator, fayl esa bitta: sinovdagi yozuvchi bash va uning bolasi sleep, deskriptor bolaga meros o'tgan. Master va worker jarayonli servislarda ham shunday bo'ladi — joy hammasi faylni yopgandagina bo'shaydi. lsof o'rnatilmagan bo'lsa, xuddi shu ma'lumot /proc da bor: find /proc/*/fd -lname '*(deleted)' 2>/dev/null.

To'g'ri yo'l — jarayonga faylni qayta ochirish: servisni qayta ishga tushirish yoki uning log'ni qayta ochish buyrug'i (nginx'da nginx -s reopen). Buning iloji bo'lmasa, faylni deskriptor orqali qisqartiring:

console
$ ls -l /proc/5636/fd/3
l-wx------ 1 root root 64 Sep 21 09:07 /proc/5636/fd/3 -> /mnt/demo/app/logs/api.log (deleted)
$ : > /proc/5636/fd/3
$ df -h /mnt/demo
Filesystem      Size  Used Avail Use% Mounted on
/dev/loop0       59M   70K   54M   1% /mnt/demo

: > faylni nolga qisqartiradi: jarayon yozishda davom etadi, eski mazmun esa yo'qoladi. Jarayon faylni O_APPEND siz ochgan bo'lsa, u eski joyidan yozishda davom etadi va fayl "teshikli" (sparse) bo'lib qoladi: ls -l katta hajm ko'rsatadi, du va df esa haqiqiy bandlikni.

4. systemd jurnali#

Doimiy saqlash yoqilgan bo'lsa, journald loglari /var/log/journal da jim o'sadi. Qancha joy olganini bitta buyruq aytadi, javobi bitta gap: Archived and active journals take up … in the file system.

bash
sudo journalctl --disk-usage
sudo journalctl --vacuum-size=500M
sudo journalctl --vacuum-time=2weeks

Vacuum faqat arxivlangan fayllarni o'chiradi, hozir yozilayotganlariga tegmaydi, shuning uchun undan keyin ham --disk-usage chegaradan katta chiqishi mumkin. sudo journalctl --rotate --vacuum-size=500M avval faol fayllarni arxivga o'tkazadi, keyin tozalaydi.

Doimiy chegara journald.conf da. Standart qiymat fayl tizimi hajmining 10% i, lekin 4G dan oshmaydi — bu siz kutgan raqam bo'lmasligi mumkin. Asosiy faylni tahrirlash o'rniga drop-in yarating va sudo systemctl restart systemd-journald qiling:

ini
# /etc/systemd/journald.conf.d/size.conf
[Journal]
SystemMaxUse=500M

5. Docker: avval o'lchang, keyin tozalang#

docker system df image'lar, konteynerlar, volume'lar va build cache qancha joy olganini ko'rsatadi. RECLAIMABLE ustuni — hozir ishlatilmayotgan va o'chirsa bo'ladigan qism. docker system df -v har birini alohida sanaydi. Tozalashdan oldin prune nimani olishini aniq biling — tasdiqlash so'rovining o'zi aytadi:

BuyruqNimani o'chiradi
docker system pruneto'xtagan konteynerlar, ishlatilmayotgan tarmoqlar, tegsiz (dangling) image'lar, ishlatilmayotgan build cache
docker system prune --volumesyuqoridagilar + hech bir konteynerga ulanmagan anonim volume'lar
docker system prune -ato'xtagan konteynerlar va tarmoqlar + konteyneri yo'q hamma image'lar (faqat tegsizlari emas) va butun build cache
docker volume prune -aishlatilmayotgan nomlangan volume'lar ham

Sinov engine'ida to'xtab turgan olddb konteyneri (nomlangan pgdata va anonim /cache volume'lari bilan) va hech bir konteyner ishlatmayotgan app:v1 image'i (hamda uning oldingi, tegsiz qolgan build'i) bor edi:

console
$ docker system prune -af --volumes
Deleted Containers:
899687516bdb07d2a46cb8f9c59897485b8466a07b2e4821eb48ec657879f23c

Deleted Volumes:
7eb89eae3fa5d74c18b650e02588f5144ea0136482a7efbc6e2e07601f09add0

Deleted Images:
deleted: sha256:917dddb76f6bfdd6a08f8597e9dab85b8145168f0c81d6fc5de0b570a02af724
untagged: app:v1
deleted: sha256:7d7f9a7f3c03be74974113af80386ed17267529faec8ad98a526fd35ae741894

Deleted build cache objects:
krfawd42ert9lt5fpdxli04zj
80owrfe51aj6sscfqosjn6052
lvz9a6n8e9ue0za6jwtl7lfbz
yulbway8falwfv14urswskamb
0h7vztuzpakls14tj8e6de4fm

Total reclaimed space: 40B
$ docker volume ls
DRIVER    VOLUME NAME
local     pgdata

Nomlangan pgdata qoldi — hozirgi Docker'da --volumes faqat anonim volume'larni oladi. Qolgani ketdi, va bu yerda xavf bor:

  • To'xtab turgan konteyner va uning anonim volume'i bitta buyruqda o'chadi. Compose faylida baza uchun nomlangan volume yozilmagan bo'lsa, postgres image'i ma'lumotni aynan anonim volume'da saqlaydi.
  • -a orqaga qaytish uchun saqlab qo'yilgan eski image'larni ham o'chiradi. Rollback kerak bo'lsa, ularni qayta tortish yoki qayta qurish kerak bo'ladi.
  • -f tasdiqlash so'rovini o'chiradi. -af --volumes ni cron'ga qo'yishdan oldin shu ro'yxatni yana bir o'qing.

Aniq nishonli buyruqlar xavfsizroq: docker image prune faqat tegsiz image'larni, docker builder prune faqat build cache'ni o'chiradi. docker image prune -a --filter "until=168h" 7 kundan oldin yaratilgan image'larni oladi — oxirgi marta qachon ishlatilganiga qaramaydi.

6. Konteyner loglari#

Docker'ning standart json-file drayverida max-size qiymati -1, ya'ni chegara yo'q. Eng katta loglar:

bash
sudo sh -c 'du -h /var/lib/docker/containers/*/*-json.log | sort -h | tail -5'

sh -c shart: * ni shell kengaytiradi, oddiy foydalanuvchining shell'i esa /var/lib/docker/containers ichini o'qiy olmaydi. Fayl yo'lidagi ID ni konteynerga docker inspect --format '{{.LogPath}}' <nom> bilan bog'lang. Log sozlamasini tekshirish:

console
$ docker inspect --format '{{json .HostConfig.LogConfig}}' api
{"Type":"json-file","Config":{}}
$ docker inspect --format '{{json .HostConfig.LogConfig}}' capped
{"Type":"json-file","Config":{"max-file":"3","max-size":"10m"}}

Bo'sh Config chegara yo'qligini bildiradi. *-json.log ni rm qilmang: 3-bo'limdagi holat takrorlanadi. Sinovda dockerd o'chirilgan faylni ochiq ushlab turdi, joy bo'shamadi, docker logs esa Error grabbing logs: … no such file or directory bilan to'xtadi. Shoshilinch holatda faylni qisqartirish mumkin: sudo truncate -s 0 "$(docker inspect --format '{{.LogPath}}' api)". Lekin loglar yo'qoladi, Docker hujjati bu fayllarga tashqi vositalar bilan tegmaslikni so'raydi, sinovda esa qisqartirishdan keyin docker logs --tail 2 bo'sh qaytdi. Bu faqat vaqt yutish. Asl yechim — chegara:

yaml
services:
  api:
    image: python:3.12-alpine
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

Yoki butun server uchun /etc/docker/daemon.json:

json
{
  "log-driver": "local",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

local drayveri sozlamasiz ham har konteyner uchun 100MB gacha (5 ta 20M fayl) saqlaydi va eski fayllarni siqadi. Lekin u boshqa formatda yozadi: *-json.log ni fayldan o'qiydigan log agentingiz bo'lsa, json-file + max-size da qoling. log-opts qiymatlari, hatto max-file ham, qo'shtirnoq ichida bo'lishi shart. Ikki tafsilot bor:

  • daemon.json Docker qayta ishga tushgandan keyin faqat yangi konteynerlarga ta'sir qiladi. Mavjud konteynerlar eski sozlamada qoladi, ularni qayta yaratish kerak (docker compose up -d --force-recreate).
  • live-restore yoqilmagan bo'lsa, systemctl restart docker hamma konteynerlarni to'xtatadi. Buni ish vaqtida emas, rejalashtirilgan oynada qiling.

7. Fayl loglari: logrotate#

Ilova yoki host'dagi nginx loglarni faylga yozsa, ularni logrotate aylantiradi:

text
# /etc/logrotate.d/myapp
/var/log/myapp/*.log {
    daily
    rotate 14
    maxsize 500M
    compress
    delaycompress
    missingok
    notifempty
    copytruncate
}

Bu konfiguratsiya har kuni aylantiradi, 14 ta eski faylni saqlab, ularni siqadi (delaycompress eng yangi eski faylni keyingi aylanishgacha siqmay turadi). maxsize 500M bo'lsa, fayl kun tugamasdan 500M dan oshganda ham aylantiriladi.

copytruncate ilova faylni qayta ochishni bilmagan holat uchun: logrotate nusxa oladi va asl faylni nolga qisqartiradi, lekin shu ikki qadam orasida yozilgan qatorlar yo'qolishi mumkin. Ilova qayta ochishni bilsa (nginx USR1 signalida biladi), copytruncate o'rniga postrotate blokida signal bering. Ikkalasi ham bo'lmasa, jarayon eski faylga yozishda davom etadi va u oxir-oqibat o'chirilgach, 3-bo'limdagi holat qaytadi.

sudo logrotate -d /etc/logrotate.d/myapp hech narsani o'zgartirmaydi, faqat nima qilishini ko'rsatadi. Debian 12'da logrotate kuniga bir marta logrotate.timer orqali ishlaydi, maxsize ham faqat shu paytda tekshiriladi. Soatiga gigabayt yozadigan logga bu yetmaydi — log darajasini tuzating (Prod'da LOG_LEVEL=debug).

Joy bo'shagach: tekshiruv#

  1. df -h va df -i — ikkalasi ham, faqat birinchisi emas.
  2. lsof +L1 bo'sh yoki unda kutilmagan katta fayl yo'q.
  3. Servisning o'zi yoza oladi: baza logida yangi No space left on device yo'q, ilova healthcheck'dan o'tadi.
  4. Har bir servisning docker inspect dagi LogConfig ida max-size bor.
  5. journalctl --disk-usage chegaraga mos.
  6. Monitoringda disk va inode uchun ogohlantirish 100% dan ancha oldin chalinadi.

Amaliyot#

Quyidagi lab'larda loglar cheksiz o'sadi — chegarani qo'ying va server tekshiruvidan o'tkazing:

labosonDisk to'ldi: 40GB log`/var/lib/docker/containers` ostida bitta 40GB `-json.log`. Chegara yo'q edi.mashqni ochish labosonProksi loglari — eng tez o'sadigan faylHar so'rov bir qator log. Kuniga million so'rov — cheklovsiz bu 200MB.mashqni ochish labqiyinBitta konteyner butun serverni yiqitmasinXotira chegarasi yo'q, log fayllari cheksiz o'sadi.mashqni ochish

journalctl --disk-usage, --vacuum-size va boshqa systemd buyruqlarini qo'l xotirasiga o'tkazish uchun:

buyruq mashqi9-bosqich · 16 vazifaSystemd va jurnalXizmatlar nega turmayapti, kim qachon yiqilgan, disk nega to'lgan — hammasi systemd va journald'da.bosqichni boshlash