Docker学习笔记

docker底层使用的技术

  1. 隔离技术namespace,范围包括PID、Mount、UTS、IPC、Network、User
  2. 资源限制Cgroups,限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等,可以在/sys/fs/cgroup下查看各种Cgroups的配置

Cgroups存在的问题:/proc文件系统不了解 Cgroups 限制的存在,导致TOP等指令读取的仍是宿主机下的/proc文件信息

解决方案:把宿主机的 /var/lib/lxcfs/proc/* 文件挂载到容器的/proc/*(lxcfs)

  1. 容器镜像rootfs,挂载在容器根目录上、用来为容器进程提供隔离后执行环境,通常会包括如下所示的一些目录和文件,比如 /bin,/etc,/proc等等
1
2
ls /
bin dev etc home lib lib64 mnt opt proc root run sbin sys tmp usr var

需要明确的是,rootfs 只是一个操作系统所包含的文件、配置和目录,并不包括操作系统内核。在 Linux 操作系统中,这两部分是分开存放的,操作系统只有在开机启动时才会加载指定版本的内核镜像。实际上,同一台机器上的所有容器,都共享宿主机操作系统的内核。

镜像分层

镜像的分层机制主要依靠了联合文件系统UnionFS(Union File System),其功能是将多个不同位置的目录联合挂载(union mount)到同一个目录下。

不同系统的UnionFS实现:aufs是ubuntu 常用的,device mapper 是 centos,btrfs 是 SUSE,overlayfs ubuntu 和 centos 都会使用,现在最新的 docker 版本中默认两个系统都是使用的 overlayfs,vfs 和 zfs 常用在 solaris 系统。

容器的rootfs由三部分组成,从下到上依次为:

  1. 只读层,内部有五层,它们的挂载方式都是只读的(ro+wh,即 readonly+whiteout),这些层都以增量的方式分别包含了操作系统的一部分,只读层在同一宿主机上的多个镜像间是共享的,因此并不会重复占用空间;
  2. Init层,它是一个以“-init”结尾的层,夹在只读层和读写层之间。Init 层是 Docker自己生成的一个内部层,专门用来存放临时修改过的/etc/hosts、/etc/resolv.conf等文件。用户执行docker commit时只会提交可读写层,不会提交本层的内容;
  3. 可读写层,它是这个容器的 rootfs 最上面的一层,它的挂载方式为:rw,即 read write。在没有写入文件之前,这个目录是空的。而一旦在容器里做了写操作,你修改产生的内容就会以增量的方式出现在这个层中。需要删除只读层中文件时,通过whiteout的方式进行”遮挡”。

Docker基本操作

1
docker build -t helloworld

docker build 会自动加载当前目录下的Dockerfile 文件,然后按照顺序,执行文件中的原语。其中,-t 的作用是给这个镜像加一个 Tag,即:起一个好听的名字。

1
docker image ls
1
2
REPOSITORY            TAG                 IMAGE ID
helloworld latest 653287cdf998

查看镜像列表

1
docker run -p 4000:80 helloworld

运行名为helloworld的镜像,若在Dockerfile中没有指定CMD启动命令,则需要在后面追加进程的启动指令

1
docker ps
1
2
CONTAINER ID        IMAGE               COMMAND             CREATED
4ddf4638572d helloworld "python app.py" 10 seconds ago

用于查看运行中的容器

1
docker exec -it 4ddf4638572d /bin/sh

通过exec可以进入指定容器内部进行操作

1
docker inspect --format '{{ .State.Pid }}'  4ddf4638572d

通过inspect可以查看当前正在运行的docker的进程号(PID)

镜像制作

比较好的方式是通过编写dockerfile来创建镜像,一个dockerfile示例如下

1
2
3
4
5
6
7
8
9
10
11
FROM openjdk:8u191-jdk-alpine
VOLUME /tmp
ARG JAR_FILE
ADD target/${JAR_FILE} app.jar
RUN echo http://mirrors.tuna.tsinghua.edu.cn/alpine/v3.9/main > /etc/apk/repositories \
&& echo http://mirrors.tuna.tsinghua.edu.cn/alpine/v3.9/community >> /etc/apk/repositories \
&& apk update \
&& apk add tzdata \
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& sh -c 'touch /app.jar'
ENTRYPOINT exec java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar
  1. FROM:这是引入一个父镜像,在此基础上进行添加只读层。镜像可以理解成由一层层只读层组成,FROM下面的命令,可以理解就是在已有的只读层,添加只读层。FROM可以有多个,但最上面的一定是FROM命令。

  2. VOLUME:配置一个具有持久化功能的目录,主机 /var/lib/docker 目录下创建了一个临时文件,并链接到容器的/tmp。该步骤是可选的。
    通俗解释:默认情况下,容器不使用任何 volume,此时,容器的数据被保存在容器之内,它只在容器的生命周期内存在,会随着容器的被删除而被删除。
    因为如果你不想在容器删除后,容器的数据也被删除,那么就可以指定持久化目录。它被设计用来保存数据,而不管容器的生命周期。因此,当你删除一个容器时,Docker 肯定不会自动地删除一个volume。

  3. ARG:设置编译镜像时加入的参数。 这里的JAR_FILE就是maven插件中的<JAR_FILE>${project.build.finalName}.jar</JAR_FILE>

  4. ADD/COPY:COPY复制文件到容器 ,ADD功能更强大是COPY的超集,除了简单的复制以外还可以将文件夹复制为压缩文件以及复制网络文件等。

  5. ENTRYPOINT :容器启动时执行的命令。这里就是java -jar app.jar

注:dockerfile的所有内容都在只读层中

如何将镜像推送到镜像仓库

  1. 使用 docker login 命令登录镜像仓库,如以阿里云镜像仓库为例
1
sudo docker login --username=meteor-xht@163.com registry.cn-hangzhou.aliyuncs.com
  1. 使用docker tag 命令重新命名,注意,这里的meteor-xht是你自己的账户名称,helloworld是镜像名称,v1是指定的版本号
1
docker tag helloworld meteor-xht/helloworld:v1
  1. 使用docker push推送到镜像仓库
1
docker push meteor-xht/helloworld:v1

此外,也可以通过docker commit指令,把一个正在运行的容器,直接提交为一个镜像,再通过docker push推送。

1
docker commit 4ddf4638572d meteor-xht/helloworld:v2