在没有 Docker 的时代, 我们会使用硬件虚拟化(虚拟机)以提供隔离.这里, 虚拟机通过在操作系统上建立了一个中间虚拟软件层 Hypervisor , 并利用物理机器的资源虚拟出多个虚拟硬件环境来共享宿主机的资源, 其中的应用运行在虚拟机内核上.但是, 虚拟机对硬件的利用率存在瓶颈, 因为虚拟机很难根据当前业务量动态调整其占用的硬件资源, 因此容器化技术得以流行.其中, Docker 是一个开源的应用容器引擎, 让开发者可以打包他们的应用以及依赖包到一个可移植的容器中, 然后发布到任何流行的 Linux 机器上.
Docker 容器不使用硬件虚拟化, 它的守护进程是宿主机上的一个进程, 换句话说, 应用直接运行在宿主机内核上.因为容器中运行的程序和计算机的操作系统之间没有额外的中间层, 没有资源被冗余软件的运行或虚拟硬件的模拟而浪费掉.
Centos 7 或更高稳定版本
yum install docker
有问题可参考官方安装文档
docker run -p 80 --name web -i -t centos /bin/bash
rpm -ivh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm
yum install -y nginx
nginx
执行 ctrl + P + Q
切换到后台.然后, 通过 docker ps -a
命令查看随机分配的端口
访问 http://127.0.0.1: + <分配的端口号>
正常情况会看到nginx的默认页面
首先, 我们输入 docker run -p 80 --name web -i -t centos /bin/bash 命令会运行交互式容器, 其中 -i 选项告诉 Docker 容器保持标准输入流对容器开放, 即使容器没有终端连接, 另一个 -t 选项告诉 Docker 为容器分配一个虚拟终端, 以便于我们接下来安装 Nginx 服务器.(Docker 还支持输入 -d 选项告诉 Docker 在后台运行容器的守护进程)
Docker 会为我们创建的每一个容器自动生成一个随机的名称.事实上, 这种方式虽然便捷, 但是可读性很差, 并且对我们后期维护的理解成本会比较大.因此, 我们通过 --name web 选项告诉 Docker 创建一个名称是 web 的容器.此外, 我们通过 -p 80 告诉 Docker 开放 80 端口, 那么, Nginx 才可以对外通过访问和服务.如果不指定宿主端口, 宿主机器会自动选择随机端口映射, 如上面例子.需要注意的是, 如果关闭或者重启, 这个端口就变了.
这里, 还有一个非常重要的知识点:Docker 通过 run 命令来启动一个新容器.Docker 首先在本机中寻找该镜像, 如果没有安装, Docker 在 Docker Hub 上查找该镜像并下载安装到本机, 最后Docker 创建一个新的容器并启动该程序.
但是, 当第二次执行 docker run 时, 因为 Docker 在本机中已经安装该镜像, 所以 Docker 会直接创建一个新的容器并启动该程序.
docker run 每次使用都会创建一个新的容器, 因此, 我们以后再次启动这个容器时, 只需要使用命令 docker start 即可.这里, docker start 的作用在用重新启动已存在的镜像, 而docker run 包含将镜像放入容器中 docker create , 然后将容器启动 docker start.
现在, 我们可以在上面的案例的基础上, 通过 exit 命令关闭 Docker 容器.当然, 如果我们运行的是后台的守护进程, 我们也可以通过 docker stop web 来停止.注意的是, docker stop 和 docker kill 略有不同, docker stop 发送 SIGTERM 信号, 而 docker kill 发送SIGKILL 信号.
Image 是一个可执行的文件系统, 包含容器运行所需的程序、库、资源、配置等文件外, 还包含了为运行时准备的一些配置参数(如匿名卷、环境变量、用户等).镜像不包含任何动态数据, 其内容在构建之后也不会被改变.
镜像构建时, 会一层层构建, 前一层是后一层的基础.每一层构建完就不会再发生改变, 后一层上的任何改变只发生在自己这一层.比如, 删除前一层文件的操作, 实际不是真的删除前一层的文件, 而是仅在当前层标记为该文件已删除.在最终容器运行的时候, 虽然不会看到这个文件, 但是实际上该文件会一直跟随镜像.因此, 在构建镜像的时候, 需要额外小心, 每一层尽量只包含该层需要添加的东西, 任何额外的东西应该在该层构建结束前清理掉.
镜像( Image )和容器( Container )的关系, 就像是面向对象程序设计中的类和实例一样, 镜像是静态的定义, 容器是镜像运行时的实体.容器可以被创建、启动、停止、删除、暂停等.
容器的实质是进程, 但与直接在宿主执行的进程不同, 容器进程运行于属于自己的独立的 命名空间.因此容器可以拥有自己的 root 文件系统、自己的网络配置、自己的进程空间, 甚至自己的用户 ID 空间.
按照 Docker 最佳实践的要求, 容器不应该向其存储层内写入任何数据, 容器存储层要保持无状态化.所有的文件写入操作, 都应该使用 数据卷(Volume)、或者绑定宿主目录, 在这些位置的读写会跳过容器存储层, 直接对宿主(或网络存储)发生读写, 其性能和稳定性更高.
数据卷的生存周期独立于容器, 容器消亡, 数据卷不会消亡.因此, 使用数据卷后, 容器删除或者重新运行之后, 数据却不会丢失.
镜像构建完成后, 可以很容易的在当前宿主机上运行, 但是, 如果需要在其它服务器上使用这个镜像, 我们就需要一个集中的存储、分发镜像的服务, Docker Registry 就是这样的服务.和GitHub非常类似.
docker pull [选项] [Docker Repository 地址[:端口号]/]仓库名[:标签]
docker run
例: docker run -it --rm \
ubuntu:16.04 \
bash
docker run --name redis -d \
#端口映射
-p 6379:6379 \
#设置环境变量
-e REDIS_PASSWORD=_**h \
#挂载数据卷
--mount source=redis,target=/data \
#挂载配置文件
-v /root/docker/conf/redis.conf:/usr/local/etc/redis/redis.conf \
#开启容器自启
--restart=always \
#指定配置文件 开启持久化 开启认证
redis:5.0 /bin/sh -c 'redis-server /usr/local/etc/redis/redis.conf --appendonly yes --requirepass ${REDIS_PASSWORD} '
docker image ls
docker image rm [选项] <镜像1> [<镜像2> ...]
Docker可以通过Dockerfile自动构建镜像. Dockerfile是一个包含构建镜像所需的命令集合的文本文件.
以下是mysql的Dockerfile示例
FROM oraclelinux:7-slim
ARG MYSQL_SERVER_PACKAGE=mysql-community-server-minimal-8.0.17
ARG MYSQL_SHELL_PACKAGE=mysql-shell-8.0.17
# Install server
RUN yum install -y https://repo.mysql.com/mysql-community-minimal-release-el7.rpm \
https://repo.mysql.com/mysql-community-release-el7.rpm \
&& yum-config-manager --enable mysql80-server-minimal \
&& yum install -y \
$MYSQL_SERVER_PACKAGE \
$MYSQL_SHELL_PACKAGE \
libpwquality \
&& yum clean all \
&& mkdir /docker-entrypoint-initdb.d
VOLUME /var/lib/mysql
COPY docker-entrypoint.sh /entrypoint.sh
COPY healthcheck.sh /healthcheck.sh
ENTRYPOINT ["/entrypoint.sh"]
HEALTHCHECK CMD /healthcheck.sh
EXPOSE 3306 33060
CMD ["mysqld"]
所谓定制镜像, 那一定是以一个镜像为基础, 在其上进行定制.就像我们之前运行了一个nginx 镜像的容器, 再进行修改一样, 基础镜像是必须指定的.而 FROM 就是指定基础镜像, 因此一个 Dockerfile 中 FROM 是必备的指令, 并且必须是第一条指令.
除了选择现有镜像为基础镜像外, Docker 还存在一个特殊的镜像, 名为 scratch .这个镜像是虚拟的概念, 并不实际存在, 它表示一个空白的镜像.
RUN 指令是用来执行命令行命令的.由于命令行的强大能力, RUN 指令在定制镜像时是最常用的指令之一.其格式有两种:
shell 格式: RUN <命令> , 就像直接在命令行中输入的命令一样.刚才写的 Dockerfile 中的 RUN 指令就是这种格式.
exec 格式: RUN [“可执行文件”, “参数1”, “参数2”] , 这更像是函数调用中的格式.
可以注意到上面mysql中一条RUN指令中用&&连接了多条执行命令,这是因为Dockerfile 中每一个指令都会建立一层镜像,如果分开写会产生多层镜像,这是完全没有意义的, 而且很多运行时不需要的东西, 都被装进了镜像里, 比如编译环境、更新的软件包等等.结果就是产生非常臃肿、非常多层的镜像, 不仅仅增加了构建部署的时间, 也很容易出错.
此外, 还可以看到这一组命令的最后添加了清理工作的命令, 删除了为了编译构建所需要的软件, 清理所有下载、展开的文件, 并且还清理了 apt 缓存文件.这是很重要的一步, 我们之前说过, 镜像是多层存储, 每一层的东西并不会在下一层被删除, 会一直跟随着镜像.因此镜像构建时, 一定要确保每一层只添加真正需要添加的东西, 任何无关的东西都应该清理掉.
docker build [选项] <上下文路径/URL/->
当构建的时候, 用户会指定构建镜像上下文的路径, dockerbuild 命令得知这个路径后, 会将路径下的所有内容打包, 然后上传给 Docker 引擎.这样Docker 引擎收到这个上下文包后, 展开就会获得构建镜像所需的一切文件.
如果在 Dockerfile 中这么写:
COPY ./package.json /app/
这并不是要复制执行 docker build 命令所在的目录下的 package.json , 也不是复制Dockerfile 所在目录的 package.json , 而是复制 上下文(context) 目录下的package.json .
启动容器有两种方式, 一种是基于镜像新建一个容器并启动(docker run), 另外一个是将在停止状态的容器重新启动(docker start).
docker run ubuntu:14.04 /bin/echo 'Hello world'
当利用 docker run 来创建容器时, Docker 在后台运行的标准操作包括:
docker attach <容器名/容器ID>
//如果从这个 stdin 中 exit, 会导致容器的停止.
docker exec -it <容器名/容器ID> bash
//如果从这个 stdin 中 exit, 不会导致容器的停止.
docker container stop
当 Docker 容器中指定的应用终结时, 容器也自动终止.
处于终止状态的容器, 可以通过 docker container start 命令来重新启动.
docker export //导出容器
docker import //导入容器
docker container rm //删除容器
docker pause //暂停容器
//<命令> + --help 可以查看详细用法
默认情况下,所以数据都在容器内部创建,这意味着移除容器的同时数据也会消失.
所以官方建议将数据和配置文件独立于容器管理,在容器中管理数据主要有两种方式:
在Linux上还可以使用tmpfs mount, Windows中还可以使用named pipe.
Volume将数据储存在宿主文件系统中一块由Docker管理的区域(/var/lib/docker/volumes/ on Linux).
其他进程不应该修改这部分数据. Volumes是Docker管理数据最合适的方式.
一个数据卷可以同时挂载到多个容器上, 容器关闭或被删除后挂载的数据卷依然存在.
docker volume create my-vol //创建一个名为my-vol的数据卷
docker volume ls //查看所有数据卷
docker volume inspect my-vol //查看my-vol详细信息
docker volume rm my-vol //删除my-vol
docker volume prune //删除没有被挂载到容器的数据卷
启动一个挂载数据卷的容器
docker run -d -P \
--name web \
# -v my-vol:/wepapp \
--mount source=my-vol,target=/webapp \
training/webapp \
python app.py
Bind mounts 可以将数据储存在宿主文件系统的任意地方, 其他进程也可以读写这里的数据. 依赖宿主文件目录系统.
在开发新Docker项目时建议使用Volume.
docker run -d -P \
--name web \
# -v /src/webapp:/opt/webapp \
--mount type=bind,source=/src/webapp,target=/opt/webapp \
training/webapp \
python app.py
//上面的命令加载主机的 /src/webapp 目录到容器的 /opt/webapp 目录.
在以前的版本中,-v用于独立容器, --mount用于swarm, 但从Docker 17.06开始–mount也可用于独立容器. --mount相比-v意义更明确,更冗长. 在版本支持mount的情况下建议使用 --mount.
容器中可以运行一些网络应用, 要让外部也可以访问这些应用, 可以通过 -P 或 -p 参数来
指定端口映射.
当使用 -P 标记时, Docker 会随机映射一个 49000~49900 的端口到内部容器开放的网络端
口.
-p 则可以指定要映射的端口, 并且, 在一个指定端口上只可以绑定一个容器.支持的格式
有 ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort .
-p 标记可以多次使用来绑定多个端口
docker port my-con //查看my-con容器的端口映射
如果你之前有 Docker 使用经验, 你可能已经习惯了使用 --link 参数来使容器互联.
随着 Docker 网络的完善, 强烈建议大家将容器加入自定义的 Docker 网络来连接多个容器,
而不是使用 --link 参数.
Docker有多种网络类型:bridge, host, overlay, macvlan, none.这里只介绍最常用的bridge类型.
docker network ls //查看所有网络
//默认会显示下面三个网络,其中bridge是默认的网络,启动容器时如果没有指定网络就使用的是这个.
b21ea58840e9 bridge bridge local
f903d3f8e256 host host local
da11f115a191 none null local
docker network create --driver bridge alpine-net //创建一个名为alpine-net的网络
docker run -dit --name alpine1 --network alpine-net alpine ash
docker run -dit --name alpine2 --network alpine-net alpine ash
docker run -dit --name alpine3 alpine ash
docker run -dit --name alpine4 --network alpine-net alpine ash
docker network connect bridge alpine4 //将alpine4加入bridge网络
docker network inspect bridge
//查看bridge 应该能看到alpine3和alpine4
docker network inspect alpine-net
//查看alpine-net 应该能看到alpine1, alpine2 和 alpine4
//可进入容器测试同一网络下的容器能否相互ping通,
https://docs.docker.com/get-started/
https://docs.docker.com/engine/reference/commandline/docker/
https://www.youtube.com/watch?v=YFl2mCHdv24
https://github.com/yeasy/docker_practice
https://juejin.im/post/5d026212f265da1b8608828b