Skip to content

Vận hành LCOJ

Các thao tác hằng ngày với một bản cài LCOJ bằng Docker: bật/tắt, xem log, áp dụng cấu hình, xóa cache, sao lưu và khôi phục.

⏱ ~15 phút đọc; sao lưu/khôi phục tùy dung lượng dữ liệu · 👤 Người vận hành · 🔑 SSH + quyền chạy docker trên máy chủ

TIP

Mọi lệnh trong trang này chạy từ thư mục lcoj-docker/dmoj/. Danh sách service và cổng xem ở Kiến trúc hệ thống, các script trong scripts/ xem ở Script hỗ trợ.

Trước khi bắt đầu

Bật, tắt và khởi động lại

Việc cần làmLệnh
Bật toàn bộ (tạo lại container nếu cấu hình đổi)docker compose up -d
Xem trạng tháidocker compose ps
Khởi động lại một servicedocker compose restart site
Tạm dừng / bật lại một servicedocker compose stop site / docker compose start site
Tắt và xóa container, giữ dữ liệudocker compose down

Service base chỉ dùng để build image và luôn ở trạng thái đã thoát. Đó không phải lỗi.

docker compose down -v xóa những gì?

Cờ -v xóa các named volume khai báo trong docker-compose.yml. Dữ liệu chính của LCOJ nằm trong thư mục bind mount trên máy chủ nên không bị xóa.

Dữ liệuNơi lưudown -v có xóa?
Cơ sở dữ liệu./database/ (bind mount)Không
Dữ liệu test./problems/ (bind mount)Không
File tải lên./media/ (bind mount)Không
Mã nguồn, cấu hình./repo/, ./environment/, ./nginx/Không
CSS/JS đã build, staticvolume assets, tạo lại bằng ./scripts/copy_static
File tải dữ liệu người dùng / kỳ thivolume userdatacache, contestdatacache, người dùng phải yêu cầu tạo lại
Cache dùng chung site–nginxvolume cache
Dữ liệu Redis (cache, hàng đợi Celery)volume ẩn danh của redis

DANGER

Sau docker compose down -v, trang web sẽ mất CSS cho đến khi bạn chạy lại ./scripts/copy_static. Muốn xóa sạch cơ sở dữ liệu thì phải xóa thư mục ./database/. Việc này không thể hoàn tác, hãy sao lưu trước.

Xem log

sh
docker compose logs -f site            # theo dõi log site
docker compose logs --tail=100 celery  # 100 dòng cuối
docker compose logs --since 1h bridged # log trong 1 giờ qua
docker compose logs -f                 # tất cả service

Service cần xem theo từng triệu chứng:

Triệu chứngService
Lỗi 500, trang không tảisite
Tác vụ nền (chấm lại, xuất dữ liệu) bị treocelery
Máy chấm không kết nối, bài nộp đứng ở trạng thái chờbridged
Kết quả không tự cập nhật trên trangwsevent
Lỗi 502, file tĩnh 404nginx

Vào bên trong container

sh
./scripts/enter_site               # shell bash trong container site
./scripts/manage.py dbshell        # shell SQL bằng tài khoản của site
./scripts/manage.py <lệnh>         # chạy lệnh quản trị Django

Mở shell MariaDB bằng root mà không phải gõ mật khẩu ra dòng lệnh (biến môi trường có sẵn trong container db):

sh
docker compose exec db sh -c 'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" mariadb -u root "$MYSQL_DATABASE"'

INFO

Service db dùng image mariadb (bản mới nhất). Hãy dùng các lệnh mariadb, mariadb-dump, mariadb-admin, mariadb-check. Từ MariaDB 11, image chính thức không còn kèm các tên cũ mysql, mysqldump

Danh sách lệnh quản trị xem Management Commands.

Áp dụng thay đổi cấu hình

