作为目前最火的应用,Docker 确实存在着其独到之处,无论是程序猿还是运维都应该听说过 Docker 的大名,Docker 已经走过了许多的坑,目前最新版本是 v1.11.0 版本,应该说是完全能承载开发使用和运维监控,这款工具能帮助我们高效的打包、发布和运行承载着应用程序的容器系统。而且收集日志、帮助 App 的快速开发都有很大作用。
容器和虚拟机,经常是被拿出来对比的两款产品,实际上两者有着根本的差别,虚拟机是完全模拟了一台真实计算机,在上面运行的系统可能或者不可能知道自己运行在虚拟化环境下,并且虚拟机承载了将用户指令转换为特权指令的功能,所以虚拟机非常复杂,但是很完备,而 Docker 则完全不同。Docker 使用主机自身的 Linux 内核,然后从镜像中产生磁盘目录和软件,所有的进程都运行在主机上,如果有兴趣的话完全可以 ps aux
查询一下,就能发现在 Docker 中运行的进程,只不过 Docker 对其做了如同 chroot
差不多概念的封装。
在 Docker 发展的早期,由于 busybox 等轻量化镜像不完备,所以各大发行版的缩减瘦身镜像得到了更多的使用,特别是由于 Docker 本身是在 Ubuntu 环境下开发的,所以 Ubuntu 和 Debian 在很多镜像中作为基镜像,以此作为基础产生目标镜像。但是随着在实践中的使用,其弊端也暴露出来了,就是太过于重量化,比如 systemd 的日志功能和 Docker 本身的日志功能被重复使用,镜像很难缩小到 300M 以内。而且 Docker 的推荐使用方式就是单进程模型,而并非是多个进程如同一个完备的操作系统一般。所以就产生了 alpine 等轻量级基镜像,alpine 是什么则可以自行百度,这个镜像是 Docker 官方推荐的镜像,未来官方镜像将会迁移到 alpine 作为基础的镜像上,所以,我们应当早日熟悉此镜像。
本文讲述的是 Docker 容器的 Nginx 实践,不过官方实际上已经有了关于 Nginx 的 alpine 镜像。而在实际使用过程中,笔者更多的是使用 Tengine,所以根据官方 Dockerfile 的参考,笔者自行编写了 Tengine 镜像的 Dockerfile,希望能抛砖引玉,各位能够批评指正。
```
FROM alpine:3.3
MAINTAINER ChasonTang < chasontang@gmail.com >
ENV TENGINE_VERSION 2.1.2
ENV CONFIG "/
--prefix=/etc/nginx /
--sbin-path=/usr/sbin/nginx /
--conf-path=/etc/nginx/nginx.conf /
--error-log-path=/var/log/nginx/error.log /
--http-log-path=/var/log/nginx/access.log /
--pid-path=/var/run/nginx.pid /
--lock-path=/var/run/nginx.lock /
--http-client-body-temp-path=/var/cache/nginx/client_temp /
--http-proxy-temp-path=/var/cache/nginx/proxy_temp /
--http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp /
--http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp /
--http-scgi-temp-path=/var/cache/nginx/scgi_temp /
--user=nginx /
--group=nginx /
--with-http_ssl_module /
--with-http_realip_module /
--with-http_addition_module /
--with-http_sub_module /
--with-http_dav_module /
--with-http_flv_module /
--with-http_mp4_module /
--with-http_gunzip_module /
--with-http_gzip_static_module /
--with-http_random_index_module /
--with-http_secure_link_module /
--with-http_auth_request_module /
--with-mail /
--with-mail_ssl_module /
--with-file-aio /
--with-http_spdy_module /
--with-ipv6 /
--with-jemalloc /
"
ADD ngx_user.patch /
ADD repositories /etc/apk/repositories
RUN /
addgroup -S nginx /
&& adduser -D -S -h /var/cache/nginx -s /sbin/nologin -G nginx nginx /
&& apk add --no-cache --virtual .build-deps /
gcc /
libc-dev /
make /
openssl-dev /
pcre-dev /
zlib-dev /
linux-headers /
curl /
jemalloc-dev /
&& curl " http://tengine.taobao.org/download/tengine- $TENGINE_VERSION.tar.gz" -o tengine.tar.gz /
&& mkdir -p /usr/src /
&& tar -zxC /usr/src -f tengine.tar.gz /
&& rm tengine.tar.gz /
&& cd /usr/src/tengine-$TENGINE_VERSION/src/os/unix/ /
&& mv /ngx_user.patch ./ngx_user.patch /
&& patch ngx_user.c ngx_user.patch /
&& rm ngx_user.patch /
&& cd ../../../ /
&& ./configure $CONFIG --with-debug /
&& make /
&& mv objs/nginx objs/nginx-debug /
&& ./configure $CONFIG /
&& make /
&& make install /
&& rm -rf /etc/nginx/html/ /
&& mkdir /etc/nginx/conf.d/ /
&& mkdir -p /usr/share/nginx/html/ /
&& install -m644 html/index.html /usr/share/nginx/html/ /
&& install -m644 html/50x.html /usr/share/nginx/html/ /
&& install -m755 objs/nginx-debug /usr/sbin/nginx-debug /
&& strip /usr/sbin/nginx* /
&& runDeps="$( /
scanelf --needed --nobanner /usr/sbin/nginx /
| awk '{ gsub(/,/, "/nso:", $2); print "so:" $2 }' /
| sort -u /
| xargs -r apk info --installed /
| sort -u /
)" /
&& apk add --virtual .nginx-rundeps $runDeps /
&& apk del .build-deps /
&& rm -rf /usr/src/nginx-$NGINX_VERSION /
&& apk add --no-cache gettext /
/
# forward request and error logs to docker log collector
&& ln -sf /dev/stdout /var/log/nginx/access.log /
&& ln -sf /dev/stderr /var/log/nginx/error.log
COPY nginx.conf /etc/nginx/nginx.conf
COPY nginx.vh.default.conf /etc/nginx/conf.d/default.conf
EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]
```
我们知道,Docker 可以根据 Dockerfile 构建镜像,上面就是笔者写的 Dockerfile。首先,使用 FROM
指令指定此镜像的基镜像为 alpine:3.3,第二行为 Dockerfile 维护者声明,然后使用两个 ENV
指令声明两个环境变量,一个指定 Tengine 需要获取的版本号,一个则是编译安装选项。这里暂时不讲解,然后将一个补丁文件和 alpine 镜像源配置文件复制到容器内,实际上是因为 tengine v2.1.2 存在着一个遗留的 glibc bug,会导致编译时出错,上游 Nginx 的最新代码已经修复,而 tengine 的开发分支上面也已经修复了这个问题,笔者前不久提 issue 将此补丁修正了 tengine v2.1.3 分支的代码,但是很可惜,v2.1.3 版本尚未有正式发布,所以只能先使用补丁手动修复此问题。至于镜像源,则是因为国内存在着网络问题,导致 apk 包管理命令无法成功下载各个依赖项,所以将其指定为了国内源,如果正式使用则可以移除这两个文件。
然后就是使用 RUN 命令执行代码,这里大家可以看到笔者使用 &&
和 /
将所有的指令都压缩为了一行,这里是有两个原因:
1. RUN
指令不会保存上一条指令的工作路径,每条 RUN
指令都只会将工作目录指定为 /
目录
2. 一条 Dockerfile 中的指令就会产生一次镜像的提交,换言之,减少 Dockerfile 中的指令就可以提高镜像的复用水平
然后就是使用 apk
包管理命令下载安装包括编译器等依赖项,并且将这些依赖项标记为 .build-deps
组,便于后面将其卸载清理。然后就是非常常规的思路, ./configure && make && make install
,编译选项都是非常中规中矩的,基本熟悉 Nginx 编译的朋友都能看懂。但是上面可以注意到,Nginx 被编译了两次,一次开启了 --with-debug
参数,一次没有,这是因为在很多情况下,我们需要 Nginx 提供 debug 级别的监控日志,特别是在开发环境下,所以就编译了两次,便于使用。然后后面使用字符串分析处理将 Tengine 的运行时依赖项提取出来,标记为 .nginx-rundeps
然后卸载 .build-deps
,最后则是两个符号链接将 accessLog
和 errorLog
链接到标准输入输出,这样我们就能使用 docker logs
命令方便的查看日志了。最后则是复制自定义的 Nginx 配置文件,然后使用 nginx -g daemon off;
让 Nginx 以前台进程方式运行。
到这里已经讲完了 Docker 在生产开发中的正确使用方法,Docker 也确实是一样不可多得的好工具,祝愿大家早日使用 Docker 提升自己的生产力。