@adonia
2016-04-04T16:12:21.000000Z
字数 7691
阅读 204
Docker
docker
Docker Image(镜像文件)是Docker中的一种静态资源。假设Docker的基础资源---Linux Kernel(cgroups、namespace及lxc等)为Layer0,那Docker镜像就是基于Layer0构建的资源。
Docker镜像的构建都是基于基础镜像
完成的,即base image
。例如,如果想要构建自己的SUSE环境,需要在OpenSUSE的基础上增加基础软件和依赖包等,相应的Dockerfile描述如下:
FROM opensuse:latest
ENV JAVA_PATH /opt/java
ENV JAVA_HOME $JAVA_PATH/jdk1.8.0_20
# setup dependency
COPY unzip-6.00-11.7.1.x86_64.rpm /tmp
RUN rpm -ivh /tmp/unzip-6.00-11.7.1.x86_64.rpm
# setup jdk
RUN mkdir -p $JAVA_PATH
COPY jdk1.8.0_20.zip $JAVA_PATH
WORKDIR $JAVA_PATH
RUN unzip jdk1.8.0_20.zip && rm -f jdk1.8.0_20.zip
RUN chmod +x $JAVA_HOME/bin/*
ENV PATH $JAVA_HOME/bin:$PATH
# validate jdk
RUN echo $PATH
RUN java -version
Tips:
1.
FROM
指定了依赖的基础镜像,opensuse
为镜像名称,latest
指明了该镜像的标签(tag
),可以理解为版本。2.镜像名称中不能包含大写字母,必须满足
[a-z0-9](?:-*[a-z0-9])*(?:[._][a-z0-9](?:-*[a-z0-9])*)*
格式要求。3.基础镜像
opensuse
是最基本的Linux Kernel包,后续的组件需要运行在构建的SUSE容器上的话,那就需要增加基础软件和工具包。例如,如果需要执行unzip
命令,就要安装相应的RPM包,即步骤RUN rpm -ivh /tmp/unzip-6.00-11.7.1.x86_64.rpm
;JDK作为JVM运行的必要条件,也要预置进去。
将依赖的RPM和JDK放置在与Dockerfile文件同一目录下,结构如下:
suse-image
|--Dockerfile
|--jdk1.8.0_20.zip
|--unzip-6.00-11.7.1.x86_64.rpm
进入suse-image目录下,执行docker build -t suse .
即可执行镜像的构建了。
Tips:
docker build
即执行镜像的构建,-t
指定了镜像的名称,如上,构建之后,Docker会默认为镜像打上标签latest
。如果需要自己指明标签,可通过:
来分隔,如docker build -t suse:1.14.0 .
2.命令最后的
.
指明了Dockerfile
所在的目录,.
即表示当前目录。
通过docker images
命令即可查询本地缓存的镜像列表了:
REPOSITORY TAG IMAGE ID CREATED SIZE
suse latest 0ed722da7281 13 days ago 105.7 MB
如果重新执行构建命令docker build -t suse .
会发生什么呢?
让我们看下构建输出:
Sending build context to Docker daemon 137.6 MB
Step 1 : FROM opensuse:latest
---> 7617deda9fe6
Step 2 : ENV JAVA_PATH /opt/java
---> Using cache
---> a00eb8430923
Step 3 : ENV JAVA_HOME $JAVA_PATH/jdk1.8.0_20
---> Using cache
---> c4f6f362fa53
Step 4 : COPY unzip-6.00-11.7.1.x86_64.rpm /tmp
---> Using cache
---> e0b1cffbae28
Step 5 : RUN rpm -ivh /tmp/unzip-6.00-11.7.1.x86_64.rpm
---> Using cache
---> f13fe8a2d457
Step 6 : RUN mkdir -p $JAVA_PATH
---> Using cache
---> 65ce06226901
Step 7 : COPY jdk1.8.0_20.zip $JAVA_PATH
---> Using cache
---> de4912c9337b
Step 8 : WORKDIR $JAVA_PATH
---> Using cache
---> c429b72babd6
Step 9 : RUN unzip jdk1.8.0_20.zip && rm -f jdk1.8.0_20.zip
---> Using cache
---> 5d5968def54a
Step 10 : RUN chmod +x $JAVA_HOME/bin/*
---> Using cache
---> c7134e1e4ad9
Step 11 : ENV PATH $JAVA_HOME/bin:$PATH
---> Using cache
---> 07984773586d
Step 12 : RUN echo $PATH
---> Using cache
---> 149e5b09f2c0
Step 13 : RUN java -version
---> Using cache
---> 85cc8f19eeab
Successfully built 85cc8f19eeab
会发现从Step 2
开始,都有Using cache
的标识。这是Docker构建的默认机制,会默认使用缓存来构建镜像。如果这是通过docker images
查询镜像列表的话,会发现suse
镜像的生成时间(CREATED
列)没有发生变化。
如果构建时,指向新的名称,也会出现上述的情况,如
docker build -t suse:1.14.0 .
那如果镜像源文件有更新,想要重新生成镜像,怎么办呢?只要在构建命令中加上--no-cache
就可以了,即docker build --no-cache -t suse .
。查看构建输出日志和镜像列表,会发现镜像文件已经发生更新了。
从上面也可以看出,Docker Image的名称+标签是唯一的,等同于IMAGE ID
.
对于镜像来说,如果不指定tag
的话,默认就是latest
。在执行构建、运行容器和删除镜像时都一样。
可通过IMAGE ID
或者IMAGE NAME
+ TAG
来关联镜像,如下三种方式:
docker rmi a68e59d81983
--- 通过镜像ID来删除镜像
docker rmi tomcat
--- 通过镜像名称,Docker会默认识别tag为latest
的镜像文件
docker rmi tomcat:7.0.62
--- 通过IMAGE NAME
+ TAG
Tips:
1.
docker rmi
即为删除镜像文件的命令,该命令只是将镜像文件从本地仓库移除。2.删除的镜像必须是闲置的,即没有容器关联着。
Docker镜像构建完成之后,就可以基于构建的镜像来运行了,即生成Docker容器(Container
)。可以把Docker Image看作是资源模板,Docker Container看作基于模板生成的一个个实例,没错,可以通过一个镜像生成N个容器。
从Docker Image到Docker Container包含两个过程: 生成容器(Create Container
)和启动容器(Start Container
),可参考Docker Remote API。相应的,Docker Container就包含两个状态: Exited
和Running
。
可以通过docker run image
的方式来运行Docker Container(包括Create和Start过程),此处的image
效果雷同,可以是IMAGE ID
、IMAGE NAME
、IMAGE NAME
+ TAG
。
Tips:
运行容器时,可以指定一些参数,包括
-p
指定映射端口列表(Docker Container端口和VM端口之间的映射,以便于通过外部端口能够访问Docker Container内部的服务),-e
指定运行时环境变量等。
可通过docker ps
或者docker ps -a
查询容器的运行状态,区别在于docker ps
只会显示正在运行的Docker容器列表,而docker ps -a
则会显示全部的容器列表,包括状态为Exited
的。如下:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
976fd81ae9ff tomcat:7.0.62 "/opt/tomcat/apache-t" 8 seconds ago Up 6 seconds 0.0.0.0:18080->8080/tcp agitated_poincare
50d6c47f615e tomcat "/opt/tomcat/apache-t" About a minute ago Exited (130) 35 seconds ago
上面显示了两种状态的Tomcat容器实例,一个是运行状态,而另一个处于停止状态。
Docker容器在运行时,都会生成一个名称标识,可以手动指定,否则的话,Docker会根据内部规则生成一个随机名称。注意:Docker容器的名称是不能重复的!
可以通过--name {name}
在启动容器时指定容器的名称,如docker run -p 18089:8080 --name tomcat tomcat:7.0.62
。
Docker容器的名称是唯一标识,不可以重复,如果重复的话,会发生如下错误:
docker: Error response from daemon: Conflict. The name "/tomcat" is already in use by container 147cb494d6ee3d67d9bffe01883fdf1014d2ca9f9e1247cdd03badd8b83f1628. You have to remove (or rename) that container to be able to reuse that name..
由于Docker名称是唯一识别的,所以容器的相关操作都可以通过名称指定。
前面说过,如果在运行容器时,不指定名称的话,Docker会默认生成一个名称,那么Docker是如何实现的呢?可以看下源码实现:
(部分代码)
var (
left = [...]string{
"admiring",
"adoring",
"agitated",
...
}
// Docker, starting from 0.7.x, generates names from notable scientists and hackers.
// Please, for any amazing man that you add to the list, consider adding an equally amazing woman to it, and vice versa.
right = [...]string{
// Muhammad ibn Jābir al-Ḥarrānī al-Battānī was a founding father of astronomy. https://en.wikipedia.org/wiki/Mu%E1%B8%A5ammad_ibn_J%C4%81bir_al-%E1%B8%A4arr%C4%81n%C4%AB_al-Batt%C4%81n%C4%AB
"albattani",
// Frances E. Allen, became the first female IBM Fellow in 1989. In 2006, she became the first female recipient of the ACM's Turing Award. https://en.wikipedia.org/wiki/Frances_E._Allen
"allen",
// June Almeida - Scottish virologist who took the first pictures of the rubella virus - https://en.wikipedia.org/wiki/June_Almeida
"almeida",
...
}
// GetRandomName generates a random name from the list of adjectives and surnames in this package
// formatted as "adjective_surname". For example 'focused_turing'. If retry is non-zero, a random
// integer between 0 and 10 will be added to the end of the name, e.g `focused_turing3`
func GetRandomName(retry int) string {
rnd := random.Rand
begin:
name := fmt.Sprintf("%s_%s", left[rnd.Intn(len(left))], right[rnd.Intn(len(right))])
if name == "boring_wozniak" /* Steve Wozniak is not boring */ {
goto begin
}
if retry > 0 {
name = fmt.Sprintf("%s%d", name, rnd.Intn(10))
}
return name
}
通过上述源码可以看到,Docker生成容器名称的机制是,维护两个字符串数组left
和right
,left
是一些常见的形容词,right
则是一些伟大的科学家或者黑客。然后,根据内部的随机算法各从left
和right
中取出一个字符串,拼接在一起。如果随机数字不是0
的话,再将该数字拼接在最后。
可以从代码注释中体验下这段代码的乐趣算在。
可以简单估算下,left
和right
的容量大小大概分别是65和140,在加上随机数字的因素,整个随机串的容量大概是 65 * 140 * 10 = 91000. 按照常规的虚拟机规划来说,可分配的端口一般是在1024~65535之间,可以看出上述随机算法还是能满足部署要求的。(按照我们的常规思维,应该是直接通过UUID.randomUUID.toString()
的方式来生成了吧。)
docker start [CONTAINER NAME | ID]
docker stop [CONTAINER NAME | ID]
docker logs [CONTAINER NAME | ID]
docker export [CONTAINER NAME | ID] > xx.tar
,将容器导出成tar包docker cp [CONTAINER NAME | ID]:PATH HOSTPATH
,将容器中的文件/目录拷贝到VM上,PATH
是指Docker Container中的目录或者文件,HOSTPATH
是指导出到VM上的目标路径。如docker cp tomcat:/opt/tomcat/apache-tomcat-7.0.62/logs .
docker exec [CONTAINER NAME | ID] COMMAND
,例如docker exec tomcat ls /opt/tomcat/apache-tomcat-7.0.62/logs
docker rename OLD_NAME NEW_NAME
docker inspect [CONTAINER NAME | ID]
在Docker中运行的Container,IP都是小网的,也就是说,默认情况下,外部是无法访问其中的服务的。可以通过docker-proxy
来实现Container Port
与Host Port
之间的互通,形成端口映射,以实现服务的外部可访问。
1.Container发布端口:
1) 可以在Dockerfile中,通过EXPOSE
命令导出端口,如在制作TOMCAT
镜像时,需要将其Connector端口导出,这样才能处理外部请求。Dockerfile如下:
FROM suse:14.04
# setup apache tomcat
RUN mkdir /opt/tomcat
COPY apache-tomcat-7.0.62 /opt/tomcat
WORKDIR /opt/tomcat
RUN chmod -R +x /opt/tomcat/bin/*.sh
# Expose the server ports
EXPOSE 8080
EXPOSE 8443
# Set the default command
CMD ["/opt/tomcat/bin/catalina.sh", "run"]
在这里,发布了两个Connector端口,分别是HTTP
和HTTPS
。
之后,制作镜像即可。
2) 在创建容器时指定:
在运行容器时,可通过--expose
指定。如需要发布8080和8443端口,命令如下:
docker run --expose=8080 --expose=8443 tomcat
通过--expose
也可以发布端口段,如--expose=2000-3000
.
2.Host映射端口:
Container中发布端口,只是将端口绑定到Container的小网IP上,外部还是无法访问的。可以通过-p [HOSTIP:HOSTPORT:CONTAINERPORT/PROTOCOL]
的方式来映射,其中:
HOSTIP
: VM端的IP,可选,默认是0.0.0.0
HOSTPORT
: VM端的端口,注意端口冲突
CONTAINERPORT
: CONTAINER发布的端口
PROTOCOL
: 端口协议,TCP
或者UDP
,可选,默认是TCP
例如,上述tomcat发布两个端口: 8080
和8443
,如果想要在VM上分别以18080
和18443
与之映射,可以通过如下命令:
docker run -p 18080:8080 -p 18443:8443 tomcat
3.查看端口映射:
在VM上查看docker-proxy
的映射关系:
root@ubuntu:/home/ubuntu# netstat -anp | grep 8080
tcp6 0 0 :::18080 :::* LISTEN 12991/docker-proxy
root@ubuntu:/home/ubuntu# ps -ef | grep 12991
root 12991 653 0 13:25 ? 00:00:00 docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 18080 -container-ip 172.17.0.2 -container-port 8080
可以看出,18080
(host port)与8080
(container port)形成了映射关系,其中18080
绑定在全网段上,8080
绑定在Container的小网上。
可以通过http://10.137.206.65:8080访问tomcat了。