Tìm hiểu về Docker - Phần 3 - Tạo image từ Dockerfile

Tìm hiểu về Docker - Phần 3 - Tạo image từ Dockerfile

Mở đầu

Ở các bài trước mình đã giới thiệu tổng quan Docker Engine và kiến trúc của Docker, cũng như chạy một container đầu tiên với các image có sẵn. Ở phần này mình sẽ hướng dẫn tạo image bằng Dockerfile.

1. Dockerfile là gì ?

  • Dockerfile là một file dạng text không có extension, và tên bắt buộc phải là Dockerfile
  • Dockerfile là một file kịch bản sử dụng để tạo mới một image

2. Cấu trúc một Dockerfile

Cú pháp chung của một Dockerfile có dạng:

INSTRUCTION arguments
  • INSTRUCTION là tên các chỉ thị có trong Dockerfile, mỗi chỉ thị thực hiện một nhiệm vụ nhất định, được Docker quy định. Khi khai báo các chỉ thị này phải được viết bằng chữ IN HOA.
  • Một Dockerfile bắt buộc phải bắt đầu bằng chỉ thị FROM để khai báo đâu là image sẽ được sử dụng làm nền để xây dựng nên image của bạn.
  • aguments là phần nội dung của các chỉ thị, quyết định chỉ thị sẽ làm gì.

Ví dụ cho cấu trúc một Dockerfile của ứng dụng Laravel:

FROM php:7.3-fpm-alpine

RUN apk --update add freetype-dev \
    libjpeg-turbo-dev \
    libpng-dev \
    shadow \
    libzip-dev \
    gettext \
    gettext-dev \
    icu-dev \
    && docker-php-ext-install pdo_mysql mbstring zip gettext intl exif \
    && docker-php-ext-configure gd --with-gd \
        --with-png-dir=/usr/include/ \
        --with-freetype-dir=/usr/include/ \
        --with-jpeg-dir=/usr/include/ \
    && docker-php-ext-install -j$(nproc) gd

RUN apk add autoconf build-base \
&& pecl install xdebug

ARG PUID=1000
ENV PUID ${PUID}
ARG PGID=1000
ENV PGID ${PGID}

RUN groupadd -g ${PGID} laravel && \
    useradd -u ${PUID} -g laravel -m laravel && \
    usermod -p "*" laravel -s /bin/sh

COPY upload.ini /usr/local/etc/php/conf.d/

# install composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

USER laravel
RUN composer global require "laravel/installer" && composer global require "phpunit/phpunit"
ENV PATH $PATH:/home/laravel/.composer/vendor/bin

WORKDIR /var/www/laravel

3. Các command cơ bản trong Dockerfile

FROM

FROM ubuntu:18.04

FROM chỉ định rằng image build này sẽ base trên image gốc nào đó.
Chỉ thị FROM là bắt buộc và phải được để lên phía trên cùng của Dockerfile.

LABEL

LABEL "image-type"="hapo-test"
LABEL "image-type1"="hapo-test1"
LABEL "image-type2"="hapo-test2"

LABEL: Chỉ định label metadata của image. Để xem được các label này sử dụng docker inspect <IMAGE ID>

MAINTAINER

MAINTAINER haposoft

MAINTERNER là author (tác giả) build image đó.

RUN

RUN apt-get update -y

RUN thực hiện một câu lệnh Linux. Tùy vào image gốc mà có các câu lệnh tương ứng (ví dụ centos sẽ là RUN yum update -y).

COPY

COPY start.sh /start.sh

COPY Sao chép một file từ Dockerhost vào image trong quá trình build image.

ENV

ENV source /var/www/html/

ENV là biến môi trường sử dụng trong quá trình build image.
ENV chỉ có thể được sử dụng trong các command sau:

  • ADD
  • COPY
  • ENV
  • EXPOSE
  • FROM
  • LABEL
  • STOPSIGNAL
  • USER
  • VOLUME
  • WORKDIR

CMD

CMD ["./start.sh"]

CMD dùng để truyền một Linux command khi khởi tạo container từ image

VOLUME

VOLUME ["/etc/http"]

VOLUME Tạo một volume nằm trong folder /var/lib/docker/volumes của docker host và mount với folder chẳng hạn /etc/http khi khởi chạy container

EXPOSE

EXPOSE 80 443

EXPOSE Chỉ định các port sẽ Listen trong container khi khởi chạy container từ image

Và còn 1 số lệnh nữa, các bạn có thể tham khảo thêm tại đây

4. Tạo image với Dockerfile

Phần trên mình đã đi qua được cấu trúc cũng như các chỉ thị chính của một Dockerfile. Bây giờ chúng ta sẽ thực hành build một image bằng cách viết một Dockerfile đơn giản. Lưu ý rằng trước khi bắt đầu, hãy chắc chắn rằng máy của bạn đã được cài đặt sẵn Docker. Nếu chưa, bạn có thể tham khảo hướng dẫn cài đặt ở đây.

Bước 1: Tạo Dockerfile

