Claude cho sysadmin: 6 việc server dùng hằng tuần
Xem 6 việc Claude làm tốt trên server: đọc log unit lỗi, viết systemd, rà nginx và Compose. Biết 4 nhóm dữ liệu tuyệt đối không được dán.
Claude cho sysadmin: tư vấn trước, thực thi sau
Claude hoạt động tốt nhất với vai trò reviewer cho sysadmin. Bạn dán một đoạn log, file cấu hình, command không nhận ra hoặc chuỗi lỗi, rồi nhận lại phần giải thích để kiểm tra trước khi thay đổi bất kỳ thứ gì trên server. Câu trả lời sai không gây thiệt hại cho đến khi bạn chạy nó. Vì vậy, giữ model ở phía tư vấn là toàn bộ mô hình an toàn.
Mỗi tuần, có 6 công việc thường gặp trên một Linux VPS (virtual private server) thuê ngoài. Mỗi công việc dưới đây có một mẫu prompt hiệu quả, command dùng để xác minh câu trả lời và failure mode bạn nên dự đoán. Không công việc nào yêu cầu model truy cập server của bạn.
Thứ tự này rất quan trọng trên production box: đọc phần giải thích, tự chạy bước kiểm tra, rồi mới quyết định. Bạn có thể dùng autonomy trên scratch VM. Với server đang phục vụ khách hàng, review luôn an toàn hơn, vì model không thể thấy trạng thái thực tế mà nó đang suy đoán.
Những nội dung tuyệt đối không được dán
Mọi nội dung trong prompt đều rời khỏi server của bạn. Có 4 nhóm phải giữ lại trên máy:
- Private key:
~/.ssh/id_ed25519,/etc/ssh/ssh_host_*_keyvà mọi TLS (transport layer security) key trong/etc/letsencrypt/live/. - File thông tin xác thực:
.env,~/.aws/credentials,/root/.docker/config.jsonvà password database trong bất kỳ file hoặc dòng log nào. - Dữ liệu tài khoản:
/etc/shadowvà/etc/gshadow. Không có câu hỏi sysadmin nào cần password hash để trả lời. - Mọi dữ liệu thuộc về user của bạn: địa chỉ email, các dòng đơn hàng, request log chứa session cookie hoặc PII (personally identifiable information).
Public key an toàn để dán. Private key thì không. Hai file này nhìn khá giống nhau, vì vậy hãy đọc dòng đầu tiên trước khi copy: file có dòng đầu chứa BEGIN OPENSSH PRIVATE KEY tuyệt đối không được đưa vào prompt. Phân biệt đúng các file SSH key đáng để dành riêng 10 phút.
Hãy redact trước khi dán, thay vì tin rằng bạn có thể tự phát hiện một token trong 200 dòng:
sudo journalctl -u myapp -n 200 --no-pager | sed -E 's/(token|secret|password|api[_-]?key)[=:][^[:space:]]+/\1=REDACTED/gI'Docker có một bẫy riêng. docker compose config nội suy các giá trị .env của bạn vào output mà nó in ra, nên output đó là secret dù file trên disk không phải secret. Hãy dùng docker compose config -q; lệnh này chỉ validate và không in gì. Để xem chính sách rộng hơn về những gì agent được phép xem, giữ secret khỏi AI agent trình bày phần liên quan đến environment.
Job 1: vì sao service này bị lỗi?
Bắt đầu bằng 2 lệnh chứa câu trả lời:
systemctl status myapp.service --no-pager
sudo journalctl -u myapp.service -b -n 100 --no-pager -o short-isoDán cả 2 lệnh, kèm theo những thông tin model không thể tự đoán: bản phân phối và phiên bản, thay đổi gần nhất bạn đã thực hiện, service đã từng chạy được hay chưa, và nó bị lỗi cách đây bao lâu. Trước tiên, hãy hỏi về cơ chế gây lỗi.
Ubuntu 24.04.myapp.servicevẫn chạy bình thường cho đến khi tôi sửa unit cách đây 1 giờ. Đây làsystemctl statusvà 100 dòng journal cuối cùng. Dòng nào là lỗi thực sự đầu tiên, và nó có nghĩa gì? Chưa cần đưa ra cách khắc phục.
Cụm "chưa cần đưa ra cách khắc phục" có tác dụng rõ ràng trong prompt này. Log thường che khuất lỗi đầu tiên bằng các lần retry do lỗi đó gây ra. Nếu được yêu cầu khắc phục, model sẽ giải thích dòng cuối cùng mà nó nhìn thấy. Dòng quan trọng thường nằm cách phần nhiễu khoảng 20 dòng.
Kết quả hữu ích có thể là một dòng như Main PID: 1841 (code=exited, status=203/EXEC). Exit status 203/EXEC nghĩa là kernel không thể thực thi file được chỉ định trong ExecStart: hoặc đường dẫn không tồn tại, hoặc file tồn tại nhưng không có quyền thực thi. Dòng #! chỉ đến một interpreter chưa được cài cũng tạo ra status này. Bạn có thể kiểm tra tất cả các trường hợp đó bằng ls -l và head -1.
Chế độ lỗi: tự bịa nguyên nhân. Nếu dán quá ít thông tin, model sẽ lấp khoảng trống bằng một nguyên nhân chung chung, chẳng hạn "cổng đã được sử dụng". Cách xử lý là hỏi ngược lại một câu: "Trong nội dung tôi đã cung cấp, dòng nào hỗ trợ kết luận đó?" Một nguyên nhân mà không ai có thể chỉ ra trong văn bản chỉ là phỏng đoán.
Tác vụ 2: soạn systemd unit hoặc cron entry
Cung cấp các thông tin mà unit file cần: command chính xác, user chạy command, working directory, unit có phải chờ network hay không và cần làm gì khi tiến trình thoát với mã khác 0. Sau đó kiểm tra kết quả trả về trước khi enable bất kỳ thứ gì.
sudo systemd-analyze verify /etc/systemd/system/myapp.service
sudo systemctl daemon-reload
sudo systemctl start myapp.service
systemctl status myapp.service --no-pagersystemd-analyze verify phân tích file theo đúng cách systemd phân tích, nên phát hiện được những lỗi mà mắt người dễ bỏ qua. Directive viết sai sẽ in ra /etc/systemd/system/myapp.service:7: Unknown key name 'Enviroment' in section 'Service', ignoring.. Binary bị thiếu sẽ in ra Command /usr/local/bin/myapp is not executable: No such file or directory. Cả hai đều không báo lỗi trong lúc daemon-reload. Vì vậy, unit có thể load bình thường nhưng vẫn fail ngay khi chạy.
Có 2 lỗi khi soạn unit lặp đi lặp lại. Lỗi đầu tiên là After=network.target. Directive này chỉ có nghĩa là network stack đã được cấu hình, không có nghĩa là máy đã có địa chỉ IP. Service bind vào một IP cụ thể sẽ fail lúc boot với bind: Cannot assign requested address. Cách sửa là dùng Wants=network-online.target cùng với After=network-online.target. Lỗi thứ hai là dùng Type=simple cho một chương trình tự daemonise: systemd coi process đầu tiên là service, process cha thoát ngay, unit bị đánh dấu là đã chết trong khi process thật vẫn chạy mà không được systemd quản lý.
Với schedule, hãy kiểm tra thay vì chỉ đọc:
systemd-analyze calendar 'Mon *-*-* 04:00:00'Lệnh này in ra dạng đã được chuẩn hóa và thời điểm tiếp theo expression sẽ chạy. Nhờ đó, bạn có thể xác định chính xác expression có nghĩa gì. Nếu đang chọn giữa timer và crontab, systemd service và timer trên VPS trình bày các điểm đánh đổi.
Cron có một bẫy mà không model nào tự cảnh báo nếu bạn không hỏi. Cron chạy job với environment tối thiểu, nên PATH gần tương đương với /usr/bin:/bin và shell profile không bao giờ được đọc. Một job chạy được khi bạn paste vào terminal nhưng fail dưới cron với /bin/sh: 1: docker: not found, vì binary đó nằm trong /usr/local/bin. Hãy dùng absolute path trong crontab.
Job 3: kiểm tra file nginx hoặc Compose trước khi đưa vào vận hành
Job này mang lại hiệu quả cao nhất. Dán file, nêu rõ file cần làm gì, rồi yêu cầu giải thích từng dòng về hành vi thực tế của file.
Vhost này phải phục vụexample.comqua HTTPS và proxy/apiđến một service local trên port 8080. Hãy đọc lại nội dung và nêu mọi điểm không khớp với mô tả đó.
Sau đó chạy công cụ kiểm tra cú pháp:
sudo nginx -t
docker compose config -qnginx -t in ra nginx: configuration file /etc/nginx/nginx.conf test is successful hoặc nêu tên file và số dòng, như trong nginx: [emerg] unknown directive "proxy_pas" in /etc/nginx/conf.d/app.conf:12. docker compose config -q không in gì khi file hợp lệ về cú pháp, và in một thông báo ngắn gọn như yaml: line 7: did not find expected key khi bạn thụt lề sai.
Cả hai công cụ đều không kiểm tra ý định. Một config vượt qua nginx -t vẫn có thể proxy đến sai port hoặc listen trên 0.0.0.0 trong khi bạn muốn 127.0.0.1. Đây là khoảng trống mà model phát huy tác dụng, nhưng cũng là nơi model dễ mắc lỗi: khi được yêu cầu sửa một directive, model thường trả về toàn bộ file đã viết lại và âm thầm làm mất hai directive của bạn. Hãy yêu cầu model chỉ trả về các dòng đã thay đổi và lý do của từng thay đổi, sau đó tự chỉnh sửa.
Xác nhận những gì bạn thực sự đã expose:
sudo ss -tulpnNếu không có sudo, bạn chỉ thấy các listening socket chứ không thấy các process đang sở hữu chúng. Nếu output này gây bất ngờ, hãy đọc bài ngắn hơn về port và cách Linux bind chúng.
Bài 4: giải thích một lệnh chưa quen thuộc trước khi chạy
Dán lệnh vào và hỏi 4 câu: mỗi flag làm gì, lệnh ghi gì, xóa gì và điều gì xảy ra nếu tôi chạy lệnh đó 2 lần. Câu cuối thường phát hiện nhiều thiệt hại hơn các câu còn lại.
Lấy find /var/log -name '*.gz' -mtime +7 -delete làm ví dụ. Câu trả lời tốt phải cho biết -mtime +7 đếm các khoảng thời gian trọn vẹn 24 giờ và bỏ phần lẻ, nên nó khớp với các file đã cũ ít nhất 8 ngày thay vì 7 ngày. Câu trả lời cũng phải cho biết find đánh giá expression từ trái sang phải, nên nếu đưa -delete lên trước -name, lệnh sẽ xóa mọi thứ bên dưới path bắt đầu. Điểm thứ hai này được ghi rõ như một cảnh báo trong man page của find và đã khiến nhiều người mất /var/log.
Hoặc lấy rsync -a --delete /srv/app/ /backup/app/ làm ví dụ. Dấu slash ở cuối source có nghĩa là “nội dung của directory này”. Bỏ dấu slash đó, bạn sẽ nhận được /backup/app/app/. Thêm --delete thì mọi thứ trong destination nhưng không có trong source sẽ bị xóa. Điều này đúng khi đồng bộ mirror và sẽ gây thảm họa nếu source path bị sai.
Xác minh bằng tool, không phải bằng model:
rsync -a --delete --dry-run /srv/app/ /backup/app/ | head -20
find /var/log -name '*.gz' -mtime +7Chạy find mà không có -delete, bạn sẽ nhận được một danh sách thay vì bị mất dữ liệu.
Dạng lỗi: bịa flag. Model thường đáng tin với các tool có tài liệu tồn tại 30 năm, nhưng kém tin cậy hơn với CLI (command line interface) của vendor và các subcommand mới. Trong những trường hợp đó, model có thể tạo ra một flag trông hoàn toàn hợp lý nhưng không tồn tại. --help sẽ xác nhận điều này trong 1 giây. Quoting là điểm yếu khác. Vì vậy, khi một lệnh chứa expression $(...), hãy đọc cách command substitution được mở rộng trước khi lệnh chạy thay vì tin ngay vào phần giải thích.
Job 5: biến shell history thành runbook
Bạn vừa mất hai giờ để làm cho một thứ hoạt động. Kiến thức đó chỉ nằm trong scrollback và sẽ biến mất vào tháng sau.
history 200 > /tmp/session.txtĐọc file đó và xóa mọi dòng chứa password, token hoặc mã định danh khách hàng trước khi đưa file đi đâu đó. Shell history là một trong những nơi đáng tin cậy nhất để tìm secret trên Linux box, vì ai cũng có lúc nhập secret trực tiếp trên command line. Đặt HISTCONTROL=ignorespace trong ~/.bashrc; khi đó command được nhập với một dấu cách ở đầu sẽ không bao giờ được ghi vào history.
Prompt để tạo runbook hữu dụng phải yêu cầu các bước kiểm tra, không chỉ các bước thực hiện:
Đây là một shell session đã đưa một box Debian 13 mới cài đến trạng thái Postgres hoạt động. Hãy viết lại thành runbook có đánh số. Mỗi bước chỉ dùng một command. Sau mỗi bước, đưa ra command chứng minh bước đó đã thành công và mô tả output bình thường sẽ trông như thế nào. Đánh dấu mọi bước phụ thuộc vào host cụ thể của tôi.
Failure mode: câu chuyện quá gọn gàng. Session của bạn có một bước đã thực hiện sai hai lần trước khi sửa được, và đó là bước model thường lược bỏ vì transcript trông sạch hơn nếu không có nó. So sánh runbook với history rồi đưa phần sửa lỗi đó trở lại. Model cũng có thể tự tạo ra các command kiểm tra nghe có vẻ hợp lý, vì vậy hãy chạy mọi lệnh kiểm tra mà nó viết trước khi lưu file. Nếu runbook bao quát lần boot đầu tiên, hãy đối chiếu nó với mười phút đầu tiên trên một VPS mới để không ghi lại một phiên bản tệ hơn của một vấn đề đã được giải quyết.
Job 6: biến thông báo lỗi thành cách khắc phục
Dán nguyên văn chuỗi lỗi, lệnh đã tạo ra lỗi đó và thay đổi duy nhất bạn thực hiện trước khi lỗi xuất hiện. Yêu cầu liệt kê các nguyên nhân theo thứ tự khả năng xảy ra, kèm một lệnh phân biệt cho từng nguyên nhân. Cách này buộc câu trả lời phải có thể kiểm thử.
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use). Hãy xếp hạng các nguyên nhân có khả năng xảy ra và đưa cho tôi một lệnh cho mỗi nguyên nhân để xác nhận hoặc loại trừ nguyên nhân đó.
Cơ chế của lỗi này không mơ hồ: một tiến trình khác đang giữ cổng 80, và sudo ss -tulpn | grep ':80 ' cho biết tiến trình đó là gì. Trường hợp thường gặp là một nginx master thứ hai còn sót lại sau khi reload thất bại, hoặc Apache được kéo vào dưới dạng dependency rồi được package riêng tự khởi động.
Dạng lỗi: cách khắc phục hoạt động bằng cách che giấu nguyên nhân. chmod 777, --privileged, tắt SELinux và chạy service dưới tài khoản root đều khiến lỗi biến mất. Không chấp nhận bất kỳ cách khắc phục nào mở rộng quyền truy cập cho đến khi mô hình giải thích được vì sao quyền tối thiểu không hoạt động. Phần giải thích đó mới là câu trả lời thực sự. Workaround chỉ làm lỗi không còn hiển thị.
Những điểm nó thường sai
- Nó không thể nhìn thấy máy chủ của bạn. Mọi câu trả lời đều dựa trên nội dung bạn đã dán vào, và nó sẽ không cho biết khi đoạn trích quá ngắn.
- Nó thường nhầm lẫn giữa các phiên bản. Tên package và flag mặc định thay đổi giữa các bản phân phối và bản phát hành, còn model thường lấy trung bình từ tất cả các trường hợp đó.
- Nó vẫn diễn đạt trôi chảy dù đang sai. Một cơ chế bịa ra vẫn đọc giống hệt cơ chế đúng. Vì vậy, mỗi nguyên nhân ở trên đều đi kèm một command để kiểm tra.
- Nó mất mạch trong các session dài. Những thông tin ở đầu một cuộc hội thoại kéo dài 2 giờ không còn định hướng cho câu trả lời ở cuối.
Điểm cuối cùng chủ yếu là vấn đề trong quá trình làm việc hơn là vấn đề của model. quản lý context trong một session Claude Code dài là cách xử lý thực tế: session ngắn hơn, mỗi session chỉ thực hiện một task.
Đặt agent ngay trên server
Toàn bộ nội dung ở trên chỉ là sao chép và dán, nên model không bao giờ truy cập vào máy của bạn. Khi chạy trực tiếp trên server, đọc file và thực thi command, mức độ rủi ro thay đổi: một command sai có thể làm hỏng một service. Hãy cấp cho agent một user riêng không có quyền đặc biệt thay vì dùng root, không chạy agent trên server production trong thời gian tìm hiểu cách nó hoạt động, và tạo snapshot trước. Chạy Claude Code an toàn trên VPS trình bày về sandbox và mô hình phân quyền. Điều khiển Claude Code bên trong tmux giải quyết phần còn lại, vì một phiên SSH (secure shell) bị ngắt sẽ dừng agent foreground giữa chừng. Hãy tạo account theo cách bạn tạo bất kỳ service account nào; user có quyền tối thiểu trên VPS trình bày chi tiết cách này.
FAQ
Claude có thể đọc trực tiếp log trên server của tôi không?
Không tự mình làm được. Giao diện chat chỉ thấy phần văn bản bạn dán vào. Claude Code chạy trên server dưới dạng công cụ dòng lệnh có thể đọc file và chạy command với quyền của user đã khởi động nó. Đây là quyết định về mức độ tin cậy lớn hơn. Với câu hỏi hỗ trợ thông thường, dán một đoạn trích 100 dòng đã loại bỏ dữ liệu nhạy cảm sẽ nhanh và an toàn hơn cấp quyền shell cho agent.
Tôi không bao giờ nên dán gì từ server?
Private key, file .env và các kho lưu trữ credential khác, /etc/shadow, cùng mọi dữ liệu thuộc về user của bạn. Hãy loại bỏ token khỏi đoạn log trước khi đưa vào prompt. Một trường hợp ít dễ nhận biết: output của docker compose config đã nội suy các giá trị .env vào đó. Vì vậy, hãy dùng docker compose config -q; lệnh này kiểm tra file và không in gì ra.
Cho Claude chạy command trên VPS production có an toàn không?
Hãy xem nó như một admin mới chưa có ngữ cảnh: cho phép đọc thì được, nhưng cần review trước khi ghi. Trên production, hãy yêu cầu giải thích rồi tự bạn chạy command. Nếu bạn thực sự muốn agent thực thi command, hãy cấp cho nó một account unprivileged riêng, không cấp sudo toàn quyền. Hãy bắt đầu trên một máy staging, nơi một lỗi chỉ khiến bạn phải dựng lại máy thay vì gây outage.
Vì sao Claude đề xuất một flag không tồn tại?
Vì nó dự đoán văn bản có vẻ hợp lý, trong khi một flag có vẻ hợp lý lại trông giống hệt flag thật. Lỗi này xảy ra nhiều nhất với CLI của vendor và các subcommand mới hơn, khi tài liệu dùng để huấn luyện model còn thiếu hoặc đã thay đổi. --help và man mới là cơ sở xác nhận, và mọi command có thao tác xóa hoặc ghi đè đều nên được chạy dry run trước.
Tôi kiểm tra systemd unit trước khi enable nó như thế nào?
Chạy sudo systemd-analyze verify /etc/systemd/system/myapp.service. Lệnh này phân tích file bằng parser riêng của systemd, báo các directive không được nhận diện cùng số dòng tương ứng, đồng thời đánh dấu binary ExecStart nếu binary đó bị thiếu hoặc không có quyền thực thi. Sau đó chạy daemon-reload, start và đọc systemctl status trước khi enable nó, vì unit có thể load thành công nhưng vẫn fail ngay lần chạy đầu tiên.