[关闭]
@adonia 2016-04-04T16:12:21.000000Z 字数 7691 阅读 204

Docker

docker


Docker使用记录

Image

Docker Image(镜像文件)是Docker中的一种静态资源。假设Docker的基础资源---Linux Kernel(cgroups、namespace及lxc等)为Layer0,那Docker镜像就是基于Layer0构建的资源。

构建镜像

Docker镜像的构建都是基于基础镜像完成的,即base image。例如,如果想要构建自己的SUSE环境,需要在OpenSUSE的基础上增加基础软件和依赖包等,相应的Dockerfile描述如下:

  1. FROM opensuse:latest
  2. ENV JAVA_PATH /opt/java
  3. ENV JAVA_HOME $JAVA_PATH/jdk1.8.0_20
  4. # setup dependency
  5. COPY unzip-6.00-11.7.1.x86_64.rpm /tmp
  6. RUN rpm -ivh /tmp/unzip-6.00-11.7.1.x86_64.rpm
  7. # setup jdk
  8. RUN mkdir -p $JAVA_PATH
  9. COPY jdk1.8.0_20.zip $JAVA_PATH
  10. WORKDIR $JAVA_PATH
  11. RUN unzip jdk1.8.0_20.zip && rm -f jdk1.8.0_20.zip
  12. RUN chmod +x $JAVA_HOME/bin/*
  13. ENV PATH $JAVA_HOME/bin:$PATH
  14. # validate jdk
  15. RUN echo $PATH
  16. 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文件同一目录下,结构如下:

  1. suse-image
  2. |--Dockerfile
  3. |--jdk1.8.0_20.zip
  4. |--unzip-6.00-11.7.1.x86_64.rpm

进入suse-image目录下,执行docker build -t suse .即可执行镜像的构建了。

Tips:

  1. docker build即执行镜像的构建,-t指定了镜像的名称,如上,构建之后,Docker会默认为镜像打上标签latest。如果需要自己指明标签,可通过:来分隔,如docker build -t suse:1.14.0 .

2.命令最后的.指明了Dockerfile所在的目录,.即表示当前目录。

通过docker images命令即可查询本地缓存的镜像列表了:

  1. REPOSITORY TAG IMAGE ID CREATED SIZE
  2. suse latest 0ed722da7281 13 days ago 105.7 MB

如果重新执行构建命令docker build -t suse .会发生什么呢?
让我们看下构建输出:

  1. Sending build context to Docker daemon 137.6 MB
  2. Step 1 : FROM opensuse:latest
  3. ---> 7617deda9fe6
  4. Step 2 : ENV JAVA_PATH /opt/java
  5. ---> Using cache
  6. ---> a00eb8430923
  7. Step 3 : ENV JAVA_HOME $JAVA_PATH/jdk1.8.0_20
  8. ---> Using cache
  9. ---> c4f6f362fa53
  10. Step 4 : COPY unzip-6.00-11.7.1.x86_64.rpm /tmp
  11. ---> Using cache
  12. ---> e0b1cffbae28
  13. Step 5 : RUN rpm -ivh /tmp/unzip-6.00-11.7.1.x86_64.rpm
  14. ---> Using cache
  15. ---> f13fe8a2d457
  16. Step 6 : RUN mkdir -p $JAVA_PATH
  17. ---> Using cache
  18. ---> 65ce06226901
  19. Step 7 : COPY jdk1.8.0_20.zip $JAVA_PATH
  20. ---> Using cache
  21. ---> de4912c9337b
  22. Step 8 : WORKDIR $JAVA_PATH
  23. ---> Using cache
  24. ---> c429b72babd6
  25. Step 9 : RUN unzip jdk1.8.0_20.zip && rm -f jdk1.8.0_20.zip
  26. ---> Using cache
  27. ---> 5d5968def54a
  28. Step 10 : RUN chmod +x $JAVA_HOME/bin/*
  29. ---> Using cache
  30. ---> c7134e1e4ad9
  31. Step 11 : ENV PATH $JAVA_HOME/bin:$PATH
  32. ---> Using cache
  33. ---> 07984773586d
  34. Step 12 : RUN echo $PATH
  35. ---> Using cache
  36. ---> 149e5b09f2c0
  37. Step 13 : RUN java -version
  38. ---> Using cache
  39. ---> 85cc8f19eeab
  40. 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.删除的镜像必须是闲置的,即没有容器关联着。

Container

Docker镜像构建完成之后,就可以基于构建的镜像来运行了,即生成Docker容器(Container)。可以把Docker Image看作是资源模板,Docker Container看作基于模板生成的一个个实例,没错,可以通过一个镜像生成N个容器。

从Docker Image到Docker Container包含两个过程: 生成容器(Create Container)和启动容器(Start Container),可参考Docker Remote API。相应的,Docker Container就包含两个状态: ExitedRunning

可以通过docker run image的方式来运行Docker Container(包括Create和Start过程),此处的image效果雷同,可以是IMAGE IDIMAGE NAMEIMAGE NAME + TAG

Tips:

运行容器时,可以指定一些参数,包括-p指定映射端口列表(Docker Container端口和VM端口之间的映射,以便于通过外部端口能够访问Docker Container内部的服务),-e指定运行时环境变量等。

查询容器状态

可通过docker ps或者docker ps -a查询容器的运行状态,区别在于docker ps只会显示正在运行的Docker容器列表,而docker ps -a则会显示全部的容器列表,包括状态为Exited的。如下:

  1. CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
  2. 976fd81ae9ff tomcat:7.0.62 "/opt/tomcat/apache-t" 8 seconds ago Up 6 seconds 0.0.0.0:18080->8080/tcp agitated_poincare
  3. 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容器的名称是唯一标识,不可以重复,如果重复的话,会发生如下错误:

  1. 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是如何实现的呢?可以看下源码实现:

(部分代码)

  1. var (
  2. left = [...]string{
  3. "admiring",
  4. "adoring",
  5. "agitated",
  6. ...
  7. }
  8. // Docker, starting from 0.7.x, generates names from notable scientists and hackers.
  9. // Please, for any amazing man that you add to the list, consider adding an equally amazing woman to it, and vice versa.
  10. right = [...]string{
  11. // 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
  12. "albattani",
  13. // 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
  14. "allen",
  15. // June Almeida - Scottish virologist who took the first pictures of the rubella virus - https://en.wikipedia.org/wiki/June_Almeida
  16. "almeida",
  17. ...
  18. }
  19. // GetRandomName generates a random name from the list of adjectives and surnames in this package
  20. // formatted as "adjective_surname". For example 'focused_turing'. If retry is non-zero, a random
  21. // integer between 0 and 10 will be added to the end of the name, e.g `focused_turing3`
  22. func GetRandomName(retry int) string {
  23. rnd := random.Rand
  24. begin:
  25. name := fmt.Sprintf("%s_%s", left[rnd.Intn(len(left))], right[rnd.Intn(len(right))])
  26. if name == "boring_wozniak" /* Steve Wozniak is not boring */ {
  27. goto begin
  28. }
  29. if retry > 0 {
  30. name = fmt.Sprintf("%s%d", name, rnd.Intn(10))
  31. }
  32. return name
  33. }

