浅谈本站nginx的Dockerfile


因为项目的更换,经常需要接触web环境的搭建。搭建的步骤基本上大同小异的,经常会因为测试和生产环境而重复进行两次同样的操作,在还原和误操作等方面也经常浪费许多时间。接触了docker之后,有一种相见恨晚的感觉,即使在开发的时候,也可以尽量少的污染到系统,软件依赖的问题也同时得到了解决。对于喜欢升级系统或软件的人来说,在折腾的路上,减少了许多头疼的事情。

在搭建本站的前期,去看了nginx官方的Dockerfile,其实是看不太懂的。以往搭建都是通过编译源码并且支持brotli压缩算法的形式进行的。因此,基于过往经验,编写了比较符合个人习惯的Dockerfile。

搭建nginx环境主要分为用户创建、编译及运行依赖安装、日志和运行目录创建并分配权限、nginx和openssl以及brotli下载、nginx编译的步骤。根据环境需求也可以省略某些操作。

载体系统和通用参数

FROM alpine:3.11

LABEL maintainer="kotomi@kotomiko.com"

ARG BUILD_ROOT=/usr/local/nginx
ARG CACHE_ROOT=/var/cache/nginx
ARG NGINX_VERSION=1.16.1
ARG OPENSSL_VERSION=1.1.1d
ARG OPENSSL_SHA256=1e3a91bc1f9dfce01af26026f856e064eab4c8ee0a8f457b5ae30b40b8b711f2

FROM alpine:3.11alpine是特意为docker而制作的操作系统,差不多什么都没有。因此大小只有5.61M,根据环境要求,按需做加法。很符合一个容器只做一件事的思想。系统内安装的都是运行时依赖,对整体大小的物尽其用。

FROM是每个Dockerfile必须要有的语句,且通常都在首行,运行软件总是需要一个操作系统作为载体的。

ARG通用参数和ENV环境变量

ARG相当于全局参数,能够被Dockerfile的后续语句使用。比如上述的ARG NGINX_VERSION=1.16.1在之后都被${NGINX_VERSION}所使用,以后更新nginx版本就可以很方便的修改。

在官方的Dockerfile中更常见的是ENV。它在编写Dockerfile时与ARG的作用是相同的。使用ENV的写法如下.

ENV BUILD_ROOT /usr/local/nginx
ENV CACHE_ROOT /var/cache/nginx
ENV NGINX_VERSION 1.16.1
ENV OPENSSL_VERSION 1.1.1d
ENV OPENSSL_SHA256 1e3a91bc1f9dfce01af26026f856e064eab4c8ee0a8f457b5ae30b40b8b711f2

它们的不同之处在于。

  1. build Dockerfile的时候,ARG是可以被build语句中的参数覆盖的。

    docker build -t kotomiko/nginx:latest --build-arg NGINX_VERSION=1.18.0 .
    

    此时编译过程中会下载1.18.0版本的nginx并编译。

  2. ARG在容器build结束后,就不再能被访问了。而ENV则始终可以访问,在容器运行的时候依然可以,ENV是被写入环境变量的。程序可以以访问环境变量的方式,访问Dockerfile中的ENV

  3. ENV可以在运行容器的时候被覆盖。如果不存在则会被添加。

    docker build -t kotomiko/nginx:latest --env NGINX_VERSION=1.18.0 .
    

RUN命令构建软件

