1.什么是Docker?
Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的 Linux或Windows 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口。
一个完整的Docker有以下几个部分组成:
DockerClient客户端
Docker Daemon守护进程
Docker Image镜像
DockerContainer容器
起源
Docker 是 PaaS 提供商 dotCloud 开源的一个基于 LXC 的高级容器引擎,源代码托管在 Github 上, 基于go语言并遵从Apache2.0协议开源。
Docker自2013年以来非常火热,无论是从 github 上的代码活跃度,还是Redhat在RHEL6.5中集成对Docker的支持, 就连 Google 的 Compute Engine 也支持 docker 在其之上运行。
Docker是一个命令行工具,它提供了中央“docker”执行过程中所需的所有工具。这使得Docker的操作非常简单。
Docker 架构
Docker 使用客户端-服务器 (C/S) 架构模式,使用远程API来管理和创建Docker容器。Docker 容器通过 Docker 镜像来创建。容器与镜像的关系类似于面向对象编程中的对象与类。
Docker | 面向对象 |
---|---|
容器 | 对象 |
镜像 | 类 |
Docker采用 C/S架构 Docker daemon 作为服务端接受来自客户的请求,并处理这些请求(创建、运行、分发容器)。 客户端和服务端既可以运行在一个机器上,也可通过 socket 或者RESTful API 来进行通信。
Docker daemon 一般在宿主机后台运行,等待接收来自客户端的消息。 Docker 客户端则为用户提供一系列可执行命令,用户用这些命令实现跟 Docker daemon 交互。
2.容器和虚拟机的区别
容器特点:容器共享宿主机的操作系统,容器的迁移非常方便,可以迁移到任何地方,只需要将容器拷贝到你的u盘,容器基本上是秒级部署,单台主机可以支持上千个容器
虚拟机原理:在宿主主机上运行虚拟机的操作系统,相当于再启动了一个主机,镜像比较大,而且操作系统会占用额外的cpu、内存、硬盘,底层必须要有qemu,就是内核的虚拟化、kvm的支持,可以做到完全隔离,而容器只能做到安全隔离
我们用的传统虚拟机如 VMware , VisualBox 之类的需要模拟整台机器包括硬件,每台虚拟机都需要有自己的操作系统,
虚拟机一旦被开启,预分配给它的资源将全部被占用。 每一台虚拟机包括应用,必要的二进制和库,以及一个完整的用户操作系统。
而容器技术是和我们的宿主机共享硬件资源及操作系统,可以实现资源的动态分配。
容器包含应用和其所有的依赖包,但是与其他容器共享内核。容器在宿主机操作系统中,在用户空间以分离的进程运行
容器技术是实现操作系统虚拟化的一种途径,可以让您在资源受到隔离的进程中运行应用程序及其依赖关系。
通过使用容器,我们可以轻松打包应用程序的代码、配置和依赖关系,将其变成容易使用的构建块,
从而实现环境一致性、运营效率、开发人员生产力和版本控制等诸多目标。
容器可以帮助保证应用程序快速、可靠、一致地部署,其间不受部署环境的影响
3.Docker的安装部署和简单应用(2048小游戏,ubuntu)
Docker的安装启动
(1)从物理机上获取安装docker时所需要的安装包
docker-ce-18.09.6-3.el7.x86_64.rpm
docker-ce-cli-18.09.6-3.el7.x86_64.rpm
containerd.io-1.2.5-3.1.el7.x86_64.rpm
container-selinux-2.21-1.el7.noarch.rpm
(2)启动 docker,设置为开机自启动
systemctl start docker
systemctl enable docker
管理docker常用的基础命令
docker load -i ubuntu.tar | 导入镜像 |
---|---|
docker run -it --name vm1 ubuntu | 创建容器(以ubuntu镜像为模板) |
docker ps | 查看容器状态 |
docker ps -a | 查看容器状态(包括不活跃的容器) |
docker attach vm1 | 连接容器 |
docker top vm1 | 查看容器进程 |
docker logs vm1 | 查看容器指令输出 -f 参数可以实时查看 |
docker inspect vm1 | 查看容器详情 |
docker stats vm1 | 查看容器资源使用率 |
docker diff vm1 | 查看容器修改 |
docker stop vm1 | 停止容器 |
docker start vm1 | 启动容器 |
docker kill vm1 | 强制结束容器进程 |
docker rm vm1 | 删除容器 |
docker rmi mirror | 删除镜像 |
删除镜像使用rmi,删除容器使用rm
Docker的应用
实例一:利用容器简单搭建一个2048小游戏
(1)从物理机上获取2048小游戏的镜像tar包到server1
(2)docker命令导入镜像game2048.tar
docker load -i game2048.tar
docker run -d --name game1 -p 80:80 game2048 后台运行容器 (-d后台运行,-p端口映射,宿主主机80端口和容器80端口)
docker ps 查看当前运行的容器
docker ps -a 查看所有的容器
docker images 查看系统存在的镜像
du -sh game2048.tar查看tar包大小
(3)在浏览器访问server1的ip,前提条件是server1的80端口没有被占用,这样就可以通过server1访问容器vm1的80端口
输入server1的ip:172.25.254.1,可以看到2048的游戏界面
docker info 查看 docker 信息
有警告WARNING
vim /etc/sysctl.d/docker.conf ##解决docker信息中出现的警告
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-ip6tables=1
sysctl --system ##使生效
实例二:利用docker创建一个交互式容器
(1)从物理机获取ubuntu的镜像到server1
导入镜像
(2)查看镜像大小并运行
docker run -it --name vm3 ubuntu ##创建一个vm1的容器,-it表示打开交互式docker界面(ubuntu自带,rhel镜像需要在后面再加bash)
uname -r 容器vm1的内核版本与宿主机的内核版本一致
在容器中建立文件file1、file2
(3)删除容器,重新运行容器,刚才建立的文件不存在了
容器类似于快照,可以随时还原
docker所有的数据都存放在/var/lib/docker目录中
4.镜像的构建
构建一个新的镜像有两种方式:docker commit 和 Dockerfile
1、docker commit(不推荐使用)
(1)获取一个镜像busybox.tar,linux系统基础的文件系统, 适合从零开始构建镜像
(2)docker load -I busybox.tar 导入busybox的镜像
docker run -it --name vm1 busybox 运行容器
vi testfile 编辑一个文件
快捷键:ctrl+p+q 退出容器但不关闭
docker ps
只显示了sh,看不到操作的镜像一般是不会用的,必须让其他人看到你做了什么操作,证明我们已经把这个文件封装到镜像里去了
docker attach vm1 重新连接到容器
(3)docker commit vm1 test:v1 提交更改过的容器
test比busybox多了一步sh
(4)重新运行更改后封装的容器
编辑的testfile文件还存在
2、Dockerfile
Dockerfile相当于ansible中的playbook,把你像构建镜像的操作写在文件里,根据这个文件构建镜像,这样可以看到新镜像构建的过程
mkdir docker ##新建目录docker来放dockerfile, docker 引擎会提交这个目录下的所有数据,该目录下不要放其他文件
vim Dockerfile
FROM busybox ##指定以busybox镜像为基础
RUN echo testfile > file1 ##操作
RUN echo testfile > file2
docker build -t test:v2 . ##构建镜像v2,表示提交当前目录中的内容
##第一步,基础容器;第二步,添加第一条命令后的容器;第三步,添加第二条命令后的容器
docker history test:v2 ##可以看到新建镜像过程的全部操作
再次更改Dockerfile,构建容器,如果有相同的镜像层,它会从缓存中读取,容器构建速度变快
docker rmi test:v1 ##删除镜像
5.Dockerfile中的一些用法
1、COPY和ADD用法
COPY可以把本机的文件拷贝到我们的容器中,方便部署
vim Dockerfile
FROM busybox
COPY testfile /tmp ##本机文件拷贝到容器中的/tmp目录
echo helloworld > testfile ##建立本地文件
docker build -t test:v1 . ##构建镜像
docker run -it --name vm1 test:v1 ##运行容器
ADD和COPY用法一样,它在拷贝文件的同时可以解压文件
获取一个nginx安装包,编辑Dockerfile
vim Dockerfile
FROM busybox
COPY testfile /tmp
ADD nginx-1.16.1.tar.gz /tmp ##拷贝并解压文件
docker build -t test:v2 . ##构建镜像
docker run -it --name vm1 test:v2 ##运行容器,可以看到文件已经被解压
2、VOLUME(数据卷)用法
vim Dockerfile
FROM busybox
COPY testfile /tmp
ADD nginx-1.16.1.tar.gz /tmp
VOLUME ["/data"] ##在容器启动时会自动新建这个目录/data
docker build -t test:v3 . ##构建镜像
docker run -it --name vm2 test:v3 ##运行容器
docker inspect vm2 ##查看vm2信息,这个容器所对应的数据存放在 “Source”后的目录中,这个目录对应的就是容器里面的/data目录
docker attach vm2 ##可以在/data看到在宿主机上新建的文件,在容器里也可以看到
容器中数据卷挂载到指定目录 -v
docker run -it --name vm2 -v /opt/data:/data test:v3 ##/opt/data是宿主机的目录(不存在时会自动新建),/data是容器对应的目录
在容器的/data目录中建立文件,指定的挂载目录下可以看到
3、WORKDIR用法
WORKDIR是在镜像中的工作目录,相当于cd
WORKDIR为 ADD、RUN、COPY 等命令设置在镜像中的工作目录,不存在会新建
CMD 和 ENTRYPOINT 都是用于设置容器启动后执行的命令,
但是 CMD 会被 docker run 后面的命令行覆盖,而 ENTRYPOINT 一定会被执行
docker run 后面的参数可以传递给 ENTRYPOINT 指令当作参数
Dockerfile 中只能指定一个 ENTRYPOINT,如果有多个,只有最后一个生效
Dockerfile的两种格式:shell、exec
(1)shell 格式
vim Dockerfile
FROM busybox
ENV name world ##定义变量 name,值为 world
ENTRYPOINT echo "hello,$name" ##使用变量
docker build -t test:v4 .
docker run --rm test:v4 ##–rm 表示这个容器在运行完之后把它删除(一次性容器)
(2)exec 格式
exec格式+ENTRYPOINT
vim Dockerfile
FROM busybox
ENV name world
ENTRYPOINT ["/bin/sh","-c","echo hello,$name"] ##变量被成功解析
docker build -t test:v6 .
docker run --rm test:v6
exec格式+CMD
vim Dockerfile
FROM busybox
ENTRYPOINT ["/bin/echo","hello"]
CMD ["world"]
docker build -t test:v7 .
docker run --rm test:v7 ##当 docker run命令行里没有指定命令时,CMD会执行,有的话CMD命令就会被覆盖
6.Dockerfile构建镜像的优化
镜像的优化:
选择最精简的基础镜像
减少镜像层数
清理镜像构建的中间产物
注意优化 网络请求
尽量用构建缓存
使用多阶段构建镜像
实验:
以rhel7镜像构建容器,并在容器中安装nginx的源码包
以此容器构建新的镜像并做优化
(1)获取rhel7镜像并导入
(2)编写Dockerfile
vim Dockerfile
FROM rhel7
EXPOSE 80 ##暴露的端口80
MAINTAINER wsp333@sina.com
COPY dvd.repo /etc/yum.repos.d/ ##dvd.repo需要在同级目录
RUN rpmdb --rebuilddb ##在yum之前重新构建 rpm 数据库
RUN yum install -y gcc make pcre-devel zlib-devel
ADD nginx-1.16.1.tar.gz /mnt
WORKDIR /mnt/nginx-1.16.1
RUN sed -i 's/CFLAGS="$CFLAGS -g"/#CFLAGS="$CFLAGS -g"/g' auto/cc/gcc ##关闭debug日志
RUN ./configure --prefix=/usr/local/nginx
RUN make
RUN make install
ENTRYPOINT ["/usr/local/nginx/sbin/nginx" , "-g" , "daemon off;"]
(3)第一次构建镜像,查看大小为303M
docker build -t nginx:v1 .
优化1
再次编辑Dockerfile,清理镜像构建的中间产物,在使用 yum 安装软件包时会产生缓存,在/var/cache/yum 中清理 yum 缓存和编译后的目录
FROM rhel7
EXPOSE 80 ##暴露的端口80
MAINTAINER wsp333@sina.com
COPY dvd.repo /etc/yum.repos.d/
RUN rpmdb --rebuilddb
RUN yum install -y gcc make pcre-devel zlib-devel && yum clean all ##清理yum缓存
ADD nginx-1.16.1.tar.gz /mnt
WORKDIR /mnt/nginx-1.16.1
RUN sed -i 's/CFLAGS="$CFLAGS -g"/#CFLAGS="$CFLAGS -g"/g' auto/cc/gcc
RUN ./configure --prefix=/usr/local/nginx
RUN make
RUN make install
RUN rm -fr /mnt/nginx-1.16.1 ##清理编译后的目录
ENTRYPOINT ["/usr/local/nginx/sbin/nginx" , "-g" , "daemon off;"]
docker build -t nginx:v2 .
第一次优化,查看大小为277M
优化2
将RUN都放在一行,减少镜像层数
FROM rhel7
EXPOSE 80
MAINTAINER wsp333@sina.com
COPY dvd.repo /etc/yum.repos.d/
ADD nginx-1.16.1.tar.gz /mnt
WORKDIR /mnt/nginx-1.16.1
RUN rpmdb --rebuilddb && yum install -y gcc make pcre-devel zlib-devel && yum clean all &&./configure--prefix=/usr/local/nginx&&make&&makeinstall&&rm-fr/mnt/nginx-1.16.1
ENTRYPOINT ["/usr/local/nginx/sbin/nginx" , "-g" , "daemonoff;"]
docker build -t nginx:v3 .
第二次优化,查看大小为258M
优化3
多阶段构建镜像
在构建 nginx 镜像时,最终只需要的是/usr/local/nginx 的二进制程序,其他过程都可以不要,那么我们可以把构建过程在一个镜像中完成,二进制程序放在一个新的镜像中,这样新的镜像就很小了
FROM rhel7 as build ##作为构建过程的镜像
EXPOSE 80
MAINTAINER wsp333@sina.com
COPY dvd.repo /etc/yum.repos.d/
ADD nginx-1.16.1.tar.gz /mnt
WORKDIR /mnt/nginx-1.16.1
RUN rpmdb --rebuilddb && yum install -y gcc make pcre-devel zlib-devel && yum clean all &&./configure--prefix=/usr/local/nginx&&make&&makeinstall&&rm-fr/mnt/nginx-1.16.1
FROM rhel7
EXPOSE 80
MAINTAINER wsp333@sina.com
VOLUME ["/usr/local/nginx/html"] ##使用容器时可以在宿主机对容器内容进行发布
COPY --from=build/usr/local/nginx/usr/local/nginx ##拷贝 nginx二进制程序目录
ENTRYPOINT ["/usr/local/nginx/sbin/nginx","-g","daemonoff;"]
docker build -t nginx:v4 .
第三次优化,查看大小为141M
优化4
使用更加精简的基础镜像
从底层优化
首先我们需要导入一个distroless和nginx镜像
distroless镜像只包含应用程序及其运行时依赖项,不包含程序包管理器、shell以及在标准Linux发行版中可以找到的任何其他程序
用distroless去除容器中所有不必要的东西
docker load -I nginx.tar ##导入 nginx 镜像
docker load -I distroless.tar ##导入 distroless 镜像
编写 Dockerfile
mkdir distroless
vim Dockerfile
FROM nginx as base
#https://en.wikipedia.org/wiki/List_of_tz_database_time_zones ARG
Asia/Shanghai #修改时区
RUN mkdir -p /opt/var/cache/nginx &&\
cp -a --parents /usr/lib/nginx /opt &&\
cp -a --parents/usr/share/nginx /opt &&\
cp -a --parents/var/log/nginx /opt &&\
cp -aL --parents/var/run /opt &&\
cp -a --parents/etc/nginx /opt &&\
cp -a --parents/etc/passwd/opt &&\
cp -a --parents/etc/group /opt &&\
cp -a --parents/usr/sbin/nginx /opt &&\
cp -a --parents/usr/sbin/nginx-debug /opt &&\
cp -a --parents/lib/x86_64-linux-gnu/libpcre.so.* /op t&&\
cp -a --parents/lib/x86_64-linux-gnu/libz.so.* /opt &&\
cp -a --parents/lib/x86_64-linux-gnu/libc.so.* /opt &&\
cp -a --parents/lib/x86_64-linux-gnu/libdl.so.* /opt &&\
cp -a --parents/lib/x86_64-linux-gnu/libpthread.so.* /opt &&\
cp -a --parents/lib/x86_64-linux-gnu/libcrypt.so.* /opt &&\
cp -a --parents/usr/lib/x86_64-linux-gnu/libssl.so.* /opt &&\
cp -a --parents/usr/lib/x86_64-linux-gnu/libcrypto.so.* /opt &&\
cp/usr/share/zoneinfo/${TIME_ZONE:-ROC} /opt/etc/localtime
FROM gcr.io/distroless/base
FROM gcr.io/distroless/base
COPY --from=base /opt /
EXPOSE 80 443
ENTRYPOINT ["nginx","-g","daemonoff;"]
docker build -t nginx:v5 .
docker run -d --name nginx nginx:v5
docker inspect nginx
第四次优化,查看大小为26.8M
能正常访问到Nginx默认发布页,证明容器镜像可以正常使用
查看IP
docker rm -f nginx
docker build -t nginx:v5 .
docker run -d -p 80:80 --name nginx ngin:5 ##做端口映射,在浏览器上可以进行访问
在浏览器上访问宿主主机ip,可以访问到容器中的内容