Bắt bug sấp mặt với Docker: Bóng ma "CRLF" trong docker-entrypoint.sh và cách hóa giải
Đổi gió với DevOps: Hóa giải con bug tàng hình CRLF trong Docker
Hôm nay chúng ta sẽ đổi gió, rời xa những câu query SQL hay những dòng code PHP/Go để đến với thế giới của DevOps và Docker.
Nếu bố đời đang code trên hệ điều hành Windows và làm việc với các container Linux, chắc chắn ít nhất một lần trong đời, bố đời đã từng đập bàn phím vì một con bug "ma làm" thế này: Code chạy mượt mà, build Docker image thành công 100% (không rớt một dòng log đỏ nào), nhưng vừa gõ docker-compose up, cái container lăn đùng ra chết ngay giây đầu tiên với dòng thông báo vô lý:
standard_init_linux.go:211: exec user process caused "no such file or directory"
# Hoặc
/usr/local/bin/docker-entrypoint.sh: line 2: $'
': command not found
Bạn điên cuồng check lại file docker-entrypoint.sh, đường dẫn đúng, file có tồn tại, quyền execute (chmod +x) cũng đã cấp. Vậy cái file đó đi đâu? Chữ từ trên trời nào rơi xuống? Xin thưa, chào mừng bố đời đến với thảm họa CRLF Line Endings.
1. Thủ phạm vô hình: Cuộc chiến hệ điều hành (Windows vs Linux)
Để hiểu con bug này, chúng ta phải lật lại lịch sử của các hệ điều hành về cách chúng xử lý phím "Enter" (xuống dòng).
- Linux/Unix (bao gồm cả MacOS): Khi bạn ấn Enter, nó chỉ lưu đúng 1 ký tự ẩn là LF (Line Feed, ký hiệu là
). - Windows: Khi bạn ấn Enter, gã khổng lồ Microsoft lại quyết định lưu tới 2 ký tự ẩn đi liền nhau là CRLF (Carriage Return + Line Feed, ký hiệu là
).
Bi kịch xảy ra khi nào?
Đó là khi bố đời tạo ra cái file docker-entrypoint.sh bằng VS Code trên máy Windows. Máy Windows tự động nhét đuôi vào mỗi cuối dòng.
Sau đó, Docker mang cái file đó bỏ vào bên trong Container (vốn là môi trường Linux nguyên thủy). Trình thông dịch Shell của Linux (/bin/sh hoặc /bin/bash) đọc file này. Nó thấy ký tự thì nó hiểu là xuống dòng, nhưng nó không hiểu ký tự là cái quái gì cả.
Với Linux, bị coi là một phần của câu lệnh. Thay vì chạy lệnh echo "Start server", nó lại hiểu là phải chạy lệnh echo "Start server". Và dĩ nhiên, lệnh đó không tồn tại!
2. Ba tầng hóa giải: Từ "Chữa cháy" đến "Tuyệt gốc"
Để diệt con bug tàng hình này, chúng ta có 3 cách tương ứng với 3 mức độ chuyên môn khác nhau.
Tầng 1: Chữa cháy tại chỗ (Dành cho Dev cá nhân)
Nếu bố đời đang dùng VS Code, cách sửa cực kỳ dễ.
- Mở file
docker-entrypoint.shlên. - Nhìn xuống thanh trạng thái màu xanh ở góc dưới cùng bên phải màn hình, bố đời sẽ thấy chữ CRLF.
- Click vào đó, chọn lại thành LF.
- Lưu file lại (
Ctrl + S) và build lại Docker. Xong! Mọi thứ sẽ chạy trơn tru.
Tầng 2: Tiêm "Vaccine" trong Dockerfile (Dành cho Team lộn xộn)
Cách 1 rất nhanh, nhưng nếu trong team có ông Dev khác lại lỡ tay pull code về, sửa bằng Notepad rồi push lên lại thì lỗi sẽ lặp lại. Để chắc ăn 100% môi trường nào cũng chạy được, chúng ta sẽ ép container tự "tắm rửa" cho cái script trước khi chạy bằng công cụ dos2unix.
Sửa file Dockerfile của bạn, thêm đoạn cài đặt và chuyển đổi định dạng:
# Copy file script vào container
COPY docker-entrypoint.sh /usr/local/bin/
# Cài tool và ép chuyển đổi từ CRLF sang LF
RUN apt-get update && apt-get install -y dos2unix && dos2unix /usr/local/bin/docker-entrypoint.sh && chmod +x /usr/local/bin/docker-entrypoint.sh
ENTRYPOINT ["docker-entrypoint.sh"]
Cách này đảm bảo dù file gốc từ máy Windows có nát bét thế nào, vào tay Docker cũng sẽ được nắn lại cho thẳng.
Tầng 3: Diệt cỏ tận gốc với Git (Chuẩn mực dự án lớn)
Cách thứ 2 tuy hay nhưng lại làm phình to dung lượng Docker Image vì phải cài thêm tool dos2unix. Giải pháp tối thượng và thanh lịch nhất của các Senior DevOps là ép thằng Git phải quản lý việc này.
Chỉ cần tạo một file tên là .gitattributes ở ngay thư mục gốc của dự án, và viết vào đó một dòng:
# Ép tất cả các file có đuôi .sh luôn luôn sử dụng LF khi checkout và commit
*.sh text eol=lf
Giờ thì bạn cứ push cái file .gitattributes này lên repo. Bất cứ ai trong team (dù xài Windows, Mac hay Linux) khi git pull code về, Git sẽ tự động khóa cứng cái file .sh ở chuẩn LF. Không ai có thể vô tình làm hỏng nó được nữa.
Lời kết
: command not found là một trong những con bug "khốn nạn" nhất vì nó hoàn toàn tàng hình trước mắt thường. Mọi dòng code đều trông có vẻ đúng, nhưng hệ thống lại không chịu chạy.
Hiểu được bản chất sự khác biệt giữa các hệ điều hành và cách Git/Docker quản lý file không chỉ giúp bố đời gỡ bug trong 5 giây, mà còn thể hiện được sự tinh tế trong việc cấu hình dự án, bảo vệ đồng đội khỏi những đêm thức trắng vô nghĩa. Happy coding!
All rights reserved