RUN命令整体上就是终端交互的那些命令的组合。通过安装编译期和运行期组件,创建用户,授权最终编辑nginx的一个流程。

  1. 安装编译依赖组件。使用alpine的包管理软件apk。

    apk add --no-cache --virtual .build-deps \
        git \
        gcc \
        libc-dev \
        make \
        pcre-dev \
        zlib-dev \ 
        perl-dev \
        linux-headers \
    
  2. 安装运行依赖组件。分开管理编译与运行组件,在nginx的编译结束后,可以方便的删除掉编译依赖。

    apk add --no-cache --virtual .nginx-rundeps \
        pcre \
    
  3. 创建运行用户及用户组。禁止登录,不创建home目录

    addgroup -g 109 -S nginx
    adduser -s /sbin/nologin -G nginx -S -D -H -u 109 nginx
    
  4. 目录所有者变更

    touch /var/run/nginx/nginx.pid
    chown nginx:nginx /var/log/nginx
    chown nginx:nginx ${CACHE_ROOT}
    chown nginx:nginx /var/run/nginx/nginx.pid
    
  5. 源码下载

    wget -S -O nginx.tar.gz https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz
    wget -S -O openssl.tar.gz https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz
    git clone https://github.com/google/ngx_brotli.git
    cd ngx_brotli
    git submodule update --init
    
  6. nginx编译

    BUILD_CONFIG="\
        --prefix=/usr/local/nginx \
        --sbin-path=/usr/sbin/nginx \
        --conf-path=/etc/nginx/nginx.conf \
        --error-log-path=/var/log/nginx/error.log \
        --pid-path=/var/run/nginx/nginx.pid \
        --lock-path=/var/run/nginx.lock \
        --user=nginx \
        --group=nginx \
        --with-threads \
        --with-http_ssl_module \
        --with-http_v2_module \
        --with-http_gzip_static_module \
        --http-log-path=/var/log/nginx/access.log \
        --http-client-body-temp-path=${CACHE_ROOT}/client \
        --http-proxy-temp-path=${CACHE_ROOT}/proxy \
        --http-fastcgi-temp-path=${CACHE_ROOT}/fastcgi \
        --http-uwsgi-temp-path=${CACHE_ROOT}/uwsgi \
        --http-scgi-temp-path=${CACHE_ROOT}/scgi \
        --add-module=${BUILD_ROOT}/ngx_brotli \
        --with-openssl=${BUILD_ROOT}/openssl \
    "\
    && ./configure $BUILD_CONFIG \
    && make \
    && make install \
    

CMD容器启动命令

CMD ["nginx", "-g", "daemon off;"]

每次运行容器时,都会运行这个命令来启动nginx。切记一点。nginx不能以后台模式启动。Docker容器并非完全的虚拟机软件。它总是需要一个处于活动状态的进程来判断当前容器的状态的。当容器启动时,内部运行命令nginx -g daemon off;此时会有一个/bin/sh的进程处于激活状态。如果nginx以后台模式启动的话,/bin/sh会在nginx启动后退出。docker容器也会跟随着退出。这适用于所有以Docker启动的软件。

构建Dockerfile

docker build -t kotomiko/nginx:latest .

通过docker build命令构建Dockerfile-t用于指定镜像的名字:latest则为标签。.用于表示当前目录,docker会在当前目录寻找Dockerfile并进行构建。 在构建完成后通过docker image ls可以看到类似的输出

REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
kotomiko/nginx       latest              fdfd9046e4d0        12 seconds ago      11MB
alpine               3.11                f70734b6a266        7 weeks ago         5.61MB

运行容器

docker run -d -p 80:80 kotomiko/nginx:latest

运行刚刚构建的image。-d容器会在后台运行,-p 80:80将容器的80端口映射到主机的80端口。此时访问http://localhost验证下是否成功。或是查看docker ps -a输出的STATUS是否处于Up状态。

CONTAINER ID        IMAGE                   COMMAND                  CREATED             STATUS              PORTS                NAMES
2977d7a642d3        kotomiko/nginx:latest   "nginx -g 'daemon of…"   4 seconds ago       Up 3 seconds        0.0.0.0:80->80/tcp   youthful_wing

此时一个容器就被创建出来。可以使用docker stop 2977d7a642d3docker restart 2977d7a642d3docker start 2977d7a642d3对容器进行重启,停止等操作。2977d7a642d3是容器的ID。

映射容器目录