通过上述源码可以看到,Docker生成容器名称的机制是,维护两个字符串数组leftrightleft是一些常见的形容词,right则是一些伟大的科学家或者黑客。然后,根据内部的随机算法各从leftright中取出一个字符串,拼接在一起。如果随机数字不是0的话,再将该数字拼接在最后。

可以从代码注释中体验下这段代码的乐趣算在。

可以简单估算下,leftright的容量大小大概分别是65和140,在加上随机数字的因素,整个随机串的容量大概是 65 * 140 * 10 = 91000. 按照常规的虚拟机规划来说,可分配的端口一般是在1024~65535之间,可以看出上述随机算法还是能满足部署要求的。(按照我们的常规思维,应该是直接通过UUID.randomUUID.toString()的方式来生成了吧。)

容器操作

端口映射

在Docker中运行的Container,IP都是小网的,也就是说,默认情况下,外部是无法访问其中的服务的。可以通过docker-proxy来实现Container PortHost Port之间的互通,形成端口映射,以实现服务的外部可访问。

1.Container发布端口:
1) 可以在Dockerfile中,通过EXPOSE命令导出端口,如在制作TOMCAT镜像时,需要将其Connector端口导出,这样才能处理外部请求。Dockerfile如下:

  1. FROM suse:14.04
  2. # setup apache tomcat
  3. RUN mkdir /opt/tomcat
  4. COPY apache-tomcat-7.0.62 /opt/tomcat
  5. WORKDIR /opt/tomcat
  6. RUN chmod -R +x /opt/tomcat/bin/*.sh
  7. # Expose the server ports
  8. EXPOSE 8080
  9. EXPOSE 8443
  10. # Set the default command
  11. CMD ["/opt/tomcat/bin/catalina.sh", "run"]

在这里,发布了两个Connector端口,分别是HTTPHTTPS
之后,制作镜像即可。

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发布两个端口: 80808443,如果想要在VM上分别以1808018443与之映射,可以通过如下命令:
docker run -p 18080:8080 -p 18443:8443 tomcat

3.查看端口映射:
在VM上查看docker-proxy的映射关系:

  1. root@ubuntu:/home/ubuntu# netstat -anp | grep 8080
  2. tcp6 0 0 :::18080 :::* LISTEN 12991/docker-proxy
  3. root@ubuntu:/home/ubuntu# ps -ef | grep 12991
  4. 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了。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注