Docker学习笔记
第1章 简介
与管理程序虚拟化(hypervisor virtualization)的区别:
管理程序虚拟化:通过中间层将一台或多台独立的机器虚拟运行于物理硬件之上
容器:直接运行在操作系统内核之上的用户空间
得益于现代Linux的内核特性(如control group、namespace),容器与宿主机之间的隔离更彻底,可以拥有独立的网络和存储栈,以及资源管理能力。
直接使用操作系统的系统调用接口。
推荐单个容器只运行一个应用程序或进程,使得形成一个分布式的应用程序模型,促使部署、扩展或调试应用程序都变得简单。
Docker核心组件
- Docker客户端和服务器,也称为Docker引擎
- Docker镜像
- Registry
- Docker容器
镜像是Docker生命周期中的构建或打包阶段,而容器是启动或执行阶段。
定义
一个命令行程序、一个后台守护进程、一组远程服务
第2章 安装
使用脚本安装
curl https://get.docker.com/ | sh
升级Docker
apt-get update
apt-get install docker-engine
Linux内核版本查看:
cat /proc/version 或 uname -a
docker log路径
/var/log/docker.log
升级Linux内核
aptitude search linux-image
apt-get install linux-image-3.10.36-openstack-amd64
升级之后执行reboot重启系统
检查网络配置
查看是否存在docker0网桥,默认安装了docker会自动给你创建一个docker0的网桥:
ip a show docker0
如果docker0网桥不存在,创建一个网桥:
aptitude install bridge-utils
brctl addbr docker0
ip addr add 172.41.1.1/24 dev docker0
ip link set docker0 up
网桥的删除:
/etc/init.d/docker stop
ip link set dev docker0 down
brctl delbr docker0
查看创建的网桥:
brctl show
注意:给网桥分配的网段是私有网段,应该根据RFC1918规定的协议规定的网段来分配。另外我们的docker是跑在云主机上面的,云主机的虚拟DNS服务器也是私有网络的地址,所以注意千万不能与虚拟DNS地址重复,否则无法访问外网。查看云主机的虚拟DNS地址的通过如下命令:
nslookup dns
查看ip_forward是否打开
cat /proc/sys/net/ipv4/ip_forward
如果ip_forward为0,要通过下面的命令将ip_forward打开:
echo 1 > /proc/sys/net/ipv4/ip_forward
启动docker守护进程(docker daemon)
/etc/init.d/docker start 或 service docker start
给非root用户添加docker执行权限
usermod -aG docker your-user
用户界面
Shipyard: link github
DockerUI: github
Kitematice: OS X 和Windows下的GUI界面工具
第3章 Docker入门
docker info 查看容器、镜像等基本信息
docker run -i -t debian /bin/bash 使用debian镜像创建并启动一个容器,然后在启动的容器中执行/bin/bash命令
-i:保证容器中STDIN开启
-t: 为创建的容器分配一个伪tty终端
这两个参数为我们与所创建的容器提供了一种shell交互方式。
新创建的容器拥有自己的网络、IP地址,以及一个用来和宿主机进行通信的桥接网络接口。
root@hzbxs-azkaban-6:~# docker run -i -t debian /bin/bash
Unable to find image 'debian:latest' locally
latest: Pulling from library/debian
06b22ddb1913: Pull complete
Digest: sha256:6ccbcbf362dbc4add74711cb774751b59cdfd7aed16c3c29aaecbea871952fe0
Status: Downloaded newer image for debian:latest
root@2053eb3eab37:/# hostname
2053eb3eab37
root@2053eb3eab37:/# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
7: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:29:01:02 brd ff:ff:ff:ff:ff:ff
inet 172.41.1.2/24 scope global eth0
valid_lft forever preferred_lft forever
root@2053eb3eab37:/# cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.41.1.2 2053eb3eab37
Docker的工作目录 /var/lib/docker,该目录存放了镜像、容器、及容器的配置。所有的容器都保存在/var/lib/dokcer/containers目录下。
第4章 使用Docker镜像和仓库
Dokcer镜像本质是由多个只读文件系统叠加而成。
- 最底层是一个引导文件系统bootfs,用于引导容器的启动。当容器启动后,引导文件系统就会被卸载(unmount)
- 第二层是root文件系统rootfs,如Debian或者Ubuntu等操作系统
- 这样的文件系统称为镜像,可以依次往上叠加。位于下面的镜像称为父镜像(parent image)
- 最后,当从一个镜像启动容器时,会在最上层再加载一个读写文件系统。
最底层的镜像Ubuntu也称为基础镜像(base image)。
写时复制(copy on write):当容器第一次启动时,初始的“读写层”是空的。当修改一个文件时,文件首先会从“只读层”复制到“读写层”,然后在“读写层”对该文件的副本进行更改。所以,每个“只读镜像层”都时只读的,永远不会变化。
读写层加上其下面的镜像层,以及一些配置数据,构成了一个容器。
image-layering framework:镜像分层框架
构建镜像
docker commit (不推荐)
1)docker run 使用一个已有的镜像启动容器
2)apt-get -y install apache2 安装apache2
3)docker commit {container_id} znirepo/apache2 提交到znirepo仓库下
注意:提交的是创建容器的镜像与容器的当前状态之间差异的部分! 所以该更新非常轻量。
docker build 和 Dockerfile
基于DSL(Domain Specific Language)语法的指令来构建镜像:更有可重复性、透明性以及幂等性。
首先创建一个static_web目录,用来保存Dockerfile,作为我们的构建环境(build environment)。Docker则称此环境为上下文(context)或者构建上下文(build context)。
mkdir static_web
cd static_web
touch Dockerfile
编辑Dockerfile:
# zni first Dockerfile
FROM debian:latest
MAINTAINER zni "zni.feng@gmail.com"
RUN apt-get update && apt-get install -y nginx
RUN echo 'Hi, I am znifeng, in container.' >/usr/share/nginx/html/index.html
EXPOSE 80
即:从debian:latest镜像启动一个容器,然后输入维护者及邮箱,再执行nginx安装语句,然后再修改nginx的index.html。开放端口80。
使用这种方式,镜像的构建过程中,每执行一条指令,都会创建一个新的镜像层,并对镜像进行提交。
指令都必须是大写字母。
使用Dockerfile构建镜像的大体流程:
(1) 从基础镜像运行一个容器
(2) 执行一条指令,对容器作出修改
(3) 执行类似docker commit的操作,提交一个新的镜像层
(4) Docker再基于刚提交的镜像运行一个新容器
(5) 执行Dockerfile的下一条指令,直到所有命令都执行完毕
然后用该Dockerfile构建新镜像,在构建环境(statci_web目录)下执行:
docker build -t="{repo_name}/{image_name}:{tag_name}" .
最后的"." 表示当前目录。
构建示例:
root@hzbxs-azkaban-6:~/static_web# docker build -t="znirepo/static_web:v1" .
Sending build context to Docker daemon 2.048kB
Step 1/5 : FROM debian:latest
---> a20fd0d59cf1
Step 2/5 : MAINTAINER zni "zni.feng@gmail.com"
---> Running in 0567c3d64cf1
---> 8c55eb76ee9b
Removing intermediate container 0567c3d64cf1
Step 3/5 : RUN apt-get update && apt-get install -y nginx
---> Running in a7cedaf9c15b
Get:1 http://security.debian.org stretch/updates InRelease [62.9 kB]
Get:2 http://security.debian.org stretch/updates/main amd64 Packages [156 kB]
Ign:3 http://deb.debian.org/debian stretch InRelease
Get:4 http://deb.debian.org/debian stretch-updates InRelease [88.5 kB]
Get:5 http://deb.debian.org/debian stretch Release [118 kB]
Get:6 http://deb.debian.org/debian stretch Release.gpg [2373 B]
Get:7 http://deb.debian.org/debian stretch/main amd64 Packages [9497 kB]
...
...
Processing triggers for sgml-base (1.29) ...
---> 82d3e680c11d
Removing intermediate container a7cedaf9c15b
Step 4/5 : RUN echo 'Hi, I am znifeng, in container.' >/usr/share/nginx/html/index.html
---> Running in 84cf77cd6228
---> 68006aba4626
Removing intermediate container 84cf77cd6228
Step 5/5 : EXPOSE 80
---> Running in a2ea23c7d7af
---> 6d03c5922d71
Removing intermediate container a2ea23c7d7af
Successfully built 6d03c5922d71
Successfully tagged znirepo/static_web:v1
查看新生成的image:
root@hzbxs-azkaban-6:~/static_web# docker images znirepo/static_web
REPOSITORY TAG IMAGE ID CREATED SIZE
znirepo/static_web v1 6d03c5922d71 3 minutes ago 176MB
也可以直接使用gitlab上的Dockerfile。如
docker build -t="{repo_name}/{image_name}:{tag_name}" {gitUrl}
构建缓存:
当构建时发生了失败,修改了Dockerfile重新构建时,Docker会直接从第一条发生了变化的指令开始。
如果想忽略缓存,可以加参数--no-cache,如:
dokcer build --no-cache -t="znirepo/static_web:v1" .
Dockerfile模板:
# zni first Dockerfile
FROM debian:latest
MAINTAINER zni "zni.feng@gmail.com"
ENV REFRESHED_AT 2017-08-12
RUN apt-get -qq update
apt-get的"-qq"参数:No output except for errors
从新镜像启动容器,指定端口
1,映射到宿主机随机端口
root@hzbxs-azkaban-6:~/static_web# docker run -d -p 80 --name zni2ndweb znirepo/static_web:v1 nginx -g "daemon off;"
37b9dc28d2a6110d0fe19d5cc920d40f711d51aee223156c56d143167536c343
root@hzbxs-azkaban-6:~/static_web# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
37b9dc28d2a6 znirepo/static_web:v1 "nginx -g 'daemon ..." 4 seconds ago Up 1 second 0.0.0.0:8193->80/tcp zni2ndweb
4d68dddfa79f znirepo/static_web:v1 "/bin/bash" 5 days ago Up About a minute 80/tcp zni1stweb
其中-p指定了容器的80端口,默认情况下它会随机映射到宿主机的一个位于32768~61000的端口。nginx -g "daemon off;"是容器内执行的命令,将以前台运行的方式启动Nginx。
端口映射情况查看docker port:
root@hzbxs-azkaban-6:~/static_web# docker port zni2ndweb
80/tcp -> 0.0.0.0:8193
root@hzbxs-azkaban-6:~/static_web# docker port zni2ndweb 80
0.0.0.0:8193
可以看到zni2ndweb端口已经映射到了宿主机的8193端口。 而zni1stweb启动时没有加-p参数,没有任何端口占用。
2. 映射到宿主机指定端口
也可以人为地将容器的端口映射到想要指定的宿主机端口:
root@hzbxs-azkaban-6:~/static_web# docker run -d -p 8080:80 --name zni3rdweb znirepo/static_web:v1 nginx -g "daemon off;"
40131f51f33d671606339212eb9004646c92542abe952e137965ebcdd3c35d17
root@hzbxs-azkaban-6:~/static_web# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
40131f51f33d znirepo/static_web:v1 "nginx -g 'daemon ..." 3 seconds ago Up 2 seconds 0.0.0.0:8080->80/tcp zni3rdweb
37b9dc28d2a6 znirepo/static_web:v1 "nginx -g 'daemon ..." 23 minutes ago Up 23 minutes 0.0.0.0:8193->80/tcp zni2ndweb
4d68dddfa79f znirepo/static_web:v1 "/bin/bash" 5 days ago Up 24 minutes 80/tcp zni1stweb
root@hzbxs-azkaban-6:~/static_web# docker port zni3rdweb
80/tcp -> 0.0.0.0:8080
可以看到,我们将容器的80端口直接映射到了宿主机的8080端口。
3. 映射到宿主机的特定网络接口(ip地址)的随机端口
如果想绑定容器的端口到宿主机的特定网络接口(ip地址)的随机端口:
docker run -d -p 127.0.0.1::80 --name zni4thweb znirepo/static_web:v1 nginx -g "daemon off;"
4. -P对外公开所有EXPOSE的端口:
使用-P参数直接对外公开在Dockerfile中通过EXPOSE指令公开的所有端口:
docker run -d -P --name zni5thweb znirepo/static_web:v1 nginx -g "daemon off;"
此处会将EXPOSE的端口对宿主机公开,并且绑定到宿主机的一个随机端口。
Dockerfile指令
1. CMD
等同于docker run命令启动容器时要运行的命令。 但是docker run会覆盖Dockerfile中的CMD命令。
与Dockerfile中的RUN指令的区别是:RUN是构建镜像时运行的指令,而CMD是容器运行时的指令。
注意的是,Dockerfile中只能指定一条CMD指令,如果有多条指令,只有最后条会被使用!
CMD ["/bin/bash", "-l"]
2. ENTRYPOINT
其实所有的CMD命令,docker run启动命令,都是作为参数传给ENTRYPOINT来执行的。
ENTRYPOINT ["/user/sbin/nginx"]
3. WORKDIR
容器内部的一个工作目录,就像Jenkins的workspace一样,容器启动时运行的指令会在该目录下执行。
在Dockerfile中可以为不同的指令指定不同的目录。
可以通过在启动时用-w覆盖运行时目录
WORKDIR /opt/webapp/db
RUN bundle install
WORKDIR /opt/webapp
ENTRYPOINT ["rackup"]
4. ENV
设置环境变量
ENV RVM_PATH /home/rvm
5. USER
指定运行该镜像的用户(组)
USER {user}
USER {user}:{group}
6. VOLUME
卷:可以存在于一个或者多个容器内的特定的目录。该目录可以绕过联合文件系统提供共享数据或者对数据进行持久化的功能。
VOLUME ["/opt/project", "/data"]
也可以在docker run启动时通过-v参数指定卷
独立于容器。它的修改是立即生效的!
7. ADD
将构建环境下的文件和目录复制到镜像中。
ADD software.lic /opt/application/software.lic
源文件可以是URL或上下文(构建环境)中的文件、目录
在处理gzip、bzip2、xz等归档文件时,自动会把里面的内容解压
8. COPY
跟ADD类似,唯一区别是不会去做文件提取和解压的工作。
COPY software.lic /opt/application/software.lic
9. LABEL
指定元数据
LABEL location="New York" type="Data Center" role="Web Server"
可以使用docker inspect查看容器标签,以查看其中的元数据
10. STOPSIGNAL
11. ARG
定义构建变量
12. ONBUILD
触发器
第5章 使用Docker构建并测试Web应用程序
容器之间的两种通信方式:
Docker Networking
推荐在Docker1.9之后的版本使用。创建容器专用网络,将所有容器放到一个网络下
相关命令:
- docker network create app 创建一个名为app的网络
- docker network inspect app 查看app网络的详情
- docker network ls 查看当前系统中的所有网络
- docker run -d --net=app --name db znirepo/redis 在app网络中创建redis容器
- cat /etc/hosts 在容器中执行,查看容器的hosts文件配置
- docker network connect app db2 将正在运行的容器db2添加到已有的网络app中
- docker network disconnect app db2 断开一个容器与指定网络的链接
Docker链接
推荐在Docker1.9之前的版本使用。
docker run -p 4567 --name webapp --link redis:db -t -i -v $PWD/webapp_redis:/opt/webapp znirepo/web /bin/bash
即用--link 指定链接的容器名为redis,并给链接创建别名db
卷的使用
对卷的修改会直接生效,并绕过镜像。当提交或创建镜像时,卷不被包含在镜像里。
应用场景:
- 希望同时对代码做开发和测试
- 代码改动很频繁,不想在开发过程中重构镜像
- 希望在多个容器间共享代码
docker run -i -t -p 81:8443 --name 1stazweb -v /home/hadoop/:/home/hadoop/:rw znirepo/azkaban_web:1.0
私有仓库Registry的构建
先修改配置文件/etc/default/docker,指定仓库地址和允许http访问:
DOCKER_OPTS="--insecure-registry {hostname}:5000"
然后重启docker:
/etc/init.d/docker restart
创建本地目录/root/zni_docker_registry 作为VOLUME用于存储镜像。然后启动registry容器作为镜像库:
docker run -d -p 5000:5000 --restart=always --name registry -v /root/zni_docker_registry:/var/lib/registry registry:2
其中restart=always会使docker daemon退出重启后容器服务自动恢复。
检查registry是否启动的方法:
在浏览器输入:
{hostname}:5000/v2
看是否有响应。
也可以输入:
{hostname}:5000/v2/_catalog
查看仓库内容
给原有镜像打tag:
docker tag znirepo/kerberos:v2.0 {hostname}:5000/kerberos:1.0
推送镜像到私有仓库
docker push {hostname}:5000/kerberos:1.0
然后从私有仓库中拉取镜像(此操作可以在其它机器执行):
docker pull {hostname}:5000/kerberos:1.0