虽然上面成功启动了一个容器,但我们修改nginx.conf很麻烦。应当将conf映射到容器中。通过修改主机目录的conf文件,来更改nginx的配置。

先把nginx.conf复制到主机上。

docker run --rm -i -t kotomiko/nginx:latest cat /etc/nginx/nginx.conf > /docker/nginx/nginx.conf

之后映射conf。后续可以通过修改主机上的nginx.conf更改容器中的nginx配置.

ocker run -d -p 80:80 -v /docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro kotomiko/nginx:latest

Dockerfile完整内容

FROM alpine:3.11

LABEL maintainer="kotomi@kotomiko.com"

ARG BUILD_ROOT=/usr/local/nginx
ARG CACHE_ROOT=/var/cache/nginx
ARG NGINX_VERSION=1.16.1
ARG OPENSSL_VERSION=1.1.1d
ARG OPENSSL_SHA256=1e3a91bc1f9dfce01af26026f856e064eab4c8ee0a8f457b5ae30b40b8b711f2

RUN apk add --no-cache --virtual .build-deps \
        git \
        gcc \
        libc-dev \
        make \
        pcre-dev \
        zlib-dev \ 
        perl-dev \
        linux-headers \
    && apk add --no-cache --virtual .nginx-rundeps \
        pcre \
    && addgroup -g 109 -S nginx \
    && adduser -s /sbin/nologin -G nginx -S -D -H -u 109 nginx\
    && mkdir /var/run/nginx \
    && mkdir /var/log/nginx \
    && mkdir -p ${CACHE_ROOT} \
    && mkdir -p ${BUILD_ROOT} \
    && touch /var/run/nginx/nginx.pid \
    && chown nginx:nginx /var/log/nginx \
    && chown nginx:nginx ${CACHE_ROOT} \
    && chown nginx:nginx /var/run/nginx/nginx.pid \
    && cd ${BUILD_ROOT} \
    && wget -S -O nginx.tar.gz https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz \
    && wget -S -O openssl.tar.gz https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz \
    && echo "$OPENSSL_SHA256 *openssl.tar.gz" | sha256sum -c - \
    && git clone https://github.com/google/ngx_brotli.git \
    && cd ngx_brotli \
    && git submodule update --init \
    && cd .. \
    && mkdir nginx \
    && mkdir openssl \
    && tar --extract --file nginx.tar.gz --directory ./nginx --strip-components 1 \
    && tar --extract --file openssl.tar.gz --directory ./openssl --strip-components 1 \
    && BUILD_CONFIG="\
        --prefix=/usr/local/nginx \
        --sbin-path=/usr/sbin/nginx \
        --conf-path=/etc/nginx/nginx.conf \
        --error-log-path=/var/log/nginx/error.log \
        --pid-path=/var/run/nginx/nginx.pid \
        --lock-path=/var/run/nginx.lock \
        --user=nginx \
        --group=nginx \
        --with-threads \
        --with-http_ssl_module \
        --with-http_v2_module \
        --with-http_gzip_static_module \
        --http-log-path=/var/log/nginx/access.log \
        --http-client-body-temp-path=${CACHE_ROOT}/client \
        --http-proxy-temp-path=${CACHE_ROOT}/proxy \
        --http-fastcgi-temp-path=${CACHE_ROOT}/fastcgi \
        --http-uwsgi-temp-path=${CACHE_ROOT}/uwsgi \
        --http-scgi-temp-path=${CACHE_ROOT}/scgi \
        --add-module=${BUILD_ROOT}/ngx_brotli \
        --with-openssl=${BUILD_ROOT}/openssl \
    "\
    && cd nginx \
    && ./configure $BUILD_CONFIG \
    && make \
    && make install \
    && strip /usr/sbin/nginx* \
    && cd .. \
    && rm -rf ${BUILD_ROOT} \
    && apk del .build-deps

CMD ["nginx", "-g", "daemon off;"]