Bạn sửaCần làm
environment/*.envdocker compose up -d (lệnh restart không nạp lại file env)
repo/dmoj/local_settings.pydocker compose restart site celery bridged
repo/uwsgi.inidocker compose restart site
repo/websocket/config.jsdocker compose restart wsevent
nginx/conf.d/nginx.confdocker compose restart nginx
SCSS, JS, ảnh trong repo/resources/, file dịch./scripts/copy_static rồi docker compose restart site
Code Python, templatedocker compose restart site celery bridged

Code không cần build lại image vì thư mục ./repo được mount thẳng vào container. Khi nào cần build lại, xem Cập nhật LCOJ.

Cache

LCOJ dùng Redis: database số 0 cho cache Django (REDIS_CACHING_URL), database số 1 cho hàng đợi Celery (CELERY_BROKER_URL). Không có lệnh clear_cache. Để xóa cache, dùng một trong hai cách:

sh
docker compose exec site python3 manage.py shell -c "from django.core.cache import cache; cache.clear()"
sh
docker compose exec redis redis-cli -n 0 FLUSHDB

WARNING

Đừng dùng FLUSHALL. Lệnh này xóa cả database số 1, làm mất các tác vụ Celery đang chờ.

Celery

sh
docker compose exec celery celery -A dmoj_celery inspect active     # tác vụ đang chạy
docker compose exec celery celery -A dmoj_celery inspect scheduled  # tác vụ đã hẹn giờ
docker compose restart celery

Celery chạy với --concurrency=2 (đặt trong celery/Dockerfile).

Sao lưu

Cần sao lưu bốn thứ:

Thành phầnVị tríGhi chú
Cơ sở dữ liệucontainer dbDump bằng mariadb-dump khi đang chạy, không chép thô thư mục database/
Dữ liệu testproblems/Thường là phần lớn nhất
File tải lênmedia/Ảnh, PDF, file đính kèm
Cấu hìnhenvironment/*.env, repo/dmoj/local_settings.py, repo/uwsgi.ini, repo/websocket/config.js, nginx/conf.d/, cùng .envdocker-compose.override.yml nếu bạn có tạoChứa bí mật, hãy lưu ở nơi an toàn. Cấu hình reverse proxy trên host (/etc/caddy/Caddyfile hoặc /etc/nginx/sites-available/) nằm ngoài dmoj/, hãy lưu riêng

Luồng sao lưu và khôi phục tổng quát:

Sao lưu thủ công

  1. Dump cơ sở dữ liệu:

    sh
    mkdir -p backups
    docker compose exec -T db sh -c \
      'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" exec mariadb-dump -u root --single-transaction "$MYSQL_DATABASE"' \
      | gzip > backups/db_$(date +%F_%H%M).sql.gz
  2. Nén dữ liệu test, file tải lên và cấu hình:

    sh
    tar -czf backups/files_$(date +%F_%H%M).tar.gz \
      problems media environment nginx/conf.d \
      repo/dmoj/local_settings.py repo/uwsgi.ini repo/websocket/config.js
  3. Chép thư mục backups/ sang máy khác hoặc kho lưu trữ ngoài. Bản sao lưu nằm cùng máy chủ không giúp được gì khi hỏng ổ đĩa.

WARNING

Thư mục dmoj/backups/ chứa mật khẩu và không nằm trong .gitignore. Hãy thêm nó vào .gitignore (hoặc lưu bản sao lưu ngoài repo) để không lỡ commit.

Sao lưu tự động

Lưu script sau thành dmoj/backup.shchmod +x:

bash
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"          # thư mục dmoj/

DEST=backups
STAMP=$(date +%F_%H%M)
mkdir -p "$DEST"

docker compose exec -T db sh -c \
  'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" exec mariadb-dump -u root --single-transaction "$MYSQL_DATABASE"' \
  | gzip > "$DEST/db_$STAMP.sql.gz"

tar -czf "$DEST/files_$STAMP.tar.gz" \
  problems media environment nginx/conf.d \
  repo/dmoj/local_settings.py repo/uwsgi.ini repo/websocket/config.js

# Giữ bản sao lưu trong 7 ngày
find "$DEST" -type f -mtime +7 -delete

Chạy lúc 2 giờ sáng hằng ngày (crontab -e):

cron
0 2 * * * /đường/dẫn/tới/lcoj-docker/dmoj/backup.sh >> /var/log/lcoj_backup.log 2>&1

Cờ -T trong docker compose exec là bắt buộc khi chạy từ cron vì không có terminal.

Khôi phục

Trên máy chủ đang chạy

  1. Dừng các service ghi vào cơ sở dữ liệu, giữ db chạy:

    sh
    docker compose stop site celery bridged
  2. Nạp lại dump (ghi đè các bảng hiện có):

    DANGER

    Bước này thay dữ liệu hiện tại bằng dữ liệu trong bản sao lưu.

    sh
    gunzip -c backups/db_2026-09-19_0200.sql.gz | docker compose exec -T db sh -c \
      'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" exec mariadb -u root "$MYSQL_DATABASE"'
  3. Nếu cần, giải nén file (chạy trong dmoj/, đường dẫn trong file nén là tương đối):

    sh
    tar -xzf backups/files_2026-09-19_0200.tar.gz
  4. Bật lại và kiểm tra:

    sh
    docker compose up -d
    ./scripts/migrate          # chỉ cần nếu code mới hơn bản sao lưu

Sang máy chủ mới

  1. Làm Bước 1–2 của Cài đặt: cài Docker, clone repo.

  2. Trong dmoj/, giải nén file sao lưu. Thao tác này khôi phục problems/, media/, environment/, cấu hình nginx và các file cấu hình trong repo/ (không cần chạy initialize).

  3. Build image: docker compose build base && docker compose build.

  4. Bật db với thư mục database/ trống để MariaDB tạo database và user từ mysql.env:

    sh
    docker compose up -d db
    docker compose logs -f db   # đợi "ready for connections"
  5. Nạp dump như bước 2 ở trên.

  6. Bật phần còn lại, tạo lại static:

    sh
    docker compose up -d site celery
    ./scripts/migrate
    ./scripts/copy_static
    docker compose up -d
  7. Trỏ bản ghi DNS sang IP của máy chủ mới và cài lại reverse proxy HTTPS trên host, xem Cài đặt: HTTPS trên VPS.

Trang bảo trì

Nginx đã cấu hình error_page 502 504 /502.html. Khi site bị dừng, người dùng sẽ thấy trang này thay vì lỗi trống. Vì vậy cách bật "chế độ bảo trì" đơn giản nhất là:

sh
docker compose stop site      # người dùng thấy trang 502.html
# ... bảo trì ...
docker compose start site

Nội dung trang nằm ở repo/502.html. Sau khi sửa, chạy ./scripts/copy_static để chép sang volume assets.

Đổi mật khẩu cơ sở dữ liệu

  1. Đổi mật khẩu trong MariaDB (thay dmoj nếu MYSQL_USER của bạn khác):

    sh
    docker compose exec db sh -c 'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" mariadb -u root'
    sql
    ALTER USER 'dmoj'@'%' IDENTIFIED BY '<mật khẩu mới>';
  2. Sửa MYSQL_PASSWORD trong environment/mysql.env.

  3. Tạo lại container để chúng nhận mật khẩu mới:

    sh
    docker compose up -d

Kiểm tra kết quả

Sau khi sao lưu:

  • ls -lh backups/ có file db_*.sql.gzfiles_*.tar.gz mới, dung lượng khác 0.
  • gunzip -t backups/db_<thời điểm>.sql.gz không báo lỗi (file nén còn nguyên vẹn).
  • tar -tzf backups/files_<thời điểm>.tar.gz | head liệt kê được problems/, media/, environment/

Sau khi bật/tắt, đổi cấu hình hoặc khôi phục:

  • docker compose ps cho thấy các service đang chạy (base ở trạng thái đã thoát là bình thường).
  • Trang chủ tải được, có CSS; đăng nhập được; nộp thử một bài và thấy kết quả tự cập nhật.

TIP

Thỉnh thoảng hãy thử khôi phục bản sao lưu lên một máy thử. Bản sao lưu chưa từng được khôi phục thử thì chưa chắc dùng được.

Sự cố thường gặp

Triệu chứngCách xử lý
Mất CSS, file tĩnh 404./scripts/copy_static && docker compose restart nginx
Lỗi kết nối cơ sở dữ liệudocker compose ps db, docker compose logs db, kiểm tra environment/mysql.env
Tác vụ Celery bị treodocker compose logs -f celery, rồi docker compose restart celery
Kết quả chấm không tự cập nhậtdocker compose ps wsevent, kiểm tra EVENT_DAEMON_POST. Nếu site chạy HTTPS, kiểm tra SECURE_PROXY_SSL_HEADER (Cài đặt)
Container khởi động lại liên tụcdocker compose logs --tail=100 <service>
Đầy ổ đĩadocker image prune, docker builder prune, kiểm tra dung lượng problems/backups/

Tiếp theo

Cần hỗ trợ?

Tạo issue tại lcoj-docker, hoặc liên hệ qua behitek.comluyencode.net/about/#lien-he.