Trong bước này, chúng ta sẽ tạo một thư mục mới cho Dockerfile và tiến hành định nghĩa những thứ chúng ta muốn làm với Dockerfile này.

Tạo mới thư mục build-image và tạo mới Dockerfile:

$ mkdir build-image && cd build-image && touch Dockerfile

Trong ví dụ này, mình sẽ cài đặt NginxPhp-fpm dựa trên image Ubuntu20.04 của Docker. Ngoài ra, cài thêm Supervisord cho phép khởi động Nginx và Php-fpm mỗi khi chúng ta start container trong cùng một command. Nào, bắt đầu cùng chỉnh sửa nội dung Dockerfile nhé.

Dòng đầu tiên của file, hãy thêm base image mà chúng ta muốn sử dụng bằng lệnh FROM ở đây là image Ubuntu20.04.

#Download base image ubuntu 20.04
FROM ubuntu:20.04

Tiến hành update Ubuntu software bên trong Dockerfile bằng lệnh RUN:

# Update Ubuntu Software repository
RUN apt-get update

Tiếp đến, tiền hành cài đặt Nginx, php-fpm, supervisord cho image:

# Install nginx, php-fpm and supervisord from ubuntu repository
RUN apt-get install -y nginx php-fpm supervisor && \
   rm -rf /var/lib/apt/lists/*

Ở giai đoạn này, tất cả các ứng dụng đã được cài đặt, việc chúng ta cần làm tiếp theo là tiến hành config cho chúng. Chúng ta sẽ tiến hành config Nginx để nó có thể xử lý các ứng dụng PHP bằng cách chỉnh sửa config của default virtual host. Chúng ta có thể thay thế mới hoàn toàn bằng file config của mình hoặc có thể chỉnh sửa với lệnh sed.

# Define the ENV variable
ENV nginx_vhost /etc/nginx/sites-available/default
ENV php_conf /etc/php/7.4/fpm/php.ini
ENV nginx_conf /etc/nginx/nginx.conf
ENV supervisor_conf /etc/supervisor/supervisord.conf

# Enable php-fpm on nginx virtualhost configuration
COPY default ${nginx_vhost}
RUN sed -i -e 's/;cgi.fix_pathinfo=1/cgi.fix_pathinfo=0/g' ${php_conf} && \
    echo "\ndaemon off;" >> ${nginx_conf}

Tiếp theo, tiến hành config Supervisord cho Nginx và PHP-FPM. Chúng ta sẽ thay thế file config default của Supervisord bằng một file config mới sử dụng lệnh COPY của Dockerfile

# Copy supervisor configuration
COPY supervisord.conf ${supervisor_conf}

Bây giờ, tạo một đường đẫn mới để lưu php-fpm sock file và cấp quyền owner cho /var/www/html và PHP thành www-data

RUN mkdir -p /run/php && \
    chown -R www-data:www-data /var/www/html && \
    chown -R www-data:www-data /run/php

Tiếp theo, định nghĩa volumn để chúng ta có thể mount list directory vào host machine:

# Volume configuration
VOLUME ["/etc/nginx/sites-enabled", "/etc/nginx/certs", "/etc/nginx/conf.d", "/var/log/nginx", "/var/www/html"]

Cuối cùng, định nghĩa default command cho container và mở port cho http và https. Chúng ta sẽ tạo một file start.sh cho default CMD command khi mà container được khởi chạy.

# Configure Services and Port
COPY start.sh /start.sh
RUN chmod +x start.sh
CMD ["./start.sh"]

EXPOSE 80 443

Sau khi hoàn tất các bước trên, bạn có được file Dockerfile như sau:

#Download base image ubuntu 20.04
FROM ubuntu:20.04"

# Update Ubuntu Software repository
RUN apt-get update

# Install nginx, php-fpm and supervisord from ubuntu repository
RUN apt-get install -y nginx php-fpm supervisor && \
    rm -rf /var/lib/apt/lists/*

#Define the ENV variable
ENV nginx_vhost /etc/nginx/sites-available/default
ENV php_conf /etc/php/7.4/fpm/php.ini
ENV nginx_conf /etc/nginx/nginx.conf
ENV supervisor_conf /etc/supervisor/supervisord.conf

# Enable php-fpm on nginx virtualhost configuration
COPY default ${nginx_vhost}
RUN sed -i -e 's/;cgi.fix_pathinfo=1/cgi.fix_pathinfo=0/g' ${php_conf} && \
    echo "\ndaemon off;" >> ${nginx_conf}

#Copy supervisor configuration
COPY supervisord.conf ${supervisor_conf}

RUN mkdir -p /run/php && \
    chown -R www-data:www-data /var/www/html && \
    chown -R www-data:www-data /run/php

# Volume configuration
VOLUME ["/etc/nginx/sites-enabled", "/etc/nginx/certs", "/etc/nginx/conf.d", "/var/log/nginx", "/var/www/html"]

# Configure Services and Port
COPY start.sh /start.sh
RUN chmod +x start.sh
CMD ["./start.sh"]

EXPOSE 80 443

Tiếp theo, tạo các file default, supervisord.conf, start.sh cùng directory với Dockerfile:

$ touch default && touch supervisord.conf && touch start.sh

Trong file default, thêm nội dung sau:

server {
    listen 80 default_server;

    root /var/www/html;
    index index.html index.htm index.nginx-debian.html;

    server_name _;

    error_log /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php7.4-fpm.sock;
    }
}

Trong file supervisord.conf, thêm nội dung sau:

[unix_http_server]
file=/dev/shm/supervisor.sock   ; (the path to the socket file)

[supervisord]
logfile=/var/log/supervisord.log ; (main log file;default $CWD/supervisord.log)
logfile_maxbytes=50MB        ; (max main logfile bytes b4 rotation;default 50MB)
logfile_backups=10           ; (num of main logfile rotation backups;default 10)
loglevel=info                ; (log level;default info; others: debug,warn,trace)
pidfile=/tmp/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
nodaemon=false               ; (start in foreground if true;default false)
minfds=1024                  ; (min. avail startup file descriptors;default 1024)
minprocs=200                 ; (min. avail process descriptors;default 200)
user=root             ;

; the below section must remain in the config file for RPC
; (supervisorctl/web interface) to work, additional interfaces may be
; added by defining them in separate rpcinterface: sections
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///dev/shm/supervisor.sock ; use a unix:// URL  for a unix socket

; The [include] section can just contain the "files" setting.  This
; setting can list multiple files (separated by whitespace or
; newlines).  It can also contain wildcards.  The filenames are
; interpreted as relative to this file.  Included files *cannot*
; include files themselves.

[include]
files = /etc/supervisor/conf.d/*.conf


[program:php-fpm7.4]
command=/usr/sbin/php-fpm7.4 -F
numprocs=1
autostart=true
autorestart=true

[program:nginx]
command=/usr/sbin/nginx
numprocs=1
autostart=true
autorestart=true

Trong file start.sh, thêm nội dung:

#!/bin/sh

/usr/bin/supervisord -n -c /etc/supervisor/supervisord.conf

Đến đây, việc tạo Dockerfile coi như hoàn tất.

Bước 2: Build Docker image và tạo container mới trên image vừa tạo
Chạy command sau để build image từ Dockerfile đã được tạo ở trên:

$ docker build -t nginx_image .

Câu lệnh trên sẽ tạo ra 1 image mới có tên là nginx_image. Sau khi build hoàn tất, trên màn hình của bạn sẽ hiển thị.
Screen-Shot-2021-09-05-at-02.44.14

Tiến hành kiểm tra image bằng lệnh:

$ docker images

Screen-Shot-2021-09-05-at-02.43.47

Như vậy là chúng ta đã build thành công 1 Docker image. Bây giờ hãy build một container mới dựa trên image đó. Để tiện cho việc test, hãy tạo thêm thư mục html cùng directory với Dockerfile.

$ mkdir -p html

Bây giờ, chạy lệnh sau để tạo container mới từ nginx_image ở trên:

$ docker run -d -v ${PWD}/html:/var/www/html -p 8080:80 --name test_container nginx_image

Screen-Shot-2021-09-05-at-03.09.59

Giải thích:

  • --name test_container nginx_image: Tạo mới container với tên 'test_container', dựa trên docker image 'nginx_image'.
  • -p 8080:80: 'test_container' container chạy trên cổng 8080 của host machine
  • -v ${PWD}/html:/var/www/html: Thư mục html ở trên host machine sẽ được rewrite vào thư mục /var/www/html ở trong container.

Bước 3: Testing Nginx and PHP-FPM in the Container
Tạo 2 file bên trong thư mục html:

$ cd html && touch index.html && touch info.php

Nội dung file index.html:
<h1>Hello World! Welcome to Haposoft.</h1>

Nội dung file info.php:
<?php phpinfo(); ?>

Bây giờ hãy kiểm tra quyền truy cập container của bạn bằng lệnh curl trên cổng 8080:

# server-ip có thể là localhost hoặc 127.0.0.1
$ curl server-ip:8080
$ curl -I server-ip:8080

Screen-Shot-2021-09-05-at-03.03.45

Sau khi kiểm tra bằng lệnh command, để trực quan hơn bạn mở trình duyệt lên và truy cập vào địa chỉ http://server-ip:8080 (server-ip có thể là localhost hoặc 127.0.0.1)
Screen-Shot-2021-09-05-at-03.06.00

Giờ thì thử kiểm tra xem php có được cài thành công không nhé
http://server-ip:8080/info.php

Screen-Shot-2021-09-05-at-03.11.21

Cuối cùng, hãy thử thay đổi nội dung của file index.html và reload lại trình duyệt của bạn nhé. ^^

Tổng kết

Như vậy, trong bài viết này mình đã giới thiệu cách build một image từ Dockerfile cũng như giải thích các command trong cấu trúc file của Dockerfile.
Cảm ơn các bạn đã theo dõi và hẹn gặp lại ở bài viết tiếp theo nhé.
Chúc các bạn thành công !

Tài liệu tham khảo: