认识 Docker

jefxff 153,634 2020-02-01

1. 何为容器, 为何需要它

  1. 容器是对应用程序及其依赖关系的封装, 乍一看容器只是个轻量级的虚拟机,它和虚拟机一样拥有一个被隔离的操作系统实例,用来运行应用程序。虚拟机一样拥有一个被隔离的操作系统实例,用来运行应用程序。
  2. 容器的目的是使应用程序能够移植, 并把所有依赖关系包含进去
  3. 容器的优点:
    • 容器能与主机的操作系统共享资源, 因而它的效率高出一个数量级.
    • 容器具有可移植性, 能解决由于运行环境的些许改变导致的问题
    • 容器是轻量的, 意味着开发者能同时运行多个容器, 并能模拟分布式系统在真实运行环境下的情况
    • 对用户及开发者而言, 容器的优势不仅仅体现在云端部署

1.1 Docker与容器

  1. Docker 利用现有的Linux容器技术, 以不同方式将其封装及扩展--主要是通过提供可移植的镜像, 以及一个用户友好的接口, 来创建一套完整的容器创建及发布方案
  2. Docker 容器的两个部分:
    • Docker 引擎: 负责创建与运行容器
    • Docker Hub: 用来发布容器的云服务

1.2 Docker 基本概念

  1. 镜像(Image):Docker 镜像(Image),就相当于是一个 root 文件系统。比如官方镜像 ubuntu:16.04 就包含了完整的一套 Ubuntu16.04 最小系统的 root 文件系统。
  2. 容器(Container):镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。也可根据容器自定义镜像
  3. 仓库(Repository):仓库可看成一个代码控制中心(相当于github),用来保存镜像

