简介
用docker容器进行打包服务非常方便 不需要考虑依赖的问题 只要把容器复制到其他有docker daemon的服务器就可以直接启动 并能很方便的利用Cgroup, Namespace技术实现资源控制和资源隔离
这篇文档以Nginx为例 其他的像redis mysql php等服务也是可以使用这个方法进行打包为最精简的 只包含必须依赖的服务容器
环境
系统是ubuntu server 15.10版 用ubuntu系统作为docker服务的载体 如果非ubuntu15.10发行版 尽量保证内核版本在3.18以上 Nginx是在CentOS 6.7的容器中编译的 之后在ubuntu上完成打包
在容器中编译Nginx只是我的个人习惯 不喜欢将主机环境乱安装一些不必要的包 保持清洁就好 编译什么的就交给容器去做吧
主机环境
身份 | 系统 | IP |
---|---|---|
Docker服务 | ubuntu 15.10 | 172.17.0.1 |
Docker容器 | CentOS 6.7 | 172.17.0.3 |
软件环境
软件名称 | 版本号 | 下载地址 |
---|---|---|
Nginx | 1.9.15 | 点击下载 |
Docker | 1.9.1 | 点击下载 |
kernel | 4.2.0 | 系统自带 |
步骤
以下是打包Nginx服务容器的基本思路
- 选用Busybox环境作为基础
- 在CentOS 容器中编译Nginx
- 将Nginx的运行依赖库和Nginx程序复制到主机环境
- 部署到Busybox构建的rootfs中
- 导入Docker 查看是否正常运行
构建Busybox最小容器
Busybox构建文档:点击打开文档内容
构建完毕之后 busybox-1.24.2/_install 就是我们需要当作容器基础的rootfs了 将它复制到随便一个目录 留用
1 | cp -rf busybox-1.24.2/_install/ rootfs |
编译安装Nginx
Nginx的编译文档:点击打开文档内容
只需要做到文档中make -j4 也就是编译完成就可以了 之后的安装步骤就不按照文档的走了
安装nginx到非默认根
1 | make DESTDIR=/nginx install |
通过DESTDIR更改安装Nginx的根位置 以/nginx目录作为nginx安装的根
[root@localhost nginx-1.9.15]# ls /nginx/
usr var
[root@localhost nginx-1.9.15]# tree /nginx/
/nginx/
├── usr
│ └── local
│ └── nginx
│ ├── conf
│ │ ├── fastcgi.conf
│ │ ├── fastcgi.conf.default
│ │ ├── fastcgi_params
│ │ ├── fastcgi_params.default
│ │ ├── koi-utf
│ │ ├── koi-win
│ │ ├── mime.types
1 | cd /nginx/ |
复制Nginx运行依赖库
接下来查看nginx程序的所有依赖
[root@localhost nginx]# ldd usr/local/nginx/sbin/nginx
linux-vdso.so.1 => (0x00007fff3caa3000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007f9bc2ff8000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f9bc2ddb000)
libcrypt.so.1 => /lib64/libcrypt.so.1 (0x00007f9bc2ba3000)
libz.so.1 => /lib64/libz.so.1 (0x00007f9bc298d000)
libc.so.6 => /lib64/libc.so.6 (0x00007f9bc25f9000)
/lib64/ld-linux-x86-64.so.2 (0x0000561c3968f000)
libfreebl3.so => /lib64/libfreebl3.so (0x00007f9bc23f5000)
将依赖复制到刚才创建的lib或者lib64目录中 如果这个依赖库在系统的lib目录下 那么就复制到lib目录中1
2
3
4
5
6
7cp /lib64/libdl.so.2 lib64/
cp /lib64/libpthread.so.0 lib64/
cp /lib64/libcrypt.so.1 lib64/
cp /lib64/libz.so.1 lib64/
cp /lib64/libc.so.6 lib64/
cp /lib64/ld-linux-x86-64.so.2 lib64/
cp /lib64/libfreebl3.so lib64/
由于Nginx运行需要使用普通用户 所以需要读取passwd文件 读取解析passwd文件需要用到libnss_files.so.2这个库 所以也需要复制这个库到lib64中
1 | cp /lib64/libnss_files.so.2 lib64/ |
如果在ubuntu上编译的话libnss_files.so.2位置在/lib/x86_64-linux-gnu/libnss_files.so.2
复制完成后lib64目录的内容应该如下所示:
[root@localhost nginx]# ls lib64/
ld-linux-x86-64.so.2 libc.so.6 libfreebl3.so libpthread.so.0
libcrypt.so.1 libdl.so.2 libnss_files.so.2 libz.so.1
打包Nginx服务容器
Nginx以及Nginx的运行依赖都已经放到/nginx目录中了 将这个目录移动到和刚才的rootfs同一目录下 然后再创建两个目录 留用
1 | mkdir build target |
认识overlayFS
overlay是一种联合挂载的文件系统 可以将几个不同的目录挂载到一个目录中 然后将所有的文件展示出来 而且在挂载的目录中对文件的操作并不会影响到其他目录中实际的文件
同时 在新版本的docker中 overlay便是docker容器默认使用的存储驱动 但是overlay在内核大于3.18的版本中才被支持 所以尽量用使用较新内核版本的发行版来玩Docker
因为overlay的这些特性 用来辅助制作Nginx服务容器岂不是很方便 因为在挂载的目录中的操作并不会影响基层目录的文件 这样就算误删了文件 也是可以恢复的
现在看起来 应该是这个效果
root@ubuntu:~# ls -lh
total 16K
drwxr-xr-x 9 root root 4.0K Apr 22 12:50 build # overlay的工作目录
drwxr-xr-x 6 root root 4.0K Apr 22 15:34 nginx # 刚才安装的Nginx目录
drwxr-xr-x 18 root root 4.0K Apr 22 11:52 rootfs # busybox的目录
drwxr-xr-x 1 root root 4.0K Apr 22 12:50 target # 这个就是联合挂载到的目标目录
检查overlay驱动是否已经加载 否则加载overlay驱动1
2lsmod |grep overlay
modprobe overlay
如下所示 那么就可以使用overlay文件系统了
root@ubuntu:~# lsmod |grep overlay
overlay 49152 1
root@ubuntu:~# cat /proc/filesystems |grep overlay
nodev overlayfs
nodev overlay
用rootfs和nginx做基层 其中rootfs是第一层 如果还有其他目录 依次用冒号隔开 build做为overlay工作目录 /tmp是overlay必须的一个空目录 将lowerdir中的目录都挂载到target目录上1
mount -t overlay overlay -o lowerdir=rootfs:nginx,upperdir=build,workdir=/tmp target/
接下来可以看到target目录中 rootfs和nginx目录中的内容同时出现在target目录中
root@ubuntu:~# ls target/
bin etc lib linuxrc mnt root sbin tmp var
dev home lib64 media proc run sys usr
root@ubuntu:~# ls target/bin/busybox
target/bin/busybox
root@ubuntu:~# ls target/usr/local/nginx/sbin/nginx
target/usr/local/nginx/sbin/nginx
如果系统刚好不支持overlay文件系统的话 思想原理是相同的 直接将rootfs和nginx目录中的文件都复制到target中吧
Chroot进行根切换
接下来就是配置Nginx的运行环境了 这样才能保证Nginx打包为容器后能正常的运行
通过chroot工具切换当前根到target目录中 这也算文件系统隔离一种的方式
1 | chroot target sh |
可以看到 进入到了一个不同的shell中 ls / 竟然发现了linuxrc文件 现在已经将根切换到target目录中了
/ # ls /
bin etc lib linuxrc mnt root sbin tmp var
dev home lib64 media proc run sys usr
根据执行Nginx的报错进行配置
接下来的操作在这个根中进行 配置Nginx的运行环境 这些操作都是通过不断的执行nginx程序 查看nginx的报错总结的
1 | adduser nginx # 添加nginx用户 |
再次运行nginx程序 这时候遇到了ginx: [emerg] open(“/dev/null”) failed的错误 原因是打不开这个设备文件 这个只能通过启动为一个Docker容器来解决了
/ # /usr/local/nginx/sbin/nginx
nginx: [emerg] open("/dev/null") failed (2: No such file or directory)
修改Nginx配置文件
剩下的就是修改nginx的配置文件了
1 | vi /etc/nginx.conf |
第一行添加 daemon off; 让nginx前台运行 这个必须添加
http{}中添加 autoindex on; 运行目录索引 可以根据实际情况添加
http{
server{
location / {
root /var/www/html; # root改为/var/www/html 可以根据实际情况更改
index index.html index.htm;
}
}
}
将目录打包为Docker容器
输入exit或者键入 Ctrl+d 就可以退出chroot 回到正常的根中了 接下来 将数据打包为Docker容器 可以看到 大小仅仅8M1
2cd target/
tar -cf /dev/stdout *|docker import - nginx:1.9.15
root@ubuntu:~/target# docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
nginx 1.9.15 99089cedcc48 3 seconds ago 8.071 MB
启动容器 检查是否成功
然后后台启动这个容器 并且查看容器IP 通过curl命令检查nginx是否正常运行 可以看到 成功返回 hello nginx1
2docker run -d --name nginx nginx:1.9.15 nginx
docker inspect --format '{{.NetworkSettings.IPAddress}}' nginx
root@ubuntu:~/target# docker run -d --name nginx nginx:1.9.15 nginx
0db0d45152948339fb0c9a23e8dfba6798c650e24075d725ba2d3021eb3b801b
root@ubuntu:~/target# docker inspect --format '{{.NetworkSettings.IPAddress}}' nginx
172.17.0.8
root@ubuntu:~/target# curl 172.17.0.8
<h1>hello nginx</h1>
root@ubuntu:~/target#
这样 一个非常小 但是可以提供完整功能的Nginx服务容器就打包完成了
overlayFS文件误删的恢复
如果在制作这个容器的过程中 不幸误删了target目录中的文件 还记的build这个文件吗1
rm -rf linuxrc
root@ubuntu:~/target# ls -l ../build/linuxrc
c--------- 1 root root 0, 0 Apr 22 17:28 ../build/linuxrc
可以看到 build目录中出现了一个主次设备号都为0的字符设备 只要删除了这个字符设备 文件就恢复了1
rm -rf ../build/linuxrc
root@ubuntu:~/target# ls
bin etc lib linuxrc mnt root sbin tmp var
dev home lib64 media proc run sys usr
附录
将Nginx容器镜像保存为文件
如果想把这个容器共享给其他人使用 除了使用push到仓库中 还可以直接通过文件的方式共享
配置一个默认启动命令
导入这个Nginx服务容器之后 每次启动都需要手动输入nginx命令 这样显的比较麻烦 可以通过Dockerfile的方式给这个容器配置一个默认的启动命令
1 | vim Dockerfile |
键入以下内容:
root@ubuntu:~# cat Dockerfile
FROM nginx:1.9.15
CMD nginx
nginx就是启动的命令 如果命令还带有参数 可以直接写出 例如 CMD nginx -s reload
1 | docker build -t nginx . # 给新镜像配置一个标签 记得不要忘记了最后的 . |
root@ubuntu:~# docker build -t nginx .
Sending build context to Docker daemon 25.09 MB
Step 1 : FROM nginx:1.9.15
---> 99089cedcc48
Step 2 : CMD nginx
---> Running in 12b1a81d553c
---> 114ba21e8f1c
Removing intermediate container 12b1a81d553c
Successfully built 114ba21e8f1c
root@ubuntu:~# docker run -d nginx:latest
0f3f39dd3aea5a16b10a470ffecca716b31d0deab5420b07ee86bd1180bc2256
可以看到 启动这个新容器已经不需要输入nginx命令了 其实还有一个方法可以更改默认启动命令 通过直接修改镜像的配置文件
可以看到 nginx:1.9.15镜像的ID是99089cedcc48 这只是完整ID的一部分 配置信息保存在/var/lib/docker/graph中
root@ubuntu:~# docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
nginx 1.9.15 99089cedcc48 48 minutes ago 8.071 MB
1 | cd /var/lib/docker/graph |
这时就打开了这个镜像的配置文件 Cmd字段就是默认命令 还可以顺便改下comment信息
"comment":"nginx 1.9.15", # comment字段改为你想表达的信息
"Cmd":["yunfwe"], # Cmd字段有两个 第一个是作者信息
"Cmd":["nginx"], # 第二个才是启动命令
如果运行命令有其他参数的话 用逗号隔开 例如 "Cmd":["nginx","-s","reload"],
这样就相当于 nginx -s reload 了
接下来使用docker history nginx:1.9.11 查看信息
可以看到 CREATED BY 还有 COMMENT 是自己写的信息了
root@ubuntu:~# docker history 99089cedcc48
IMAGE CREATED CREATED BY SIZE COMMENT
99089cedcc48 58 minutes ago yunfwe 8.071 MB nginx 1.9.15
顺便可以看到运行命令也已经是nginx了
root@ubuntu:~# docker inspect --format {{.Config.Cmd}} nginx:1.9.15
{[nginx]}
这两种修改默认启动命令方法的一个重要区别是 第二种在原本的镜像上修改 是不会产生新的层的 而用Dockerfile修改 相当于新建了一个层 这个层提供了默认启动命令 不信可以使用docker history验证一下
保存为文件
默认docker导出的是个tar归档 这里直接将归档压缩为gz包 文件命名规则是
REPOSITORY_TAG-Type.tar.gz
其中的Type是标识这个文件是由镜像保存的还是已经生成的容器导出的
1 | docker save nginx:1.9.15 |gzip > nginx_1.9.15-image.tar.gz |
要恢复的话也很简单 docker支持直接从gz压缩包中恢复
导入新的镜像 这个镜像的标签可能会丢失 但是id号还在 给这个id好重新打个标签
1 | docker load < nginx_1.9.15-image.tar.gz |