docker 的底层技术实现架构
docker 提供了打包运行 app 的平台,将 app 与底层基础设施隔离。
docker engine
docker engine 是核心,里面有后台进程 dockerd,提供了 REST API 接口,还提供了 CLI 接口,另外,docker 就是一种 C/S 的架构。
docker 的整体架构
底层技术支持
Namespaces:网络隔离
Control groups:资源限制
Union file systems:系统分层
docker image 概述
docker 镜像是文件和 meta data 的集合。
制作 baseImage
baseImage 指的是基于系统的基础镜像。
创建一个名为 Dockerfile 的文件:
1 | vi Dockerfile |
在文件中写入如下内容:
1 | FROM ubuntu |
编译镜像,注意最后面有一个点,指代的是当前路径:
1 | [root@node01 home]# ll |
查看编译后的镜像:
1 | [root@node01 home]# docker image ls |
除了我们创建的镜像,还出现了一个 Ubuntu 的镜像,是因为我们的镜像需要使用 Ubuntu。
运行镜像,成为一个容器:
1 | [root@node01 home]# docker run shuoliuchn/hello-docker |
顺利打印出 hello docker 来。
直接拉取官方镜像
除了我们自己通过 dockerfile 制作 docker 镜像外,我们还可以拉取人家已经做好了的 docker 镜像。
比如拉取 redis 的镜像:
1 | docker pull redis |
查看镜像:
1 | [root@node01 home]# docker image ls |
出现了 redis 的镜像。
container 的概念和使用
container 可以理解为运行时的实例,与 image 不同。
查看所有运行过的 container 容器,命令中 -a 的含义是运行中和未运行的容器都显示出来:
1 | [root@node01 home]# docker container ls -a |
可以交互运行容器,如果本地没有要运行的容器,默认会自动下载:
1 | [root@node01 home]# docker run -it centos |
主机名发生改变,是因为我们已经进入到 CentOS 容器的交互模式中。
不要退出交互模式,另起一个窗口:
此时,可以查到运行时的容器:
1 | [root@node01 ~]# docker container ls |
指定容器 id 删除某个容器,以下两种方式都可以实现:
1 | [root@node01 ~]# docker container rm f39ba8f54371 |
还可以删除所有容器,参数 q 的含义是只显示容器 id:
1 | [root@node01 ~]# docker rm $(docker container ls -aq) |
我们看到,这种方法是无法删除运行中的容器的。
如下指令可以删除没有在运行的容器:
1 | [root@node01 ~]# docker rm $(docker container ls -f "status=exited" -q) |
创建 Image 的两种方式
基于 image 创建 container 后,如果在 container 做了一些变化,例如安装了某个软件,可以将这些改变,commit 成一个新的 image,也可以简写为 docker commit。
交互运行一个 centos 实例,给里面装一个 lrzsz:
1 | [root@node01 home]# docker run -it centos |
装完之后退出 CentOS 容器:
1 | [root@a1ac5f329f5a /]# exit |
可以查看到修改后的容器:
1 | [root@node01 home]# docker container ls -a |
将这个新的容器,打包成一个新的镜像,其中 friendly_shockley 是容器的名字,shuoliuchn/centos-lrzsz 是给提交的镜像取的别名:
1 | [root@node01 home]# docker commit friendly_shockley shuoliuchn/centos-lrzsz |
查看镜像:
1 | [root@node01 home]# docker image ls |
还可以根据镜像 id 或镜像名,查看到镜像分层信息:
1 | [root@node01 home]# docker history centos |
除了通过容器提交生成镜像,我们还可以使用 Dockerfile,通过 build 制作 image,可以简写为 docker build。
定义 Dockerfile 文件:
1 | vi Dockerfile |
修改其中的内容为:
1 | FROM centos |
编译镜像
1 | docker build -t shuoliuchn/centos-lrzsz2 . |
可以查看到多出个镜像:
1 | [root@node01 home]# docker image ls |
Dockerfile 详解
FROM:从哪开始,一般都是从一个系统开始
1 | FROM scratch # 最小系统 |
LABEL:注释
1 | LABEL version=”1.0” |
RUN:执行命令,每 RUN 一次,会多一个系统分层,尽量少一些层。为了美观也可以使用 \ 将代码分成多行
1 | RUN yum -y update && install lrzsz \ |
WORKDIR:进入或创建目录,尽量不要用相对路径
1 | WORKDIR /root # 进入 /root 目录 |
ADD 和 COPY:将本地的文件,添加到 image 里,COPY 和 ADD区别是,ADD 会自动解压压缩包,而 COPY 不会解压
1 | ADD hello / # 将当前目录下hello,添加到容器的根下 |
ENV,相当于变量,增加 Dockerfile 的可读性,健壮性
CMD 和 ENTRYPOINT:执行命令或运行某个脚本,接下来详细讨论。
Dockerfile 的 CMD 和 ENTRYPOINT
Shell 和 Exec 格式:
1 | FROM centos |
ENTRYPOINT 与 CMD:容器启动时,运行什么命令
ENTRYPOINT 比 CMD 用得多,因为 CMD 有可能执行完前面的,把后面定义的 CMD 给忽略不执行了
分享 docker image
就像我们可以将代码提交到 GitHub 上一样,我们也可以将 docker 镜像共享到 docker 官方的 docker hub 上。当然,在分享之前,需要有 docker hub 的账号,这个和 docker 官网的账号是相同的。
使用 docker 账号登录 docker:
1 | [root@node01 home]# docker login |
上传镜像到 docker hub,要分享到 docker hub 上的 image 名字一定要以自己 docker hub 的用户名开头,镜像名后面可以用冒号指定版本号:
1 | docker image push shuoliuchn/centos-lrzsz2:latest |
就可以在 docker hub 上面看到刚刚上传的镜像:
若想从 docker hub 删镜像,可进入到镜像页面,通过 settings 删除:
分享 Dockerfile
分享 docker image 不如直接分享 Dockerfile。因为 Dockerfile 对每一个步骤都有详细描述,更加安全。而 docker image 中,很难说会不会有什么病毒之类的东西。
搭建私有 docker registry
github 是公开的,也可以创建自己的私有仓库。同样地,docker 官方给也提供了私有仓库的镜像。
准备第二台机器,作为私有仓库,主机名设置为 node02。
第二台运行如下指令,创建 docker 仓库:
1 | docker run -d -p 5000:5000 --restart always --name registry registry:2 |
查看进程,运行没问题:
1 | [root@node02 ~]# docker ps |
浏览器可以看到第二台机器仓库里,没东西:
测试端口,第一台可以安装个 telnet:
1 | yum -y install telnet |
测试连接:
1 | [root@node01 ~]# telnet 192.168.248.140 5000 |
连接正常,输入 ^
退出。
编写 dockerfile:
1 | vi Dockerfile |
简单制作一个镜像:
1 | FROM centos |
编译镜像,IP 是私有仓库(node02)的 IP:
1 | docker build -t 192.168.248.140:5000/centos . |
接下来,将仓库的 IP 设置为可信任的仓库。
编写配置文件:
1 | vi /etc/docker/daemon.json |
在 json 字典中加一行,这里的 IP 是私有仓库的 IP:
1 | "insecure-registries": ["192.168.248.140:5000"], |
文件中的内容最终是这样的:
1 | { |
重启 docker,应用配置:
1 | [root@node01 home]# service docker restart |
将镜像上传到私有仓库:
1 | docker push 192.168.248.140:5000/centos |
刷新页面,私有仓库有内容了
若从私有仓库下东西,如下:
1 | docker pull 192.168.248.140:5000/centos |
Dockerfile 案例
接下来,我们创建一个 Python 的 Web 应用,然后打包成 docker image 运行。
创建一个目录,在其中创建一个 py 文件并编辑:
1 | [root@node01 home]# mkdir flask-hello-world |
在 py 文件中,写入如下内容,这是一个简易的 Flask 框架服务:
1 | from flask import Flask |
在此目录下,创建 Dockerfile 并编辑:
1 | vi Dockerfile |
在其中写入如下内容:
1 | FROM python:3.6 |
构建镜像:
1 | docker build -t shuoliuchn/flask-hello-world . |
运行镜像:
1 | docker run shuoliuchn/flask-hello-world |
窗口会被 flask 占用,另起一个新窗口可以查看到进程:
1 | [root@node01 ~]# docker ps |
因为 flask 默认运行在 127.0.0.1,所以还是不能被外界访问到。
运行中对 container 操作
exec 命令用于调用并执行指令的命令
使用 -d 参数后台运行容器:
1 | docker run -d shuoliuchn/flask-hello-world |
可以交互运行里面的机器,根据运行时的 container ID
1 | [root@node01 flask-hello-world]# docker ps |
交互运行容器里面机器的 python shell:
1 | [root@node01 flask-hello-world]# docker exec -it 94525da805b4 python |
后台运行一个容器,并使用 --name
参数指定容器名字:
1 | [root@node01 flask-hello-world]# docker run -d --name=demo shuoliuchn/flask-hello-world |
容器的名字就成了 demo。
根据名字停止启动容器:
1 | [root@node01 flask-hello-world]# docker stop demo |
查看运行时的容器的运行日志:
1 | [root@node01 flask-hello-world]# docker logs demo |
查看运行时容器详细信息:
1 | docker inspect demo |
对容器资源限制
像虚拟机一样,我们也可以对容器可以使用的计算机资源进行一些限制,包括:
对内存的限制
对 CPU 的限制
可以指定开启容器占用的内存和 CPU,具体操作,可以查看帮助:
1 | docker run --help |