Tác giả: Thức NguyễnThông tin liên hệ:devopsedu.vn: thucnvlinkedin: Nguyễn Văn ThứcMình xin tự giới thiệu mình là Thức Nguyễn. Hiện đang là 1 dev FE và đang học thêm để trở thành 1 DevOps Engineer trong tương lai. Đây là bài viết đầu tiên cùa mình trên devopsedu.vn. Bài viết hướng dẫn về cách dockerize 1 ứng dụng react app với non root user. Trong quá trình mình viết cũng sẽ không tránh được những thiếu sót hy vọng sẽ nhận được các góp ý của mọi người. Mọi thắc mắc về bài viết mọi người có thể gửi về cho mình qua mail vanthuc1994@gmail.com. Xin cám ơn mọi người. Giờ chúng ta sẽ đi vào bài viết thôi nào.Chuẩn bịCài đặt 1 server (Các bạn có thể tham khảo cách cài đặt server ở local trong khóa devop for fresher).Chú ý server mình dùng ở đây là ubuntu 20.04Cài đặt Nodejs version 18 (tham khảo cài đặt tại link này)Có thể tham khảo các câu lệnh bên dưới nếu muốn thực hành nhanhapt-get updatecurl -s https://deb.nodesource.com/setup_18.x | sudo bashapt install nodejs -yChú ý nên chuyển qua user root trước khi chạy các câu lệnh này.Khi cài đặt thành công chạy 2 lệnh bên dưới để xem version của node và của npmnode -vnpm -vKết quả hiển thi như bên dưới là đã cài đặt node thành công rồi3. Cài đặt docker (tham khảo cài đặt tại link này)Có thể tham khảo nhanh các câu lệnh bên dướiapt updateapt install -y apt-transport-https ca-certificates curl software-properties-commoncurl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpgecho "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/nullapt updateapt install docker-ce -yKiểm tra cài docker đã thành công hay chưa bằng câu lệnh systemctl status dockerHướng dẫn Dockerize reactppChú ý chúng ta sẽ tuân theo nguyên tắc dưới đâyMỗi dự án sẽ được chạy ở 1 thư mục riêngMỗi dự án cũng sẽ được chạy bởi 1 user riêng (chứ không dùng user root để tránh việc hacker nếu có hack được dự án của chúng ta thì cũng sẽ không ảnh hưởng đến các dự án khác đang chạy)Tạo 1 user mới để chạy dự ánĐầu tiên chúng ta cần tạo mới 1 user có tên là reactapp bằng câu lệnh bên dưới (chú ý chạy bằng user root nhé)adduser reactappSau đó chúng ta chuyển qua user reactapp và tạo 1 thư mục mới có tên là projects trong /home/reactappChú ý thư mục projects sẽ là nơi chứa các dự án mà chúng ta sẽ chạy khi sử dụng user reactappTạo mới dự án react jsTrước hết chúng ta vào trang chủ của create react app và chạy các câu lệnh saunpx create-react-app react-app (Câu lệnh này để tạo 1 project react js và project này chúng ta sẽ dùng dockerize react app)chmod -R 750 react-app (Lệnh này để giới hạn quyền đối với thư mục react-app)Đối với user react-app thì sẽ có full quyền (read-write-execute).Đối với group react-app thì sẽ chỉ có quyền read và execute.Đối với các user khác thì không có quyền gì cả đối với project của chúng ta.cd react-app (Di chuyển vào thư mục làm việc của chúng ta) npm run build (Chạy lệnh này để build thử dự án của chúng ta xem có thành công hay không. Nếu chạy ra kết quả như bên dưới tức là chúng ta đã build thành công)Dockerize react appTạo file .dockerignore trong thư mục react-app có nội dung như bên dướinode_modules/build/.git*.md.dockerignoreDockerfileChú ý: Việc tạo file .dockerignore này để loại bỏ các file hoặc thư mục chúng ta không muốn copy vào trong docker khi thực hiện docker build. Việc này sẽ giúp chúng ta build nhanh hơn và kích thước image cuối cùng chúng ta build ra cũng nhẹ hơn vì chỉ chứa các thành phần cần thiết cho ứng dụngTạo file nginx.confworker_processes 1;error_log /var/log/nginx/error.log warn;events {worker_connections 1024;}http {include /etc/nginx/mime.types;default_type application/octet-stream;log_format main '$remote_addr - $remote_user [$time_local] "$request" ''$status $body_bytes_sent "$http_referer" ''"$http_user_agent" "$http_x_forwarded_for"';access_log /var/log/nginx/access.log main;sendfile on;keepalive_timeout 65;server {listen 80;server_name webclient;location / {root /app;index index.html;try_files $uri $uri/ /index.html;}error_page 500 502 503 504 /50x.html;location = /50x.html {root /usr/share/nginx/html;} }} Hình bên dưới mình minh họa cấu hình cho các bạn hình dung rõ hơn nhéChú ý nếu muốn thay đổi đường của các file log hay thư mục build source code của dự án thì chúng ta sẽ thay đổi tương ứng giống như mô tả của hình bên trên nhé.Tạo DockerfileFROM node:18-alpine@sha256:17514b20acef0e79691285e7a59f3ae561f7a1702a9adc72a515aef23f326729 AS baseFROM base AS buildWORKDIR /appCOPY package.json package-lock.json .RUN npm installCOPY . .RUN npm run buildFROM nginx:1.27-alpine-slim@sha256:2be9e698d136d4d9be33d1852b1259bc1b80e20aed0c964cbcd6086da7fad5c7WORKDIR /appCOPY --from=build /app/build .COPY --from=build /app/nginx.conf /etc/nginx/nginx.confEXPOSE 80CMD ["nginx", "-g", "daemon off;"]Build image với câu lệnh saudocker build -t react-app-root-user .Nếu chúng ta gặp lỗi bên dưới thì do react-app user chưa nằm trong group docker nên chạy build bị lỗi (Mặc định chỉ root user và các user trong group docker mới có thể chạy được docker daemon)Cách giải quyết là chúng ta thêm group docker cho user react-app như bên dưới (chú ý su qua user root để thực hiện nhé)Chạy ra kết quả như bên dưới là thành công nhéRun imagechạy với câu lệnh saudocker run -d -p 3003:80 react-app-root-userNếu chạy ra kết quả như bên dưới là thành công nhéĐến đây chúng ta hoàn thành việc dockerize react app và chạy chúng trên nginx. Tuy nhiên nếu chúng ta để ý kỹ hơn thì các tiến trình trong docker đang chạy bằng user root. Chúng ta có thể xem ảnh minh họa bên dưới để hiểu rõ hơnKhi sh vào container chúng ta gõ lệnh top để hiển thị được giao diện bên dưới nhéViêc chạy bằng user root có thể khiến ứng dụng của chúng kém bảo mật hơn vì vậy chúng ta sẽ chạy cấu hình để chạy container với quyền non root thông qua instruction USER trong dockerfile.Dựa theo doc của nginx trên docker hub cấu hình chạy nginx với non root user chúng ta sẽ thay đổi file nginx.conf và Dockerfile như sauThay đổi file nginx.confworker_processes 1;pid /tmp/nginx.pid;error_log /tmp/log/nginx/error.log warn;events {worker_connections 1024;}http {client_body_temp_path /tmp/client_temp;proxy_temp_path /tmp/proxy_temp_path;fastcgi_temp_path /tmp/fastcgi_temp;uwsgi_temp_path /tmp/uwsgi_temp;scgi_temp_path /tmp/scgi_temp;include /etc/nginx/mime.types;default_type application/octet-stream;log_format main '$remote_addr - $remote_user [$time_local] "$request" ''$status $body_bytes_sent "$http_referer" ''"$http_user_agent" "$http_x_forwarded_for"';access_log /tmp/log/nginx/access.log main;sendfile on;keepalive_timeout 65;server {listen 80;server_name webclient;location / {root /app;index index.html;try_files $uri $uri/ /index.html;}error_page 500 502 503 504 /50x.html;location = /50x.html {root /usr/share/nginx/html;}}}Chú ý trong file nginx.conf chúng ta có 1 vài thay đổiThay đổi đường dẫn lưu nginx.pidpid /tmp/nginx.pid;Thay đổi đường dẫn lưu error_log và access_logerror_log /tmp/log/nginx/error.log warn;access_log /tmp/log/nginx/access.log main;Thêm các config vào trong http contextclient_body_temp_path /tmp/client_temp;proxy_temp_path /tmp/proxy_temp_path;fastcgi_temp_path /tmp/fastcgi_temp;scgi_temp_path /tmp/scgi_temp;Thay đổi DockerfileFROM node:18-alpine@sha256:17514b20acef0e79691285e7a59f3ae561f7a1702a9adc72a515aef23f326729 AS baseFROM base AS buildWORKDIR /appCOPY package.json package-lock.json .RUN npm installCOPY . .RUN npm run buildFROM nginx:1.27-alpine-slim@sha256:2be9e698d136d4d9be33d1852b1259bc1b80e20aed0c964cbcd6086da7fad5c7WORKDIR /appRUN addgroup -g 10001 reactapp &&adduser -D -u 10001 -G reactapp reactapp &&chown -R reactapp:reactapp /app &&chmod -R 750 /app &&chown -R reactapp:reactapp /var/cache/nginx &&mkdir -p /tmp/log/nginx &&touch /tmp/nginx.pid &&chown -R reactapp:reactapp /tmp/nginx.pid &&chown -R reactapp:reactapp /tmp/log/nginxCOPY --from=build --chown=reactapp:reactpp --chmod=750 /app/build .COPY --from=build --chown=reactapp:reactapp /app/nginx.conf /etc/nginx/nginx.confUSER reactappEXPOSE 80CMD ["nginx", "-g", "daemon off;"]Chú ý ở trong Dockerfile này chúng ta có 1 vài thay đổi sauTạo group user có tên là reactapp với GID là 10001Tạo user reactapp có uid là 10001 và thuộc group reactappTrong thư mục /appgán lại user sở hữu và group sở hữu về user reactapp và group react appPhân quyền 750 cho thư mục này.Gán lại quyền sở hữu và nhóm sở hữu thư mục /var/cache/nginx về user reactapp và group react appTạo thư mục lưu trữ access_log và error_log tại thư mục /tmp/log/nginxgán lại user sở hữu và group sở hữu về user reactapp và group react app.Tạo file /tmp/nginx.pidgán lại user sở hữu và group sở hữu về user reactapp và group react app.Sao chép thư mục build source code từ /app/build trong stage build vào thư mục /app của stage hiện tại và đổi quyền sở hữu cùng phân quyền.Ghi đè file cấu hình nginx.confChỉ định user sẽ chạy trong container (ở đây là reactapp user)Build lại dockerfile bằng câu lệnh bên dướidocker build -t react-app-non-root-user .Chạy lại container ở cổng 3004docker run -d -p 3004:80 react-app-non-root-userKiểm tra lại xem container của chúng ta đã chạy với non-root user hay chưaNhư vậy ứng dụng của chúng ta đã chạy bằng non-root user đúng như yêu cầu rồi. Chúng ta sẽ xem kết quả chạy ứng dụng tại port 3004 như bên dưới nhéBài viết này chắc đã quá dài rồi nên mình xin kết thúc tại đây. Hy vọng với những chia sẽ trên của mình sẽ giúp ích được phần nào đó cho các bạn. Chúc các bạn 1 ngày cuối tuần vui vẻ bên gia đình và người thân. Bye bye