1.3 镜像, 容器和联合文件系统

  1. UnionFS(联合文件系统):Union文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。Union 文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。

  2. Docker 的镜像由多个不同的 "层" (layer) 组成, 每一个层都是一个只读的文件系统; Dockerfile 里的每个指令都会创建一个新的层, 而这个层将位于前一个层之上; 当一个镜像被转化成一个容器时(譬如通过 docker run 或 docker create 命令), Docker 引擎会在镜像之上添加一个处于最上层的可读写文件系统

  3. 容器几种状态:

    • 已创建(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 链接

  1. 在启用或关闭Windows功能中开启 Hyper-v
  2. 下载 Toolbox
  3. 登录下载之后, 安装(默认)并启动
  4. 镜像加速 Windows 10 的系统,在系统右下角托盘 Docker 图标内右键菜单选择 Settings,打开配置窗口后左侧导航菜单选择 Daemon。在 Registrymirrors 一栏中填写加速器地址 https://registry.docker-cn.com ,之后点击 Apply 保存后 Docker 就会重启并应用配置的镜像地址了。

3. Docker 基础


3.1 docker 相关命名


3.1.1 帮助命令

  1. docker version 查看docker版本信息
  2. docker info 查看docker详细信息
  3. docker --help 相当于Linux的man命令, 用来查看命令的详细信息

3.1.2 镜像命令

  1. docker images [OPTIONS] 列出本地主机上的镜像(OPTIONS参数如下:)

    • -a :列出本地所有的镜像(含中间映像层)
    • -q :只显示镜像ID。
    • --digests :显示镜像的摘要信息
    • --no-trunc :显示完整的镜像信息
  2. docker search [OPTIONS] 镜像名字 (OPTIONS参数如下:)

    • --no-trunc : 显示完整的镜像描述
    • -s : 列出收藏数不小于指定值的镜像。
    • --automated : 只列出 automated build类型的镜像;
  3. docker pull 镜像名字; 拉取某个镜像

  4. docker rmi 镜像名字或ID ; 删除镜像

    • docker rmi -f 镜像ID : 删除单个镜像
    • docker rmi -f 镜像名1:TAG 镜像名2:TAG : 删除2个或多个镜像
    • docker rmi -f $(docker images -qa : 删除全部镜像

3.1.3 容器命令

  1. 新建并启动容器: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

    • --name="容器新名字": 为容器指定一个名称;
    • -d: 后台运行容器,并返回容器ID,也即启动守护式容器;
    • -i:以交互模式运行容器,通常与 -t 同时使用;
    • -t:为容器重新分配一个伪输入终端,通常与 -i 同时使用;
    • -P: 随机端口映射;
    • -p: 指定端口映射,有以下四种格式
      • ip:hostPort:containerPort
      • ip::containerPort
      • hostPort:containerPort
      • containerPort
  2. ** 列出当前所有正在运行的容器 docker ps [OPTIONS]** (参数如下:)

    • -a :列出当前所有正在运行的容器+历史上运行过的
    • -l :显示最近创建的容器。
    • -n:显示最近n个创建的容器。
    • -q :静默模式,只显示容器编号。
    • --no-trunc :不截断输出。
  3. 退出容器

    • 容器停止并退出 exit
    • 容器不停止但退出 Ctrl + P +Q
  4. 启动容器 docker start 容器ID或容器名

  5. 重启容器 docker restart 容器ID或容器名

  6. 停止容器 docker stop 容器ID或容器名

  7. 强制停止容器 docker kill 容器ID或者容器名

  8. 删除已停止的容器

    • docker rm 容器ID或容器名 : 删除一个容器
    • docker rm -f $(docker ps -a -q) : 删除多个容器
    • docker rm -v $(docker ps -aq -f status=exited) :删除所有已停止的容器
  9. 启动守护式容器(后台启动) docker run -d 容器名

    • 例如(后台启动一个centos): docker run -d --name centos01 centos
    • Docker容器后台运行,就必须有一个前台进程.容器运行的命令如果不是那些一直挂起的命令(比如运行top,tail),就是会自动退出的
  10. 查看容器日志 docker logs -f -t --tail 数字 容器ID或容器名

    • -t 是加入时间戳
    • -f 跟随最新的日志打印
    • --tail 数字 显示最后多少条
  11. 查看容器内运行的进程 docker top 容器ID或容器名

  12. 查看容器内部细节 docker inspect 容器ID或容器名

  13. 进入正在运行的容器并以命令行交互

    • docker exec -t -i 容器ID或容器名 bashshell
    • 例如: docker exec -i -t centos01 /bin/bash
  14. 从容器内拷贝文件到主机上 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 容器端口映射

  1. 通过 -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(了解)

  1. 命令行登录 docker login
  2. 命令行退出 docker logout
  3. Docker Hub 就相当于 Github 是保存镜像的地方, 可以从 dockerHub 拉取需要的镜像, 也可以将自己认为newBee 的镜像推送到 dockerhub 上, 供别人拉取使用

5. Dockerfile 学习


5.1 Dockerfile是啥?

  1. Dockerfile是用来构建Docker镜像的构建文件,也是一个文本文件,是由一系列命令和参数构成的脚本, 记述了Docker构建一个镜像所需要的过程,包括安装软件包、创建文件夹、定义环境变量以及其他一些操作(不同于shell脚本,他的一个命令会生成一个容器,修改提交生成镜像,再删除这个临时的容器) Dockerfile 的指令每执行一次都会在 docker 上新建一层;

  2. 由于 docker 的运行模式是 C/S。我们本机是 C,docker 引擎是 S。实际的构建过程是在 docker 引擎下完成的,所以这个时候无法用到我们本机的文件。这就需要把我们本机的指定目录下的文件一起打包提供给 docker 引擎使用。如果未说明最后一个参数,那么默认上下文路径就是 Dockerfile 所在的位置。

  3. 构建步骤: 1.1 编写Dockerfile文件 1.2 docker build 1.3 docker run


5.2 Dockerfile 构建过程

  1. Dockerfile内容
    • 每条保留字指令都必须为大写字母且后面要跟随至少一个参数
    • 指令按照从上到下,顺序执行
    • #表示注释
    • 每条指令都会创建一个新的镜像层,并对镜像进行提交
  2. Docker执行Dockerfile 的大致流程
    • docker从基础镜像运行一个容器
    • 执行一条指令并对容器作出修改
    • 执行类似docker commit的操作提交一个新的镜像层(另一种说法是对容器执行 docker commit 操作 生成新的镜像层,删除这个容器)
    • docker再基于刚提交的镜像运行一个新容器
    • 执行dockerfile中的下一条指令直到所有指令都执行完成
  3. 总结
    • Dockerfile,需要定义一个Dockerfile,Dockerfile定义了进程需要的一切东西。Dockerfile涉及的内容包括执行代码或者是文件、环境变量、依赖包、运行时环境、动态链接库、操作系统的发行版、服务进程和内核进程(当应用进程需要和系统服务和内核进程打交道,这时需要考虑如何设计namespace的权限控制)等等;
    • Docker镜像,在用Dockerfile定义一个文件之后,docker build时会产生一个Docker镜像,当运行 Docker镜像时,会真正开始提供服务;
    • Docker容器,容器是直接提供服务的

5.3 Dockerfile 体系结构(保留字指令)

  1. FROM 基础镜像,当前新镜像是基于哪个镜像的(最基础的镜像是 scratch)
  2. MAINTAINER 镜像维护者的姓名和邮箱地址
  3. RUN 容器构建时需要运行的命令
  4. EXPOSE 当前容器对外暴露出的端口
  5. WORKDIR 指定在创建容器后,终端默认登陆的进来工作目录,一个落脚点;(如果不指定, 默认是在根目录下"/")
  6. ENV 用来在构建镜像过程中设置环境变量
    • ENV MY_PATH /usr/mytest
    • 这个环境变量可以在后续的任何RUN指令中通过$(MY-PATH)来引用使用,这就如同在命令前面指定了环境变量前缀一样;
    • 也可以在其它指令中直接使用这些环境变量, 比如:WORKDIR $MY_PATH
  7. ADD 将宿主机目录下的文件拷贝进镜像且ADD命令会自动处理URL和解压tar压缩包
  8. COPY 类似ADD,拷贝文件和目录到镜像中。将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置 COPY src dest COPY ["src", "dest"]
  9. VOLUME 容器数据卷,用于数据保存和持久化工作
  10. CMD 指定一个容器启动时要运行的命令 Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效,CMD 会被 docker run 之后的参数替换
  11. ENTRYPOINT 指定一个容器启动时要运行的命令 ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数
  12. 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" ]
  1. 构建镜像
    // -f 参数 指定 构建的 Dockerfile 文件名, 也可以是绝对路径
    docker build -f Dockerfileip -t myip .
  1. 运行
    docker run 

6. docker 容器数据卷


6.1 docker 数据卷是什么?

  1. 卷就是目录或文件,存在于一个或多个容器中,由docker挂载到容器,但不属于联合文件系统,因此能够绕过Union File System提供一些用于持续存储或共享数据的特性
  2. 通过 docker run 运行的容器, 在容器运行期间产生的有用数据, 随着容器的停止或删除就会消失; 容器数据卷的目的就是将这些需要的数据进行持久化保存
  3. 各个容器之间是隔离的, 但是通过容器数据卷就可以是实现容器之间的数据共享
  4. 特点:
    • 数据卷可在容器之间共享或重用数据
    • 卷中的更改可以直接生效
    • 数据卷中的更改不会包含在镜像的更新中
    • 数据卷的生命周期一直持续到没有容器使用它为止

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 数据卷容器

  1. 上面是创建数据卷, 实现宿主机和docker容器之间的数据共享, 没有实现容器之间的数据共享, 数据卷容器目的就是实现docker容器之间的数据共享
  2. 数据容器卷就是专门创建一个容器作为挂载数据卷的容器, 称之为数据卷容器也叫做父容器, 其他需要共享的容器通过 --volumes-from "父容器" 来挂载到这个数据卷容器, 实现数据共享
  3. 容器之间配置信息的传递,数据卷的生命周期一直持续到没有容器使用它为止(就是说, 即使删除了父容器, 剩下容器之间也是可以实现数据共享的)
  4. 后继承的容器会自动的复制先前同容器卷共享的数据
  5. 示例:
    // 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 基础

  1. Compose 是用于定义和运行多容器 Docker 应用程序的工具。通过 Compose,可以使用 YML 文件来配置应用程序需要的所有服务。然后,使用一个命令,就可以从 YML 文件配置中创建并启动所有服务

  2. Compose 使用的三个步骤

    • ① 使用 Dockerfile 定义应用程序的环境。

    • ② 使用 docker-compose.yml 定义构成应用程序的服务,这样它们可以在隔离环境中一起运行。

    • ③ 最后,执行 docker-compose up 命令来启动并运行整个应用程序。

  3. 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彻底把容器删除。

  4. 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


# Linux 部署 # docker