简介
Docker
- 容器虚拟化技术,解决了运行环境和配置问题的软件容器。
一款产品从开发到上线,从操作系统,到运行环境,再到应用配置。作为开发+运维之间的协作,我们需要关心很多东西,这也是很多互联网公司都不得不面对的问题,特别是各种版本的迭代之后,不同版本环境的兼容,对运维人员都是考验。
环境配置如此麻烦,换一台机器,就要重来一次,费力费时。很多人想到,能不能从根本上解决问题,软件可以带环境安装?也就是说,安装的时候,把原始环境一模一样地复制过来。开发人员利用 Docker 可以消除协作编码时“在我的机器上可正常工作
”的问题。
Docker的基本组成
仓库
仓库(Repository)是集中存放镜像文件的场所。
- 横向对比:
- Maven仓库,存放各种jar包的地方;
- github仓库,存放各种项目源码的地方;
- Docker公司提供的官方registry被称为Docker Hub,存放各种镜像模板的地方。
仓库分为公开仓库(Public)和私有仓库(Private)两种形式。
最大的公开仓库是链接: Docker Hub,存放了数量庞大的镜像供用户下载。
国内的公开仓库包括阿里云、网易云等
镜像
UnionFS(联合文件系统)
Union文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下。
Union 文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录。类似千层饼哈哈哈哈哈。下面展示 mysql:8.0.30
镜像联合文件系统。
[root@localhost ~] docker inspect mysql:8.0.30
[
{
"Id": "sha256:43fcfca0776df8e192d1647da2866237fbd9f8e875fb496e4ca887369b2dd995",
"RepoTags": [
"mysql:8.0.30"
],
# 千层饼展示
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:05dc728e5e49b5db657ec403b875f757afdd8d31f624eea76d706d6eee6395b2",
"sha256:e413712982712f9b6795a558563749f8b9413af078f02290a8b132df58d93587",
"sha256:3efed8ece3f742cea267c6097b6ef1f5beb34d431da0404797b817bcd228e15b",
"sha256:5bd7288fc71b94c6b6a0fe48881a23c18924c59b419328b1e0516c289406c56d",
"sha256:089c3ce0a7bd0ec7d0146ee50cf1f6a0762a2bf37c6d52a1b391e1058fe6325a",
"sha256:548aa849c503d8ec7bb942c9cfb217499c1c68b5c974aa6daa6d6e9ab0ccef85",
"sha256:390f3d47b221eb0b0d435bd5deca0d228a2becc69e8b638755205bdde6ce9d8f",
"sha256:ae26f7a91c57484862161f9d36ea6a09c94252a32a7163ac9b8dfa7d3e1bda45",
"sha256:6a6f178837b4195dba9daf066f5bec032a75a2597a341972c961a4bb2ede9055",
"sha256:80411d0a9f6fb5bea4177979c8d04ff1b6a7a22d24cba2465cb2e61cc6916a64",
"sha256:1ee6afb508377ecb9d9a003fff84b132d023b0bd7b779092fdd5a1785a0b513b"
]
}
}
]
最大的一个好处就是 ——共享资源
- 有多个镜像都从相同的 base 镜像构建而来,那么宿主机只需在磁盘上保存一份base镜像,同时内存中也只需加载一份 base 镜像,就可以为所有容器服务了。而且镜像的每一层都可以被共享。
- ps:镜像都是
只读
的
容器
-
从面向对象角度
Docker 利用容器(Container)独立运行的一个或一组应用,应用程序或服务运行在容器里面,容器就类似于一个虚拟化的运行环境,容器是用镜像创建的运行实例。就像是Java中的类和实例对象一样,镜像是静态的定义,容器是镜像运行时的实体。容器为镜像提供了一个标准的和隔离的运行环境,它可以被启动、开始、停止、删除。每个容器都是相互隔离的、保证安全的平台 -
从镜像容器角度
可以把容器看做是一个简易版的 Linux 环境(包括root用户权限、进程空间、用户空间和网络空间等)和运行在其中的应用程序。
常用命令
常用命令列表,基础命令略过演示。
DOCKEER | ||
---|---|---|
systemctl status docker | 查看服务状态 | |
systemctl start docker | 启动服务 | |
docker version | 查看版本 | |
docker system df | 查看镜像/容器/数据卷所占的空间 | |
镜像命令 | ||
docker images | 列出本地主机上的镜像 | |
docker image ls -f dangling=true | 查看所有的虚悬镜像 | |
docker search xxx | 查找xxx镜像 | |
docker pull xxx | 拉取xxx镜像 | |
docker rmi 镜像id | 删除镜像 | |
docker rmi -f 镜像id | 强制删除镜像 | |
docker rmi -f ${docker images -qa} | 删除全部镜像 | |
docker image prune | 删词所有的虚悬镜像 -- docker rmi -f | |
容器命令 | ||
docker ps | -a | 查看正在运行+历史运行的容器 |
-l | 查看最近创建的容器(正在+ 历史运行) | |
-n 2 | 查看最近2个创建的容器(正在+ 历史运行) | |
-q | 查看所有正在运行的容器 id | |
--no-trunc | 不截断输出 | |
docker start 容器ID/容器名 | 启动某容器 | |
docker restart容器ID/容器名 | 重启某容器 | |
docker stop容器ID/容器名 | 停止容器 | |
docker kill容器ID/容器名 | 强制停止容器 | |
docker rm 容器ID | 删除容器 | |
docker rm -f 容器ID | 强制删除某容器 | |
docker top 容器ID | 查看容器内运行的进程,进程号、端口号等 | |
docker inspect 容器ID | 查看容器内部细节 | |
docker cp 容器ID:内路径 目的路径 | 容器宿主机间拷贝 | |
docker commit -m="描述" -a="作者" 容器ID 目标镜像名:[标签名] | 从容器生成一个镜像 | |
docker run | -d | 后台运行容器,并返回容器ID,也即启动守护式容器 |
-i | 以交互模式运行容器,通常与 -t 同时使用 | |
-P | 随机端口映射 | |
-p | 指定端口映射 | |
--name | 为容器指定一个名称 | |
docker save | 保存镜像为tar包 | |
网络命令 | ||
docker network ls | 查看网络列表 | |
docker network create xxx | 创建网络 | |
docker network inspect xxx | 查看网络源数据 | |
docker network rm xxx | 删除网络 | |
docker inspect 容器名 | tail -n 20 | 查看容器网络 |
容器数据卷
Docker将程序与运行的环境打包形成容器运行,运行可以伴随着容器,但是我们对数据的要求希望是持久化的,容器之间希望有可能共享数据。
Docker容器产生的数据,如果不通过docker commit生成新的镜像,使得数据做为镜像的一部分保存下来,那么当容器删除后,数据自然也就没有了。
卷就是目录或文件,存在于一个或多个容器中,由Docker挂载到容器,但不属于联合文件系统,因此能够绕过Union File System提供一些用于持续存储或共享数据的特性。
卷的设计目的就是数据的持久化,完全独立于容器的生存周期。
数据卷特点
- 数据卷可在容器之间共享或重用数据
- 卷中的更改可以直接生效
- 数据卷中的更改不会包含在镜像的更新中
- 数据卷的生命周期一直持续到没有容器使用它为止
容器数据卷
docker run -it --privileged=true
-v /宿主机绝对路径目录:/容器内目录
镜像
创建本地文件并挂载到容器中:
[root@localhost nginx]# echo 'test-nginx' > a.txt
[root@localhost nginx]# ls
a.txt
[root@localhost nginx]# docker run -p 19000:80 -v /root/dist/nginx:/etc/nginx/dist -d --name nginx-test nginx
38c20a18f8b5b2e52786957e92381ad7b290c531f878ee605872cac531f3aad0
[root@localhost nginx]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
38c20a18f8b5 nginx "/docker-entrypoint.…" 3 seconds ago Up 1 second 0.0.0.0:19000->80/tcp nginx-test
[root@localhost nginx]# docker exec -it nginx-test bash
root@38c20a18f8b5:/# cd etc/nginx/dist/
root@38c20a18f8b5:/etc/nginx/dist# cat a.txt
test-nginx
查看该容器信息,Mounts展示数据卷绑定信息:宿主机 /root/dist/nginx 挂载到容器内部 /etc/nginx/dist。
在容器中改变a.txt中的值,宿主机的值也同步改变。提供多种模式挂载,一般配置文件建议只读模式:or
。
卷的集成和共享
docker run -it --privileged=true
--volumes-from
父 --name xx 镜像
DockerFile
Dockerfile是用来构建Docker镜像的构建文件,是由一系列命令和参数构成的脚本。
以熟悉的某nginx tag为例:
- 每条保留字指令都必须为大写字母且后面要跟随至少一个参数
- 指令按照从上到下,顺序执行
#
表示注释- 每条指令都会创建一个新的镜像层,并对镜像进行提交
执行流程
- docker从基础镜像运行一个容器
- 执行一条指令并对容器作出修改
- 执行类似docker commit的操作提交一个新的镜像层
- docker再基于刚提交的镜像运行一个新容器
- 执行dockerfile中的下一条指令直到所有指令都执行完成
小总结
从应用软件的角度来看,Dockerfile、Docker镜像与Docker容器分别代表软件的三个不同阶段:
- Dockerfile是软件的原材料
- Docker镜像是软件的交付品
- Docker容器则可以认为是软件的运行态
Dockerfile面向开发,Docker镜像成为交付标准,Docker容器则涉及部署与运维,三者缺一不可,合力充当Docker体系的基石。
- Dockerfile,需要定义一个Dockerfile,Dockerfile定义了进程需要的一切东西。Dockerfile涉及的内容包括
执行代码或者是文件、环境变量、依赖包、运行时环境、动态链接库、操作系统的发行版、服务进程和内核进程
(当应用进程需要和系统服务和内核进程打交道,这时需要考虑如何设计namespace的权限控制)等等; - Docker镜像,在用Dockerfile定义一个文件之后,docker build时会产生一个Docker镜像,当运行 Docker镜像时会真正开始提供服务;
- Docker容器,容器是直接提供服务的。
保留字指令
指令 | 注解 |
---|---|
FROM | FROM 基础镜像,当前新镜像是基于哪个镜像的,指定一个已经存在的镜像作为模板,第一条必须是FROM |
MAINTAINER | 维护者的姓名和邮箱地址 |
RUN | 容器构建时需要运行的命令,RUN是在 docker build时运行。用 shell的命令即可 |
EXPOSE | 当前容器对外暴露出的端口 |
WORKDIR | 指定在创建容器后,终端默认登陆的目录,一个落脚点 |
USER | 指定该镜像以什么样的用户去执行,如果都不指定,默认是root |
ENV | 用来在构建镜像过程中设置环境变量 |
ADD | 将宿主机目录下的文件拷贝进镜像且会自动处理URL和解压tar压缩包 |
COPY | 类似ADD,拷贝文件和目录到镜像中。将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置,目标路径不用事先建好,路径不存在的话,会自动创建 |
VOLUME | 容器数据卷,用于数据保存和持久化工作 |
CMD | CMD 指定容器启动后的要干的事情,CMD是在 docker run时运行,命令和 RUN 一样。Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效,CMD 会被 docker run 之后的参数替换 |
ENTRYPOINT | 也是用来指定一个容器启动时要运行的命令,类似于 CMD 指令,但是ENTRYPOINT不会被docker run后面的命令覆盖,而且这些命令行参数会被当作参数送给 ENTRYPOINT 指令指定的程序 |
PS
: 当指定了ENTRYPOINT
后,CMD
的含义就发生了改变,不再是直接的运行命令,而是将CMD
的内容作为参数传给ENTRYPOINT
指令,换句话说,将变为:
<ENTRYPOINT> "<CMD>"
Docker 网络
Docker是如何内部访问的?
启动docker后,产生一个 docker0 的虚拟网桥。并自动创建三个网络模式(container模式不做介绍)
网络模式对比:
模式 | 说明 |
---|---|
bridge | 为每一个容器分配、设置 IP 等,并将容器连接到docker0虚拟网桥,默认为该模式。 |
host | 容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP 和端口。 |
none | 容器有独立的 Network namespace,但并没有对其进行任何网络设置。 |
bridge
Docker使用Linux桥接,在宿主机虚拟一个Docker容器网桥(docker0),Docker启动一个容器时会根据Docker网桥的网段分配给容器一个IP地址,称为Container-IP,同时Docker网桥是每个容器的默认网关。
每启动一个docker容器,Docker就会给容器分配一个ip,就会有一个网卡docker0桥接模式, 使用的技术是evth-pair技术。
网桥docker0创建一对对等虚拟设备接口一个叫veth,另一个叫eth0,成对匹配。
- 整个宿主机的网桥模式都是docker0,类似一个交换机有一堆接口,每个接口叫veth,在本地主机和容器内分别创建一个虚拟接口,并让他们彼此联通(这样一对接口叫veth pair);
- 每个容器实例内部也有一块网卡,每个接口叫eth0;
- docker0上面的每个veth匹配某个容器实例内部的eth0,两两配对,一一匹配。
此时在同一宿主机内的容器都接入同一个网桥,这样容器之间就能够通过容器的Container-IP直接通信。
host
直接使用宿主机的 IP 地址与外界进行通信,容器将不会获得一个独立的Network Namespace,而是和宿主机共用一个。将不会虚拟出自己的网卡而是使用宿主机的IP和端口。
docker run -d
--network host
此时通过宿主机ip直接访问
none
在none模式下,并不为Docker容器进行任何网络配置。
docker run -d
--network none
自定义网络
很方便,自身就维护好了主机名和ip的对应关系
如果容器之间有依赖关系,可以使用自定义网络,因为容器可能因重启等原因,其容器ip发生变化,使用自定义网络,可以直接指定域名,不需担心这种问题。
docker
network create
[OPTIONS] NETWORK
具体可以查看help
创建相关网络 (bridge)
创建相关容器接入自己创建的网络
进入nginx01 ping nginx02
在用户定义网络模式下,开发者可以使用任何docker支持的第三方网络driver来定制容器的网络。并且,docker 1.9 以上的版本默认自带了bridge和overlay两种类型的自定义网络driver。可以用于集成calico、weave、openvswitch等第三方厂商的网络实现。 除了docker自带的bridge driver,其他的几种driver都可以实现容器的跨主机通信
。而基于bdrige driver的网络,docker会自动为其创建iptables规则,保证与其他网络之间、与docker0之间的网络隔离。
Docker-compose 容器编排
Compose 是 Docker 公司推出的一个工具软件,可以管理多个 Docker 容器组成一个应用。你需要定义一个 YAML 格式的配置文件docker-compose.yml,写好多个容器之间的调用关系。只要一个命令,就能同时启动/关闭这些容器。
简单来说,要运行一个web项目,除了启动web服务容器之外,还需要启动它依赖的mysql、redis等等,这时候手动一个个启动非常麻烦,可使用docker-compose。
使用步骤:
- Dockerfile先行,定义好相关镜像。
- 编写docker-compose.yml文件,定义一个完整业务单元,安排好整体应用中的各个容器服务。
- 执行docker-compose up,启动并运行整个应用程序,完成一键部署上线。
compose 命令
常用指令
命令 | 注解 |
---|---|
docker-compose up -d | 启动所有docker-compose服务并后台运行 |
docker-compose down | 停止并删除容器、网络;但不会删除卷和镜像 |
docker-compose ps | 展示当前docker-compose编排过的运行的所有容器 |
docker-compose top | 展示当前docker-compose编排过的容器进程 |
docker-compose config -q | 检查配置,有问题才有输出 |
docker-compose logs xxx | 查看容器输出的日志 |
docker-compose xx /bin/bash | 进入容器实例内部 |
docker-compose restart | 重启服务 |
docker-compose start | 启动服务 |
docker-compose stop | 停止服务 |
docker-compose -h | 查看帮助 |
docker-compose pause/unpause | 暂停/恢复服务容器 |
compose 文件组成
-
version
docker compose的版本号,与docker的版本有对应关系:
-
image
指定运行容器的镜像名称或id,如果本地镜像不存在,将会尝试拉取这个镜像。 -
ports
容器与宿主机的端口映射信息,格式为宿主机端口:容器端口
,可以不指定宿主机端口,此时宿主机将会随机选择端口。PS:端口建议双引号
version: "3.2" services: mytomcat: image: nginx ports: - "16284:80"
-
volumes
数据卷映射宿主机绝对路径:容器绝对路径
数据卷名称:容器绝对路径
,这种方式必须在后面声明所使用的数据卷。
version: "3.2" services: mytomcat: image: nginx ports: - "16284:80" volumes: - nginx:/etc/nginx/dist # 数据卷映射,必须在下面定义数据卷名称 volumes: nginx: # 声明使用的数据卷,没有会自动创建,卷名为 文件夹名_自定义数据卷名 的格式
-
networks
配置容器使用哪个网络(桥)version: "3.2" services: mytomcat: image: nginx ports: - "16284:80" networks: - mynet # 指定网络,须在下面定义 networks: mynet: # 定义网络。定义后才能在上面使用,否则报错
启动容器后,compose会自动帮我们创建 mynet 网络
- 如果我们在模板文件中没有指定容器用的网络,那么compose会自动创建一个名称为
项目名_default
的网络,项目名为yml模板文件所在的目录名。 - 如果我们在模板文件中指定了容器的网络,那么compose会自动创建一个名称为
项目名_自定义网络名
的网络。
也可以使用创建好的网络:
version: "3.2" services: mytomcat: image: nginx ports: - "16284:8080" networks: - mynet # 指定容器网络 networks: mynet: # 定义网络 external: true # 该网络须存在,不存在必须先手动创建,否则报错
- 如果我们在模板文件中没有指定容器用的网络,那么compose会自动创建一个名称为
-
container_name
指定容器的名称。不指定时默认由compose生成带项目名称前缀的容器名version: "3.2" services: mytomcat: container_name: nginx01 # 指定容器的名称 image: nginx ports: - "16284:80"
-
environment
设置环境变量,可以使用数组或字典两种格式。
如果只给定变量名称,不指定值,会自动获取宿主机上对应的环境变量的值,防止数据泄露。环境变量的名称或值中用到true|false或者yes|no这样的布尔表达式,建议放到引号里,避免解析错误
version: "3.2" services: mysql5: container_name: mysql01 image: mysql:5.7 ports: - "13306:3306" environment: - MYSQL_ROOT_PASSWORD: root # 等价写法MYSQL_ROOT_PASSWORD=root
-
env_file
从文件中获取环境变量,可以为单独的文件路径或列表一般我们会把一些敏感信息环境变量放到文件中,比如mysql密码。这样compose模板文件中不会暴露密码
如果通过
docker-compose -f file
方式来指定yml模板文件,则env_file中变量的路径会基于模板文件路径如果有变量名称与 environment 指令冲突,则以后者为准
# 指定单个环境变量文件 env_file: .env # 指定多个环境变量文件 env_file: - ./common.env - /opt/secrets.env
环境变量文件中每一行必须符合格式,支持#开头的注释行
# mysql密码 MYSQL_ROOT_PASSWORD=root
-
command
用于run镜像之后,覆盖容器启动时的默认命令,比如启动redis时:docker run -p 6379:6379 -d --name redis01 -v redis.data:/data -v redis.conf:/etc/redis/redis.conf redis:5 redis-server /etc/redis/redis.conf,需要如下配置。version: "3.2" services: redis5: container_name: redis01 image: redis:5 ports: - "16379:6379" volumes: - redis.data:/data - redis.conf:/etc/redis command: "redis-server /etc/redis/redis.conf" # 容器启动时要运行的命令
-
depends_on
解决容器之间的依赖、启动先后问题。version: "3.2" services: web: image: webapp:latest ports: - "8080:8080" depends_on: # 依赖于redis5和mysql5.7,会先启动redis5和mysql5.7 - redis5 # 注意一定要写services下面配置的服务id,而不是容器名称 - mysql5.7 redis5: container_name: redis01 image: redis:5 ports: - "6379:6379" volumes: - redis.data:/data - /dockermapping/redis/conf/redis.conf:/etc/redis/redis.conf command: "redis-server /etc/redis/redis.conf" # 指定容器启动时要运行的命令 mysql5.7: container_name: mysql01 image: mysql:5.7 ports: - "3306:3306" environment: - MYSQL_ROOT_PASSWORD: root
-
build
如果要将我们自己的应用放到compose模板文件中进行编排,首先要用dockerfile生成镜像,然后在compose模板中编排,这样做稍显麻烦。build指令可以一步到位,在compose模板中将指定的dockerfile打包成镜像后再运行。
version: "3.2" services: web: build: context: myweb # 指定上下文目录,即dockerfile所在目录 dockerfile: Dockerfile # 指定dockerfile的文件名 ports: - "8080:8080" depends_on: # 先启动redis5和mysql5.7 - redis5 # 要配置的服务id,而不是容器名称 - mysql5.7 redis5: container_name: redis01 image: redis:5 ports: - "6379:6379" volumes: - redis.data:/data - redis.conf:/etc/redis command: "redis-server /etc/redis/redis.conf" # 指定容器启动时要运行的命令 mysql5.7: container_name: mysql01 image: mysql:5.7 ports: - "3306:3306" environment: - MYSQL_ROOT_PASSWORD: root
-
限制容器可用内存
version: "3.2" services: redis: image: redis:alpine container_name: redis01 deploy: resources: limits: memory: 2G # 限制容器可用最大内存为2G