1. 何为容器, 为何需要它
- 容器是对应用程序及其依赖关系的封装, 乍一看容器只是个轻量级的虚拟机,它和虚拟机一样拥有一个被隔离的操作系统实例,用来运行应用程序。虚拟机一样拥有一个被隔离的操作系统实例,用来运行应用程序。
- 容器的目的是使应用程序能够移植, 并把所有依赖关系包含进去
- 容器的优点:
- 容器能与主机的操作系统共享资源, 因而它的效率高出一个数量级.
- 容器具有可移植性, 能解决由于运行环境的些许改变导致的问题
- 容器是轻量的, 意味着开发者能同时运行多个容器, 并能模拟分布式系统在真实运行环境下的情况
- 对用户及开发者而言, 容器的优势不仅仅体现在云端部署
1.1 Docker与容器
- Docker 利用现有的Linux容器技术, 以不同方式将其封装及扩展--主要是通过提供可移植的镜像, 以及一个用户友好的接口, 来创建一套完整的容器创建及发布方案
- Docker 容器的两个部分:
- Docker 引擎: 负责创建与运行容器
- Docker Hub: 用来发布容器的云服务
1.2 Docker 基本概念
- 镜像(Image):Docker 镜像(Image),就相当于是一个 root 文件系统。比如官方镜像 ubuntu:16.04 就包含了完整的一套 Ubuntu16.04 最小系统的 root 文件系统。
- 容器(Container):镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。也可根据容器自定义镜像
- 仓库(Repository):仓库可看成一个代码控制中心(相当于github),用来保存镜像
1.3 镜像, 容器和联合文件系统
-
UnionFS(联合文件系统):Union文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。Union 文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
-
Docker 的镜像由多个不同的 "层" (layer) 组成, 每一个层都是一个只读的文件系统; Dockerfile 里的每个指令都会创建一个新的层, 而这个层将位于前一个层之上; 当一个镜像被转化成一个容器时(譬如通过 docker run 或 docker create 命令), Docker 引擎会在镜像之上添加一个处于最上层的可读写文件系统
-
容器几种状态:
- 已创建(created): 指容器已通过 docker create命名初始化, 但未曾启动
- 重启中(restarting): 当 Docker 引擎尝试重启一个启动失败的容器时, 才会出现
- 运行中(running): 正在运行中
- 迁移中(removing)
- 已暂停(paused)
- 已退出(exited): 也叫做 "已停止" 指容器中没有正在运行的进程
- 死亡(dead)
2. docker 安装
2.1 Linux安装 Docker
2.1.1 Ubuntu18.04 Docker安装链接
- 卸载旧版本(新系统也可以不卸载)
sudo apt-get remove docker docker-engine docker.io containerd runc
- 本地更新
sudo apt-get update
- 更换国内软件源,推荐中国科技大学的源,稳定速度快
sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak
sudo sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
sudo apt update
- 安装需要的包
sudo apt install apt-transport-https ca-certificates software-properties-common curl
- 添加 GPG 密钥,并添加 Docker-ce 软件源(中国科技大学)
curl -fsSL https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu \
$(lsb_release -cs) stable"
- 添加成功后更新软件包缓存
sudo apt update
- 安装 Docker-ce
sudo apt install docker-ce
- 设置开机自启动并启动 Docker-ce(安装成功后默认已设置并启动,可忽略)
sudo systemctl enable docker
sudo systemctl start docker
- 添加当前用户到 docker 用户组,可以不用 sudo 运行 docker(可选)
sudo groupadd docker
sudo usermod -aG docker $USER
- 测试添加用户组(可选)
docker run hello-world
docker version // 应正确输出 Server 和 Client 的版本信息
2.1.2 Ubuntu18.04 Docker安装 通过(shell)
// 1. sudo apt install crul 来安装 curl
sudo apt install crul
// 2. curl -fsSL https://get.docker.com -o get-docker.sh
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
// 3. 将当前用户添加到Docker组
sudo usermod -aG docker jefxff
2.1.3 CentOS7 Docker安装链接
- 卸载旧版本(如果新装的系统, 或者之前并没有docker, 可以不运行这一步)
sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine
- 使用 Docker 仓库进行安装
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
- 设置稳定的仓库
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
- 安装最新版本的 Docker Engine-Community 和 containerd
sudo yum install docker-ce docker-ce-cli containerd.io
- 启动 Docker
sudo systemctl start docker
- 测试是否安装
sudo docker run hello-world
- 配置镜像加速
// 查看 /etc/docker 下面是否有 daemon.json 文件,如果没有就新建
vim /etc/docker/deamon.json
{
"registry-mirrors": ["https://{自已的编码}.mirror.aliyuncs.com"]
}
// 配置完重启docker systemctl restart docker
2.2 Windows安装 docker
2.2.1 Windows安装虚拟机,通过2.1步骤安装docker
2.2.2 win10安装 docker 链接
- 在启用或关闭Windows功能中开启 Hyper-v
- 下载 Toolbox
- 登录下载之后, 安装(默认)并启动
- 镜像加速 Windows 10 的系统,在系统右下角托盘 Docker 图标内右键菜单选择 Settings,打开配置窗口后左侧导航菜单选择 Daemon。在 Registrymirrors 一栏中填写加速器地址 https://registry.docker-cn.com ,之后点击 Apply 保存后 Docker 就会重启并应用配置的镜像地址了。
3. Docker 基础
3.1 docker 相关命名
3.1.1 帮助命令
- docker version 查看docker版本信息
- docker info 查看docker详细信息
- docker --help 相当于Linux的man命令, 用来查看命令的详细信息
3.1.2 镜像命令
-
docker images [OPTIONS] 列出本地主机上的镜像(OPTIONS参数如下:)
- -a :列出本地所有的镜像(含中间映像层)
- -q :只显示镜像ID。
- --digests :显示镜像的摘要信息
- --no-trunc :显示完整的镜像信息
-
docker search [OPTIONS] 镜像名字 (OPTIONS参数如下:)
- --no-trunc : 显示完整的镜像描述
- -s : 列出收藏数不小于指定值的镜像。
- --automated : 只列出 automated build类型的镜像;
-
docker pull 镜像名字; 拉取某个镜像
-
docker rmi 镜像名字或ID ; 删除镜像
- docker rmi -f 镜像ID : 删除单个镜像
- docker rmi -f 镜像名1:TAG 镜像名2:TAG : 删除2个或多个镜像
- docker rmi -f $(docker images -qa : 删除全部镜像
3.1.3 容器命令
-
新建并启动容器: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
- --name="容器新名字": 为容器指定一个名称;
- -d: 后台运行容器,并返回容器ID,也即启动守护式容器;
- -i:以交互模式运行容器,通常与 -t 同时使用;
- -t:为容器重新分配一个伪输入终端,通常与 -i 同时使用;
- -P: 随机端口映射;
- -p: 指定端口映射,有以下四种格式
- ip:hostPort:containerPort
- ip::containerPort
- hostPort:containerPort
- containerPort
-
** 列出当前所有正在运行的容器 docker ps [OPTIONS]** (参数如下:)
- -a :列出当前所有正在运行的容器+历史上运行过的
- -l :显示最近创建的容器。
- -n:显示最近n个创建的容器。
- -q :静默模式,只显示容器编号。
- --no-trunc :不截断输出。
-
退出容器
- 容器停止并退出 exit
- 容器不停止但退出 Ctrl + P +Q
-
启动容器 docker start 容器ID或容器名
-
重启容器 docker restart 容器ID或容器名
-
停止容器 docker stop 容器ID或容器名
-
强制停止容器 docker kill 容器ID或者容器名
-
删除已停止的容器
- docker rm 容器ID或容器名 : 删除一个容器
- docker rm -f $(docker ps -a -q) : 删除多个容器
- docker rm -v $(docker ps -aq -f status=exited) :删除所有已停止的容器
-
启动守护式容器(后台启动) docker run -d 容器名
- 例如(后台启动一个centos): docker run -d --name centos01 centos
- Docker容器后台运行,就必须有一个前台进程.容器运行的命令如果不是那些一直挂起的命令(比如运行top,tail),就是会自动退出的
-
查看容器日志 docker logs -f -t --tail 数字 容器ID或容器名
- -t 是加入时间戳
- -f 跟随最新的日志打印
- --tail 数字 显示最后多少条
-
查看容器内运行的进程 docker top 容器ID或容器名
-
查看容器内部细节 docker inspect 容器ID或容器名
-
进入正在运行的容器并以命令行交互
- docker exec -t -i 容器ID或容器名 bashshell
- 例如: docker exec -i -t centos01 /bin/bash
-
从容器内拷贝文件到主机上 docker cp 容器ID:容器内路径 目的的主机路径
- 例如: docker cp centos01:/etc/yum.conf /home/jefxff/Dexktop/yum.txt
3.2 Docker 容器使用
3.2.1 Dokcer 拉取并运行Ubuntu
- 获取需要的镜像
docker pull ubuntu
- 启动容器
docker run -it ubuntu /bin/bash
- 退出容器: exit
- 启动已停止的容器
// 查看所有容器
docker ps -a
// 通过容器ID启动一个已停止的容器
docker start "容器ID"
- 后台运行
docker run -d --name ubuntu_test ubuntu /bin/bash
- 停止容器
docker stop "容器ID"
- 重启容器
docker restart "容器ID"
- 进入后台启动的容器
docker exec // 退出容器终端,不会导致容器的停止
// docker exec -it 243c32535da7 /bin/bash
- 使用 docker export 命令, 导出本地某个容器快照
docker export "容器ID" > name.tar
// docker export 1e560fca3906 > ubuntu.tar
- 导入容器快照
cat docker/ubuntu.tar | docker import - test/ubuntu:v1
- 删除容器
docker rm -f "容器ID"
// docker rm -f 1e560fca3906
3.2.2 Docker 运行一个web应用
- 载入镜像
docker pull training/webapp
- 在docker容器中运行一个 Python Flask 应用来运行一个web应用
// -d:让容器在后台运行
// -P:将容器内部使用的网络端口随机映射到我们使用的主机上
docker run -d -P training/webapp python3 hello.py
- 通过 -p 参数来设置不一样的端口
docker run -d -p 5000:5000 training/webapp python3 hello.py
- 通过 docker port '容器ID' 来查看某个容器的某个确定端口映射到宿主机的端口号
docker port "容器ID"
- 通过 docker logs '容器ID' 来查看容器内部的标准输出
docker logs "容器ID"
- 通过 docker top 来查看容器内部运行的进程
docker top "容器ID"
// docker top wizardly_chandrasekhar
- 通过 docker inspect 来查看 Docker 的底层信息(返回一个 JSON 文件记录着 Docker 容器的配置和状态信息)
docker inspect "容器ID"
- 停止 WEB 应用容器 docker stop '容器ID'
- 重启 WEB 应用容器 docker start '容器ID'
- 移除 WEB 应用容器 docker rm '容器ID'
- 通过 --name 参数给容器命名
docker run -d -P --name jefftest raining/webapp python3 hello.py
3.2.3 Docker 容器端口映射
- 通过 -P 或 -p 参数来指定端口映射, 让外部也可以访问这些应用
- -p(小写) : 是容器内部端口绑定到指定的主机端口
- -P(大写) : 是容器内部端口随机映射到主机的高端口
// -p : 是容器内部端口绑定到指定的主机端口
// -P : 是容器内部端口随机映射到主机的高端口
docker run -d -P training/webapp python3 hello.py
// 指定容器绑定的网络地址,比如绑定 127.0.0.1
docker run -d -p 127.0.0.1:5001:5000 training/webapp python3 hello.py
// 指定容器绑定的网络地址,比如绑定 127.0.0.1, 以及绑定UDP
docker run -d -p 127.0.0.1:5001:5000/udp training/webapp python3 hello.py
// docker port '容器ID' 端口号 快速绑定指定容器的端口
docker port "容器ID" "端口号"
3.2.4 Docker 容器互联
-
端口映射并不是唯一把 docker 连接到另一个容器的方法; docker 有一个连接系统允许将多个容器连接在一起,共享连接信息; docker 连接会创建一个父子关系,其中父容器可以看到子容器的信息
-
新建一个 docker 网络
// -d : 参数指定 Docker 网络类型,有 bridge、overlay (overlay 网络类型用于 Swarm mode)
docker network create -d bridge test-net
- 运行一个容器并连接到新建的 test-net 网络
docker run -itd --name test1 --network test-net ubuntu /bin/bash
- 打开新的终端,再运行一个容器并加入到 test-net 网络
docker run -itd --name test2 --network test-net ubuntu /bin/bash
- 通过ping命令来测试
3.3 Docker 镜像使用
- 使用 docker images 命令来列出本地主机上的所有镜像
- 如果本地仓库中有多个镜像, 可以指定版本来运行镜像
// 运行 ununtu15.10
docker run -it ubuntu:15.10 /bin/bash
// 运行 ubuntu15.04
docker run -it ubutu:14.04 /bin/bash
- 拉取一个新镜像
docker pull ubuntu:13.10
- 查找镜像
docker search httpd
- 删除镜像
docker rmi "镜像ID"
3.3.1 更新镜像(已有镜像升级)
- 更新镜像之前,我们需要使用镜像来创建一个容器
docker run -t -i ubuntu:15.10 /bin/bash
- 在运行的容器内使用 apt-get update 命令进行更新, 在完成操作之后,输入 exit 命令来退出这个容器
- 更新后使用 docker commit 来提交容器副本
// -m: 提交的描述信息
// -a: 指定镜像作者
// e218edb10161:容器 ID
// jeff/ubuntu:v2: 指定要创建的目标镜像名
docker commit -m="has update" -a="jeff" e218edb10161 jeff/ubuntu:v2
- 启动更新后的容器
docker run -t -i jeff/ubuntu:v2 /bin/bash
- 设置镜像标签
docker tag 860c279d2fec jeff/ubuntu:dev
3.4 配置 DNS
- 在宿主机的 /etc/docker/daemon.json 文件中增加以下内容来设置全部容器的 DNS
{
"dns" : [
"114.114.114.114",
"8.8.8.8"
]
}
- 重启 docker 生效
/etc/init.d/docker restart
// 或者
sudo systemctl restart docker
- 查看容器的 DNS 是否生效
docker run -it --rm ubuntu cat etc/resolv.conf
- 指定的容器设置 DNS
docker run -it --rm host_ubuntu --dns=114.114.114.114 --dns-search=test.com ubuntu
4. Docker Hub(了解)
- 命令行登录 docker login
- 命令行退出 docker logout
- Docker Hub 就相当于 Github 是保存镜像的地方, 可以从 dockerHub 拉取需要的镜像, 也可以将自己认为newBee 的镜像推送到 dockerhub 上, 供别人拉取使用
5. Dockerfile 学习
5.1 Dockerfile是啥?
-
Dockerfile是用来构建Docker镜像的构建文件,也是一个文本文件,是由一系列命令和参数构成的脚本, 记述了Docker构建一个镜像所需要的过程,包括安装软件包、创建文件夹、定义环境变量以及其他一些操作(不同于shell脚本,他的一个命令会生成一个容器,修改提交生成镜像,再删除这个临时的容器) Dockerfile 的指令每执行一次都会在 docker 上新建一层;
-
由于 docker 的运行模式是 C/S。我们本机是 C,docker 引擎是 S。实际的构建过程是在 docker 引擎下完成的,所以这个时候无法用到我们本机的文件。这就需要把我们本机的指定目录下的文件一起打包提供给 docker 引擎使用。如果未说明最后一个参数,那么默认上下文路径就是 Dockerfile 所在的位置。
-
构建步骤: 1.1 编写Dockerfile文件 1.2 docker build 1.3 docker run
5.2 Dockerfile 构建过程
- Dockerfile内容
- 每条保留字指令都必须为大写字母且后面要跟随至少一个参数
- 指令按照从上到下,顺序执行
- #表示注释
- 每条指令都会创建一个新的镜像层,并对镜像进行提交
- Docker执行Dockerfile 的大致流程
- docker从基础镜像运行一个容器
- 执行一条指令并对容器作出修改
- 执行类似docker commit的操作提交一个新的镜像层(另一种说法是对容器执行 docker commit 操作 生成新的镜像层,删除这个容器)
- docker再基于刚提交的镜像运行一个新容器
- 执行dockerfile中的下一条指令直到所有指令都执行完成
- 总结
- Dockerfile,需要定义一个Dockerfile,Dockerfile定义了进程需要的一切东西。Dockerfile涉及的内容包括执行代码或者是文件、环境变量、依赖包、运行时环境、动态链接库、操作系统的发行版、服务进程和内核进程(当应用进程需要和系统服务和内核进程打交道,这时需要考虑如何设计namespace的权限控制)等等;
- Docker镜像,在用Dockerfile定义一个文件之后,docker build时会产生一个Docker镜像,当运行 Docker镜像时,会真正开始提供服务;
- Docker容器,容器是直接提供服务的
5.3 Dockerfile 体系结构(保留字指令)
- FROM 基础镜像,当前新镜像是基于哪个镜像的(最基础的镜像是 scratch)
- MAINTAINER 镜像维护者的姓名和邮箱地址
- RUN 容器构建时需要运行的命令
- EXPOSE 当前容器对外暴露出的端口
- WORKDIR 指定在创建容器后,终端默认登陆的进来工作目录,一个落脚点;(如果不指定, 默认是在根目录下"/")
- ENV 用来在构建镜像过程中设置环境变量
- ENV MY_PATH /usr/mytest
- 这个环境变量可以在后续的任何RUN指令中通过$(MY-PATH)来引用使用,这就如同在命令前面指定了环境变量前缀一样;
- 也可以在其它指令中直接使用这些环境变量, 比如:WORKDIR $MY_PATH
- ADD 将宿主机目录下的文件拷贝进镜像且ADD命令会自动处理URL和解压tar压缩包
- COPY 类似ADD,拷贝文件和目录到镜像中。将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置 COPY src dest COPY ["src", "dest"]
- VOLUME 容器数据卷,用于数据保存和持久化工作
- CMD 指定一个容器启动时要运行的命令 Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效,CMD 会被 docker run 之后的参数替换
- ENTRYPOINT 指定一个容器启动时要运行的命令 ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数
- ONBUILD 当构建一个被继承的Dockerfile时运行命令,父镜像在被子继承后父镜像的onbuild被触发
5.4 使用 Dockerfile 定制镜像 nginx
- 定制一个 nginx 镜像(构建好的镜像内会有一个 /usr/share/nginx/html/index.html 文件)
// 在一个空目录下,新建一个名为 Dockerfile 文件,并在文件内添加以下内容
FROM nginx // 以最新的 nginx 作为基础镜像
RUN echo "这是一个本地构建的nginx镜像" > /usr/share/nginx/html/index.html
- 参数说明
- FROM:定制的镜像都是基于 FROM 的镜像,这里的 nginx 就是定制需要的基础镜像。后续的操作都是基于 nginx
- RUN:用于执行后面跟着的命令行命令; 有 shell格式和exec格式
- shell格式和exec格式
// shell
RUN <"命令行命令">
// <命令行命令> 等同于,在终端操作的 shell 命令
// exec
RUN ["可执行文件", "参数1", "参数2"]
// 例如:
// RUN ["./test.php", "dev", "offline"] 等价于 RUN ./test.php dev offline
- 开始构建镜像
docker build -t nginx:v3 .
5.5 使用 Dockerfile 定制镜像 mycentos
- 创建 Dockerfile 文件
cd /home/jeff/DockerTest
mkdir mydockrefile
vim Dockerfile
// 文件名是 Dockerfile
FROM centos // 以最新的 centos 作为基础镜像
MAINTAINER jefxff<jefxff@outlook.com> // 镜像维护者的姓名和邮箱地址
ENV MYPATH /usr/local // 指定构建时环境变量为 /usr/local
WORKDIR $MYPATH // 工作路径就是 /usr/local
RUN yum -y install vim // 容器构建时需要运行的命令
RUN yum -y install net-tools // 容器构建时需要运行的命令
EXPOSE 80 // 对外暴露的端口
CMD echo $MYPATH
CMD echo "success--------------ok"
CMD /bin/bash
- 在同级目录下使用 docker build -t 新镜像名:TAG . 构建镜像
docker build -t mycentos:1.3 .
- 运行 docker run -it 新镜像名:TAG
docker run -it mycentos:1.3
- 通过 docker history 新镜像名字 查看镜像的变更历史
5.6 使用 Dockerfile 定制镜像 查询IP信息的容器
- 创建 Dockerfile 文件 Dockerfileip
// 文件名是 Dockerfileip
FROM centos
RUN yum install -y curl
CMD [ "curl", "-s", "http://ip.cn" ]
- 构建镜像
// -f 参数 指定 构建的 Dockerfile 文件名, 也可以是绝对路径
docker build -f Dockerfileip -t myip .
- 运行
docker run
6. docker 容器数据卷
6.1 docker 数据卷是什么?
- 卷就是目录或文件,存在于一个或多个容器中,由docker挂载到容器,但不属于联合文件系统,因此能够绕过Union File System提供一些用于持续存储或共享数据的特性
- 通过 docker run 运行的容器, 在容器运行期间产生的有用数据, 随着容器的停止或删除就会消失; 容器数据卷的目的就是将这些需要的数据进行持久化保存
- 各个容器之间是隔离的, 但是通过容器数据卷就可以是实现容器之间的数据共享
- 特点:
- 数据卷可在容器之间共享或重用数据
- 卷中的更改可以直接生效
- 数据卷中的更改不会包含在镜像的更新中
- 数据卷的生命周期一直持续到没有容器使用它为止
6.2 添加数据卷的两种方式
- 通过命令 -v 添加(实现宿主机和容器内的数据共享)
// docker run -it -v 宿主机绝对路径:容器内目录路径 centos
docker run -it --name myos -v /mydataVolume:/dataVolumeContainer centos
- 通过 Dockerfile 文件中的 VOLUME 来指定容器内的共享目录
- 通过编写Dockerfile时, 通过 VOLUME 来指定挂载的目录, 可挂载一个, 也可挂载多个
- 执行 docker build 创建镜像
- 执行 docker run 运行刚创建的镜像
- 注意:创建的 docker 容器中, 如果在 挂载目录中写入数据的话, 在宿主机中的 /var/lib/docker/volumes/"容器64位ID"/_data 目录中可以找到对应的共享数据
// 1. 编写的 Dockerfile 文件
// 自定义一个 centos 镜像, 工作目录是 /jefxff/workspace,挂在的数据卷目录是 /jefxff/volumedata
// 记住 Dockersfile 中的注释用的是 #
FROM centos // 这个Dockerfile 构建镜像的基础用的是 centos 即在本地找, 本地如果没有就去阿里云拉取
MAINTAINER jefxff <jefxff@outlook.com> // 表明作者和邮箱
ENV MYPATH /jefxff/workspace // 环境
WORKDIR $MYPATH
RUN yum -y install vim
RUN yum -y install net-tools
VOLUME ["/jefxff/volumedata1","/jefxff/volumedata2"] // 指定挂载的目录
CMD echo "==================== finished! ===================="
CMD /bin/bash
//
// 2. 执行: docker build -f dockerfile -t mycentos .
//
// 3. 运行: docker run -it --name myos01 mycentos
6.3 docker 数据卷容器
- 上面是创建数据卷, 实现宿主机和docker容器之间的数据共享, 没有实现容器之间的数据共享, 数据卷容器目的就是实现docker容器之间的数据共享
- 数据容器卷就是专门创建一个容器作为挂载数据卷的容器, 称之为数据卷容器也叫做父容器, 其他需要共享的容器通过 --volumes-from "父容器" 来挂载到这个数据卷容器, 实现数据共享
- 容器之间配置信息的传递,数据卷的生命周期一直持续到没有容器使用它为止(就是说, 即使删除了父容器, 剩下容器之间也是可以实现数据共享的)
- 后继承的容器会自动的复制先前同容器卷共享的数据
- 示例:
// 1. 创建 dc01 作为其他容器继承的父容器
docker run -it --name dc01 mycentos
//
//2. 创建 dc02 继承于 dc01
docker run -it --name dc02 --volumes-from dc01 mycentos
//
//3. 创建 dc03 继承与 dc01
docker run -it --name dc03 --volumes-from dc01 mycentos
//
//4. 创建 dc04 继承与 dc03
docker run -it --name dc04 --volumes-from dc03 mycentos
7. Docker Compose
7.1 Docker Comppose 基础
-
Compose 是用于定义和运行多容器 Docker 应用程序的工具。通过 Compose,可以使用 YML 文件来配置应用程序需要的所有服务。然后,使用一个命令,就可以从 YML 文件配置中创建并启动所有服务
-
Compose 使用的三个步骤
-
① 使用 Dockerfile 定义应用程序的环境。
-
② 使用 docker-compose.yml 定义构成应用程序的服务,这样它们可以在隔离环境中一起运行。
-
③ 最后,执行 docker-compose up 命令来启动并运行整个应用程序。
-
-
Compose 工作流程
-
一个普通的工作流程以docker-compose up -d命令启动应用程序开始。docker-compose logs和ps命令可以用来验证应用程序的状态,还能帮助调试
-
修改代码后,先执行docker-compose build构建新的镜像,然后执行docker-composeup -d取代运行中的容器。注意,Compose会保留原来容器中所有旧的数据卷,这意味着即使容器更新后,数据库和缓存也依旧在容器内(这很可能会造成混淆,因此要特别小心)。如果你修改了Compose的YAML文件,但不需要构建新镜像,可以通过up-d参数使Compose以新的配置替换容器。如果想要强制停止Compose并重新创建所有容器,可以使用--force-recreate选项来达到目的。
-
你不再需要使用该应用时,可以执行docker-compose stop来停止应用程序。假设代码没有变更,可以通过docker-compose start或up来重启相同的容器。使用docker-compose rm彻底把容器删除。
-
-
Compose 命令
-
docker-compose up : 启动所有在Compose文件中定义的容器,并且把它们的日志信息汇集一起。通常会使用-d参数使Compose在后台运行
-
docker-compose build : 重新建造由Dockerfile构建的镜像。除非镜像不存在,否则up命令不会执行构建的动作,因此需要更新镜像时便使用这个命令
-
docker-compose ps : 获取由Compose管理的容器的状态信息
-
docker-compose run : 启动一个容器,并运行一个一次性的命令。被连接的容器会同时启动,除非用了--no-deps参数
-
docker-compose logs : 汇集由Compose管理的容器的日志,并以彩色输出
-
docker-compose stop : 停止容器,但不会删除它们
-
docker-compose rm : 删除已停止的容器。不要忘记使用-v参数来删除任何由Docker管理的数据卷。
-
7.1 Compose 安装
- Linux 安装
// 下载稳定版本
sudo curl -L "https://github.com/docker/compose/releases/download/1.24.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
// 将可执行权限应用于二进制文件
sudo chmod +x /usr/local/bin/docker-compose
// 创建软链
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
// 测试是否安装成功
docker-compose --version
- Windows 的 Docker 桌面版和 Docker Toolbox 已经包括 Compose 和其他 Docker 应用程序,因此 Windows 用户不需要单独安装 Compose
8. Docker应用(Dockerfile + Compose)
8.1 python redis web 服务
- 创建一个测试目录: mkdir composetest cd composrtest
- 创建 app.py 复制下面内容
import time
import redis
from flask import Flask
app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)
def get_hit_count():
retries = 5
while True:
try:
return cache.incr('hits')
except redis.exceptions.ConnectionError as exc:
if retries == 0:
raise exc
retries -= 1
time.sleep(0.5)
@app.route('/')
def hello():
count = get_hit_count()
return 'Hello World! I have been seen {} times.\n'.format(count)
- 创建 requirements.txt 文件在 composetest 目录中,内容如下:
flask
redis
- 创建 DOckerfile 文件在同目录
FROM python:3.7-alpine // 从 Python 3.7 映像开始构建镜像
WORKDIR /code // 将工作目录设置为 /code
ENV FLASK_APP app.py
ENV FLASK_RUN_HOST 0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers // 安装 gcc,以便诸如 MarkupSafe 和 SQLAlchemy 之类的 Python 包可以编译加速
COPY requirements.txt requirements.txt // 复制 requirements.txt 并安装 Python 依赖项
RUN pip install -r requirements.txt // 复制 requirements.txt 并安装 Python 依赖项
COPY . . // 将 . 项目中的当前目录复制到 . 镜像中的工作目录
CMD ["flask", "run"] // 容器提供默认的执行命令为:flask run
- 创建 dockre-compose.yml
# yaml 配置
version: '3'
services:
web: # web:该 web 服务使用从 Dockerfile 当前目录中构建的镜像。然后,它将容器和主机绑定到暴露的端口 5000。此示例服务使用 Flask Web 服务器的默认端口 5000
build: .
ports:
- "5000:5000"
redis: # redis:该 redis 服务使用 Docker Hub 的公共 Redis 映像
image: "redis:alpine"
- 使用 cpmpose 命令构建和运行应用
#启动应用
docker-compose up
#启动后台应用
docker-compose up -d
8.2 docker 容器运行flaskWeb项目hello world
8.2.1 直接在容器中运行 python 代码
- 在/home/jefxff/ 目录下创建一个 identidock 的目录; 在 identidock 下再创建一个 app 的目录; 在 app目录中创建 identidock.py 文件, 其内容如下:
from flask import Flask
app = Flask(__name__)
#
@app.route('/')
def hello_world():
return "Hello woeld!\n"
if __name__ == "__main__":
app.run(debuge=True, host='0.0.0.0')
- 再切换到 identidock 目录下创建 Dockerfile 文件, 其内容如下:
FROM python:3.4
RUN pip install --upgrade pip
RUN pip install Flask==0.10.1
WORKDIR /app
COPY app /app
CMD ["python", "identidock.py"] # 通过 python identidock.py 命令来运行项目
- 通过 Dockerfile 来构建镜像
docker build -t identidock .
- 绑定挂载并运行测试
// 启动并挂在app 到docker 下的 /app
docker run -p 5000:5000 -v "$PWD"/app:/app identidock
curl localhost:5000
// out: hello woeld!
- 修改代码再进行测试
// vim identidock.py
// 替换输出语句
// 重启docker 容器
curl localhost:5000
8.2.2 通过 uWSGI 来运行代码
- 修改 Dockerfile 文件, 其内容如下:
FROM python:3.4
RUN pip install --upgrade pip
RUN pip install Flask==0.10.1 uWSGI # 添加python的 uWSGI 包
WORKDIR /app
COPY app /app
# CMD ["python", "identidock.py"] # 通过 python identidock.py 命令来运行项目
CMD ["uwsgi", "--http", "0.0.0.0:9090", "--wsgi-file", "/app/identidock.py", "--callable", "app", "--stats", "0.0.0.0:9191"] # 添加 通过 uwsgi 来运行项目
- 构建镜像
docker build -t identidock .
- 绑定挂载并运行测试
docker run -p 9090:9090 -p 9191:9191 -v "$PWD"/app:/app identidock
curl 172.17.0.2:9090
8.2.3 改变容器内运行的用户
-
在容器内默认是以root用户的身份运行的, 拥有最高权限, 在正常的部署时, 一般都是以指定的用户运行
-
要改变默认的以root用户运行, 必须先修改 Dockerfile 文件内容, 其内容如下:
FROM python:3.4
# 创建uwsgi用户和用户组
RUN groupadd -r uwsgi && useradd -r -g uwsgi uwsgi
RUN pip install --upgrade pip
RUN pip install Flask==0.10.1 uWSGI
WORKDIR /app
COPY app /app
EXPOSE 9090 9191 # 对外暴露的端口
USER uwsgi
CMD ["uwsgi", "--http", "0.0.0.0:9090", "--wsgi-file", "/app/identidock.py", "--callable", "app", "--stats", "0.0.0.0:9191"]
- 构建镜像
docker build -t identidock .
- 测试
docker run identidock whoami
8.2.4 通过编写shell脚本, 按实际的运行环境切换不同的功能
- 在 Dockerfile 的同一目录下, 创建一个 cmd.sh 的脚本, 其内容如下:
#!/bin/bash
set -e
if [ "$ENV" = 'DEV' ]; then
echo "Running Development Server"
exec python "/app/identidock.py"
else
echo "Running Production Server"
exec uwsgi --http 0.0.0.0:9090 --wsgi-file /app/identidock.py --callable app --stats 0.0.0.0:9191
fi
- 再次更新 Dockerfile 文件, 其内容如下:
FROM python:3.4
# 创建uwsgi用户和用户组
RUN groupadd -r uwsgi && useradd -r -g uwsgi uwsgi
RUN pip install --upgrade pip
RUN pip install Flask==0.10.1 uWSGI
WORKDIR /app
COPY app /app
COPY cmd.sh / # 将 cmd.sh 拷贝进来
EXPOSE 9090 9191
USER uwsgi
CMD ["/cmd.sh"] # 执行cmd.sh脚本
- 赋予 cmd.sh 执行的权限
chmod +x cmd.sh
- 构建镜像
docker build -t identidock .
- 绑定挂载, 运行测试
// 通过 -e 参数传入 "ENV=DEV" 或者不传入就表示 "ENV!=DEV"
docker run -e "ENV=DEV" -p 5000:5000 -v "$PWD"/app:/app identidock // 直接通过 python 运行
curl localhost:5000
// docker rm $(docker stop $(docker ps -aq)) // 停止删除所有容器
docker run -p 5000:5000 -v "$PWD"/app:/app identidock // 通过 uswgi 运行项目
curl 172.17.0.2:9090
8.2.5 通过 Compose 来管理运行项目
-
Compose 学些在第7小节
-
在 identidock 目录下, 编写 docker-compose.yml 文件, 其内容如下:
identidock: # 声明构建的容器名称, 一个YAML文件中可以定义多个容器
build: . # build关键字告诉Compose,这个容器的镜像是通过当前目录(.)下的Dockerfile构建, 每个容器的定义必须包括一个build或image关键字
ports: # ports关键字相当于docker run命令的-p参数,用于声明对外开放的端口
- "5000:5000"
environment: # environment关键字相当于docker run命令的-e参数,用来设置容器的环境变量
- ENV:DEV
volumes: # volumes关键字相当于docker run的-v参数,用于配置数据卷
- ./app:/app
- 执行 docker-compose up 运行项目
docker-compose up
- 总结: 8.2 小节实现了docker容器部署 python+flask+uwsgi 项目, 通过挂在绑定实现了更新代码的方便性, 通过编写cmd.sh脚本, 可以在运行时自主选择开发或生产环境, 通过docker-compose.yml文件, 简化了构建及容器
运行; 需要学习的点: ① Dockerfile 文件编写; ② 绑定挂载; ③ 初级在初级的shell脚本格式及编写; ④ YML文件编写格式及语法
8.3 升级 8.2 的helloworld为identicon
8.3.1 创建一个简单的Web应用
- 将identidock.py 内容替换为下:
from flask import Flask, Response
import requests
#
app = Flask(__name__)
#
default_name = 'Joe Bloggs'
#
@app.route('/')
def mainpage():
name = default_name
#
header = '<html><head><title>Identidock</title></head><body>'
body = '''<form method="POST">
Hello <input type="text" name="name" value="{0}">
<input type="submit" value="submit">
</form>
<p>You look like a:
<img src="/monster/monster.png"/>
'''.format(name)
footer = '</body></html>'
#
return header + body + footer
#
@app.route('/monster/<name>')
def get_identicon(name):
#
r = requests.get('http://dnmonster:8080/monster/' + name + '?size=80')
image = r.content
#
return Response(image, mimetype='image/png')
#
#
if __name__ == ("__main__"):
app.run(debug=True, host='0.0.0.0')
- 修改 Dockerfile 文件
FROM python:3.4
RUN groupadd -r uwsgi && useradd -r -g uwsgi uwsgi
RUN pip install Flask==0.10.1 uWSGI requests==2.5.1
WORKDIR /app
COPY app /app
COPY cmd.sh /
EXPOSE 9090 9191
USER uwsgi
CMD ["/cmd.sh"]
- 构建镜像
docker build -t identidock .
- 下载 dnmonster 镜像
// 先用普通的Docker命令操作一次,之后才会转用Compose。由于这是我们第一次使用dnmonster镜像,需要从Docker Hub下载它
docker run -d --name dnmonster amouat/dnmonster
- 运行
// 多了参数--link dnmonster:dnmonster,这个参数的目的是把两个容器相连
docker run -d -p 5000:5000 -e "ENV=DEV" --link dnmonster:dnmonster identidock
- 更新 docker-compose.yml
identidock:
build: .
ports:
- "5000:5000"
environment:
- ENV:DEV
volumes:
- ./app:/app
links: # 声明一个从identidock容器到dnmonster容器的连接。Compose会负责处理容器的正确启动顺序,使连接能成功建立
- dnmonster
dnmonster: # 定义一个新的dnmonster容器。我们只需告诉Compose,使用来自Docker Hub的amouat/dnmonster:1.0镜像
image: amouat/dnmonster
- 删除之前创建的容器
// 查找所有容器, 停止, 并删除
docker rm $(docker stop $(docker ps -q))
- 使用 componse 构建镜像 并 运行
// 构建镜像
docker-compose build
// 运行
docker-compose up -d
对用户的输入进行散列处理, 保护任何偶然输入敏感信息的人
- 更新 identidock.py
from flask import Flask, Response, request
import requests
import hashlib # 导入对用户输入进行散列处理的程序库
#
app = Flask(__name__)
salt = "UNIQUE_SALT" # 定义散列函数的salt值。通过改变这个值,相同的输入在不同的网站可以生成不一样的identicon
default_name = 'jefxff'
#
# Flask的route默认只会响应HTTP GET的请求。因为我们的表单提交的是HTTP POST请求,
# 所以必须给route的声明加入以methods命名的参数,明确宣告route可以处理POST和GET两种请求
@app.route('/', methods=['GET', 'POST'])
def mainpage():
name = default_name
# 如果request.method等同于"POST",该请求必定来自点击提交按钮。在此情况下,需要把用户输入的值赋予name变量
if request.method == 'POST':
name = request.form['name']
#
salted_name = salt + name
# 对输入执行SHA256算法,以获取散列值
name_hash = hashlib.sha256(salted_name.encode()).hexdigest()
header = '<html><head><title>Identidock</title></head><body>'
body = '''<form method="POST">
Hello <input type="text" name="name" value="{0}">
<input type="submit" value="submit">
</form>
<p>You look like a:
<img src="/monster/{1}"/>
'''.format(name, name_hash)
footer = '</body></html>'
#
return header + body + footer
#
@app.route('/monster/<name>')
def get_identicon(name):
#
r = requests.get('http://dnmonster:8080/monster/' + name + '?size=80')
image = r.content
#
return Response(image, mimetype='image/png')
#
#
if __name__ == ("__main__"):
app.run(debug=True, host='0.0.0.0')
通过 Redis 实现缓存功能
- 更新 python 代码
from flask import Flask, Response, request
import requests
import hashlib # 导入对用户输入进行散列处理的程序库
import redis # 导入Redis模块
#
app = Flask(__name__)
# 建立Redis缓存。我们将通过Docker连接的特性,使redis这个主机名能够被解析
cache = redis.StrictRedis(host='redis', port=6379, db=0)
salt = "UNIQUE_SALT" # 定义散列函数的salt值。通过改变这个值,相同的输入在不同的网站可以生成不一样的identicon
default_name = 'jefxff'
#
# Flask的route默认只会响应HTTP GET的请求。因为我们的表单提交的是HTTP POST请求,
# 所以必须给route的声明加入以methods命名的参数,明确宣告route可以处理POST和GET两种请求
@app.route('/', methods=['GET', 'POST'])
def mainpage():
name = default_name
# 如果request.method等同于"POST",该请求必定来自点击提交按钮。在此情况下,需要把用户输入的值赋予name变量
if request.method == 'POST':
name = request.form['name']
#
salted_name = salt + name
# 对输入执行SHA256算法,以获取散列值
name_hash = hashlib.sha256(salted_name.encode()).hexdigest()
header = '<html><head><title>Identidock</title></head><body>'
body = ('''<form method="POST">
Hello <input type="text" name="name" value="{0}">
<input type="submit" value="submit">
</form>
<p>You look like a:
<img src="/monster/{1}"/>''').format(name, name_hash)
footer = '</body></html>'
#
return header + body + footer
#
@app.route('/monster/<name>')
def get_identicon(name):
# 检查名字是否已经在缓存中
image = cache.get(name)
# 如果缓存未命中,Redis将返回None。在这种情况下,程序就像往常一样获取identicon
if image is None:
# 输出一些调试信息,表示我们没有在缓存中找到图像,
print("Cache miss", flush=True)
r = requests.get('http://dnmonster:8080/monster/' + name + '?size=80')
image = r.content
# 把图像添加到缓存中,并与名字相关联
cache.set(name, image)
#
return Response(image, mimetype='image/png')
#
#
if __name__ == ("__main__"):
app.run(debug=True, host='0.0.0.0')
- 更新 Dockerfile, 安装 python 的 redis 客户端库
FROM python:3.4
RUN groupadd -r uwsgi && useradd -r -g uwsgi uwsgi
RUN pip install Flask==0.10.1 uWSGI==2.0.8 requests==2.5.1 redis==2.10.3
WORKDIR /app
COPY app /app
COPY cmd.sh /
EXPOSE 9090 9191
USER uwsgi
CMD ["/cmd.sh"]
- 更新 docker-compose.yml
identidock:
build: .
ports:
- "5000:5000"
environment:
- ENV:DEV
volumes:
- ./app:/app
links: # 声明一个从identidock容器到dnmonster容器的连接。Compose会负责处理容器的正确启动顺序,使连接能成功建立
- dnmonster
- redis # 建立与Redis容器的连接
dnmonster: # 定义一个新的dnmonster容器。我们只需告诉Compose,使用来自Docker Hub的amouat/dnmonster:1.0镜像
image: amouat/dnmonster:1.0
redis:
image: redis:3.0 # 创建一个基于官方Redis镜像的容器
-
停止之前的容器 docker-compose rm $(docker stop $(docker ps -aq))
-
构建镜像 docker-compose build
-
运行 docker-componse up