上一篇讲了怎么安装Docker和运行hello-world的docker image。 现在我们就说说怎么去创建一个自己的Docker image。 这里,我示例去创建一个Tomcat+我们自己的JAVA Web App的image。
这个过程分下面几部:
1. 下载一个原始的image,后面工作都基于这个image来开展。 我们这里是下载一个tomcat的image。
2. 启动一个tomcat的docker实例,然后修改一些配置,然后用这个实例的ID来生成一个新的image叫mytomcat
3. 编写一个DockerFile, 通过这个DockerFile来生成我们自己myapp的Docker image, 运行并看效果
4. 创建一个本地的repository,把MyWebApp提交到本地repository
5. 一些情况的说明
第一步, 我们来下载一个原始的tomcat到本地:
你先用docker search 命令来查一下,有多少与tomcat相关的image在公共的repository, 然后选中其中一个下载
$ sudo docker search tomcat
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
tomcat Apache Tomcat ... 1193 [OK]
dordoka/tomcat Tomcat 8 ba... 31 [OK]
davidcaste/alpine-tomcat Apache Tomcat 7/8 ... 15 [OK]
cloudesire/tomcat Tomcat server, 6/7/8 12 [OK]
$ sudo docker pull tomcat
Digest: sha256:0fb173e213111be292962336777a134d43f59c1b8cc2da3cbaaf6308ee7a490a
Status: Downloaded newer image for tomcat:latest
注: pull的动作可能会因为网络的问题失败,请多试几次。
$ sudo docker images tomcat
REPOSITORY TAG IMAGE ID CREATED SIZE
tomcat latest 0335e4e8579b 3 days ago 355 MB
第二步启动tomcat的实例,修改配置
$ sudo docker run -it -v /home/master/program:/mnt/software tomcat /bin/bash
注: -it 是有标准输出选项, -v是主机目录到docker容器目录映射, /bin/bash是启动后第一个CMD
进入tomcat 实例的控制台之后,拷贝 tomcat的server.xml文件和 Java的java.security文件到/mnt/software下。 这个是因为这个tomcat实例里没有任何编辑工具(vi都没有),所以我们不得不拷贝他们到映射文件下(这样主机就可以访问),在主机上编辑他们.
[容器内]# cp /usr/local/tomcat/conf/server.xml /mnt/software
[容器内]# cp /etc/java-7-openjdk/security/java.security /mnt/software
在server.xml文件中,appBase改为docker_app,这是因为我们的web app的代码都会部署在/usr/local/tomcat/docker_app,所以我们修改配置文件,让应用目录指向这个文件夹。
<Host name="localhost" appBase="docker_app"
unpackWARs="true" autoDeploy="true">
在java.security中,修改securerandom.source的值,这是因为如果securerandom.source使用默认值的话,会在tomcat启动会耗费很长时间(这个网上有解释,读者可以自己去搜搜)
securerandom.source=file:/dev/./urandom
然后把修改好的文件,覆盖原来的文件
[容器内]# cp -f /mnt/software/java.security /etc/java-7-openjdk/security/
[容器内]# cp -f /mnt/software/server.xml /usr/local/tomcat/conf/
接下来退出容器实例,然后用这个实例ID生成一个新的新的image叫mytomcat
[容器内]# exit
$ sudo docker ps --last 1
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7c456c4b41d1 tomcat "/bin/bash" 31 minutes ago Exited (0) 37 seconds ago serene
$ sudo docker commit 7c45 mytomcat
sha256:c795645c8ad718b60f603cb1514d7e9f65eae6773d851b8738d4a556d52d7c03
docker commit 后面containerID,只要取前四位即可。 然后检查一下image是否已经生成。
$ sudo docker images mytomcat
REPOSITORY TAG IMAGE ID CREATED SIZE
mytomcat latest c795645c8ad7 12 minutes ago 355 MB
OK, 万事俱备就差东风了。
第三步,编写一个DockerFile, 通过这个DockerFile来生成我们自己myapp的Docker image, 运行并看效果
让我们创建APP的工作目录,在工作目录下创建Dockerfile文件和拷贝我们的可执行代码到这个目录下(注,可执行代码必须和Dockerfile在同一目录下。 但是文档上说ADD的指令能支持URL地址,我没试过)
$ mkdir /home/master/program/app_compose
$ cd /home/master/program/app_compose
$ cp -rf /我们的代码目录/image_code /home/master/program/app_compose/
$ vi Dockfile
FROM mytomcat
ADD image_code/ /usr/local/tomcat/
EXPOSE 8080
ENTRYPOINT ["/usr/local/tomcat/bin/catalina.sh"]
CMD ["run"]
FROM是指明原始的image的出处,这里我用的是我们刚刚生成的image, ADD是拷贝一个主机的目录到容器里的某个目录下,这里我是拷贝代码文件夹到tomcat的下做为appBase目录, EXPOSE是指明容器侦听的端口(需要在run的时候指明外界与之对应的外部端口),ENTRYPOINT是容器启动的入口接口,CMD是入口接口的参数。
注: image_code目录结构:$ ls program/app_compose/image_code/docker_app/ROOT/
META-INF/ WEB-INF/
现在可以来生成APP 1.0的image(name是myapp, tag是1.0-init, 以后更新,我们只改tag)
$ sudo docker build --rm --tag myapp:1.0-init /home/master/program/app_compose/
执行完成之后,可以看到image已经生成了:
$ sudo docker images myapp
REPOSITORY TAG IMAGE ID CREATED SIZE
myapp 1.0-init 1d8711421563 34 minutes ago 360 MB
启动它,来看看效果
$ sudo docker run -it -p 8080:8080 myapp:1.0-init
-it是为了你能看到有log输出, 如果你让它运行在后台,你把-it改成-d就行了; -p是指定端口映射HOST_port : CONTAINER_PORT
在浏览器里输入 http://你的公网IP:8080/ 就能看到效果了。
下次更新应用的时候,只要更新image_code里面的文件,重新执行docker build就生成新的image,非常方便。
需要停止docker实例的运行,可以先用docker ps查看当前运行的实例,然后用docker kill 实例ID(前四位即可),来停止一个实例运行。
第四步, 创建一个本地的repository,把myapp提交到本地repository
我们已经创建了myapp的image,在本机使用已经没有问题。当然,你可以通过docker save/load 来保持image到一个文件,通过文件的转移,在其他机器上运行这个image。 但是你无法使用文件的方式在swarm集群中使用。 所以,你需要提交到相关机器都能看到的repository里,一般来说有2个选择: 1. 提交的docker的官方HUB (https://hub.docker.com/)2. 在本地网络里,创建一个本地的repository.
这里,我们就讲一下怎么创建一个本地repository:
首先,要做一件事情去除https错误,因为docker默认用https去获取镜像的,但是如果你本地没有设置https服务的话,在从私有仓库pull镜像的时候,会出现一个错误。
新建一个/etc/docker/daemon.json,在这里添加:
{ "insecure-registries":["你的创建私有仓库的机器IP:5000"] }
在swarm集群网络的每台机器都要添加这个文件.
1. 下载registry的image(pull可能连接timout,可以多试几次), 并运行registry在后台
$ sudo docker pull registry
$ sudo docker run -d -p 5000:5000 registry
查看一下是否已经运行
$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS
f57efc7e30df registry "/entrypoint.sh /e..." About a minute ago
2. 把我们刚才创建myapp的image提交到本地repository
$sudo docker tag myapp:1.0-init 172.19.187.96:5000/myapp:1.0-init
$ sudo docker push 172.19.187.96:5000/myapp:1.0-init
先打一个tag标签,然后提交。 172.19.187.96是我运行机器的内网IP
查看一下image的情况
$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
172.19.187.96:5000/myapp 1.0-init 1d8711421563 About an hour ago 360 MB
myapp 1.0-init 1d8711421563 About an hour ago 360 MB
在slaver机器上,运行 $ sudo docker pull 172.19.187.96:5000/myapp:1.0-init 就可以下载image了。
把image提交到respository的主要用途,是为了后面swarm模式集群的应用部署做准备。
第五,一些情况的说明
1. 更新app, 只要更新代码文件夹,按照第三和第四步,重新build和提交就可以了。
2. 关于在容器内运行程序的权限的问题
之前,我曾经想下一个纯的centos的image,然后在里面安装我们任何想装的service比如tomcat等等,但是我碰到一个关于权限的问题, 在容器内,我无法使用systemctl启动一个service,会返回一个D-BUS什么什么的错误。 查了网上的资料,据说是CentOS7的docker image的bug(原因是说systemd在centos被删除了:systemd in CentOS 7 has been removed and replaced with a fakesystemd package for dependency resolution. This is due to systemd requiring the CAP_SYS_ADMIN capability)。
网上有一个解决办法来处理这个问题,据说最新版的centos已经解决这个问题,但是需要安装下列的Dockerfile来配置:
FROM centos:7
MAINTAINER "you" <your@email.here>
ENV container docker
RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == \
systemd-tmpfiles-setup.service ] || rm -f $i; done); \
rm -f /lib/systemd/system/multi-user.target.wants/*;\
rm -f /etc/systemd/system/*.wants/*;\
rm -f /lib/systemd/system/local-fs.target.wants/*; \
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
rm -f /lib/systemd/system/basic.target.wants/*;\
rm -f /lib/systemd/system/anaconda.target.wants/*;
VOLUME [ "/sys/fs/cgroup" ]
CMD ["/usr/sbin/init"]
生成image之后,可以用 sudo docker run --privileged [其他options] image名 来运行。--priviledged是必须的选项,给与容器实例一个超级权限。 的确这样方式,可以得到最初设想的image(包含很多后装的service 比如sshd, tomcat等等)。 但是当我用这个image去跑swarm模式的时候,当启动swarm service碰到了一个问题,docker service create 不支持--privileged参数,也没有任何相关的权限参数,导致的结果是docker 容器无法启动。
看了很多的资料,发现很多跟权限相关的选项或者命令,在swarm模式下都不支持。 比如在Dockerfile的指令里,CAPS_ADD/CAPS_DROP等在swarm模式下都不支持。
仔细想想,可能原因是在swarm模式,service部署是leader机一条命令部署,全部子节点都会被更新。 在本机上你可以命令的方式要求一个超级权限,但是在子节点那里,通过work代理,可能暂时不支持对权限动态申请(我猜的^_^)。
这里又引申出一个问题,进入到docker实例中的时候,我能看到在docker中的身份是root,那root为什么还有申请权限。 想起自己在2013年的工作经历,那时候做系统安全,要求把一些危险的APP放在LXC(linux container)里,我们在创建LXC实例,就会分配好哪些capabilities, 那些危险的APP即使在容器内取得ROOT权限,它们只能用到我们分配的权限,其他的,它们do nothing。 现在docker是基于LXC,我猜想此root非彼root的问题是同样的原因。