Docker-Tutorial

  • 概述
  • 零、Docker核心部件
  • 一、朴素的流程
  • 1.1 在container中运行应用程序
  • 1.2 修改源代码后更新应用程序
  • 1.3 分享构建好的image
  • 1.4 总结
  • 二、在Container中保留数据
  • 2.1 Named volume
  • 2.2 Bind Mounts:
  • 2.3 总结
  • 三、从单容器的App扩展到多容器的App
  • 3.1 简单的多容器部署
  • 3.2 复杂的多容器部署
  • 总结


概述

任何工具,英文官方文档1总是最好的。

对整个Tutorial做一个简明扼要的翻译。
简单的概念自行搜索:image、container、Dockerfile

假设已经安装docker:
docker run -d -p 80:80 docker/getting-started 可以在http://localhost:80本地打开docker tutorial
http://localhost/assets/app.zip下载docker tutorial中某app的源代码

流程概览:

  • 创建一个Node.js的应用
  • 创建一个Dockerfile描述如何创建Node.js的镜像
  • 运行这个镜像,创建一个镜像的实例——Node.js的容器
  • 本地构建一个数据库的容器来存储Node.js容器的数据
  • 利用Docker Compose来一起跑数据库容器和Node.js容器,使其一起提供app的to-do-list服务。

零、Docker核心部件

Docker Client ? Docker Engine ? Docker Compose ? Docker Dameon?Docker Hub? Docker Registry?

  1. Docker Client一般是一个命令行工具(Docker CLI),通过API与 Docker Dameon通信
  2. Docker Compose 是Docker Client 其中一种,适用于多容器集成,提供一种服务的场景。
  3. Docker Client 与 Docker Dameon的通信方式的是REST API,基于sockets 或 网络接口的方式
  4. Docker Hub是官方镜像库,是Docker Registry(注册镜像的仓库)的一种,因此可构建自己的docker registry
  5. Docker Engine是对Docker Client 、Docker Dameon、Docker API的一种统称
  6. Docker Dameon是创建和管理Docker对象的进程,对象主要有:镜像(images)、容器(containers)、存储(volume)、网络(network)

docker run 日子 docker run -n_docker run 日子

解释:运行一个docker build命令,是在Docker Client上(一般是Docker CLI 或 Docker Compose)发送REST API到Docker管理程序Docker Daemon,一可从镜像注册库Registry获取构建好的镜像Image,如官方Docker Hub;或自行根据Dockerfile文件在Docker Daemon下构建镜像;然后run一个镜像,变成容器,管理容器的主要对象:存储、网络

一、朴素的流程

1.1 在container中运行应用程序

  1. 首先有一份关于某app的源代码。
  2. 为该源代码写一份Dockerfile:目的是为运行源代码的应用构建运行环境(依赖、库、资源等)
FROM node:12-alpine  
RUN apk add --no-cache python g++ make
WORKDIR /app
COPY . .
RUN yarn install --production 
CMD ["node", "src/index.js"]
  • FROM:从Docker官网镜像库Docker Hub拉取一个有node环境alpine操作系统的基础镜像node:12-alpine
  • RUN:利用安卓包管理工具apk以资源最小化原则--no-cache按照package.json文件安装系统常用依赖以及一些支持库python、g++、make
  • WORKDIR:告诉要构建镜像(image)内部的工作路径/app
  • COPY:将当前源代码的目录~/app/下所有文件拷贝到image内的/app
  • RUN:利用Hadoop资源管理器yarn install 配置文件yarn.lock中的所有依赖(–production)
  • CMD:根据DockerFile文件的描述构建image,在运行该image的container中执行CMD的命令,即在container中运行node src/index.js
  1. 有了Dockerfile后,构建image,docker运行得到container,在container中跑程序
  • docker build -t getting-started . :在当前路径.下寻找dockerfile,根据dockerfile build一个标签-tgetting-started的镜像
  • docker run -dp 3000:3000 getting-started:以后台-d(detached)的方式,并配置镜像getting-started中的app应用映射端口-p3000:3000主机端口3000映射到容器端口3000,得到container,该container执行node src/index.js启动源代码的程序

1.2 修改源代码后更新应用程序

运行docker ps,查看1.1中运行了程序的container:

docker run 日子 docker run -n_Docker_02


修改源代码后,停止运行中的容器——删掉容器——重新build镜像——重新run镜像得到修改源代码后的container——> 达到更新应用程序的目的:

docker stop container_id
docker rm container_id
docker build -t getting-started .
docker run -dp 3000:3000 getting-started

1.3 分享构建好的image

这里的流程有点像git,把image push到一个repo。

  1. 准备工作:
  • 在Docker Hub注册账号YOUR-USER-NAME并创建一个仓库repo
  • docker login -u YOUR-USER-NAME登陆到Docker Hub
  1. 分享镜像getting-started到仓库repo的操作:
  • docker tag getting-started YOUR-USER-NAME/getting-started:用tag给镜像改名字
  • docker push YOUR-USER-NAME/getting-started:push镜像到repo
  1. 另一部物理机拉取镜像并运行
  • docker run -dp 3000:3000 YOUR-USER-NAME/getting-started

1.4 总结

有一份源文件,并为此写了一份Dockerfile后:

docker build -t getting-started . // 构建镜像
docker run -dp 3000:3000 getting-started // 在container中跑程序
docker stop container_id // 停止container
docker rm container_id	// 删除container
docker rm -f <id> //强行停止并删除container (二合一)
————————————————————————————————————————————————————————————————
docker build -t getting-started . // 更新代码后重新构建镜像
docker run -dp 3000:3000 getting-started // 在container中跑程序
————————————————————————————————————————————————————————————————
docker tag getting-started YOUR-USER-NAME/getting-started // 重新命名镜像
docker push YOUR-USER-NAME/getting-started // 分享镜像到repo
docker run -dp 3000:3000 YOUR-USER-NAME/getting-started // 从repo上拉去镜像并运行

于是我们创建了一个网页端的服务:

docker run 日子 docker run -n_mysql_03

二、在Container中保留数据

2.1 Named volume

假设上述的镜像getting-started里,数据放在docker容器内的/etc/todos/data.txt里,想在物理机host上保存docker容器内的数据。

  • docker volume create todo-db
  • docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started

解释:利用docker创建一个数据卷named volume即todo-db;
run一个container的时候,把volume加到容器中去,即-v todo-db:/etc/todos,意味着数据卷todo-db监控并保存container内部路径/etc/todos/所有的变化。

查看数据卷todo-db的具体信息:docker volume inspect todo-db

2.2 Bind Mounts:

  • docker run -dp 3000:3000 -v /path/to/data:/etc/todos getting-started

将物理机文件系统上的/path/to/data 映射到容器内的/etc/todos来保存数据。

举一个例子:

docker run -dp 3000:3000 \
    -w /app -v "$(pwd):/app" \
    node:12-alpine \
    sh -c "yarn install && yarn run dev"
  • -w:表示容器工作默认工作目录在容器内部的/app
  • -v:表示把物理机当前工作目录映射到容器内部的/app,有数据卷volume监控并保存所有本地的变化。意味着source code改了,容器内部程序也知道
  • dev:是node中一个监控文件系统变化的脚本,如果变化了就重启应用程序

2.3 总结

Named Volume在物理机host上一个特定的存储位置(可用docker volume inspect todo-db查看)来保存着容器中的数据变化,所以不能控制存储的位置。

Bind Mounts可以将物理机host上任意的存储位置映射到容器内的特定位置,使它们共享同一个物理存储,达到保存容器数据变化的目的。

docker run 日子 docker run -n_docker run 日子_04

因此上面两个例子,一用named volume来保存容器内SQLite数据库的数据,二用Bind mount来使物理机host上源代码的改变能直接作用容器内,用dev来检测文件变化,若变化则重新启动服务。

三、从单容器的App扩展到多容器的App

3.1 简单的多容器部署

问:能不能用MySQL数据库来保存todo App中的数据变化呢?
答:可以。但需要run一个配置好的MySQL镜像作为一个MySQL容器

问:那原本的App容器又如何跟MySQL容器进行通信呢?
答:用docker创建一个网络,包含这两个容器,用ip地址进行通信

问:App容器怎么知道网络中MySQL容器的ip地址?
答:用netshoot这个工具包进行DNS查询


  1. 创建一个名为todo-app的网络(容器之间通信)
docker network create todo-app
  1. 在todo-app的网络中,创建一个别名为mysql的容器
docker run -d \
    --network todo-app --network-alias mysql \
    -v todo-mysql-data:/var/lib/mysql \
    -e MYSQL_ROOT_PASSWORD=secret \
    -e MYSQL_DATABASE=todos \
    mysql:5.7

别名为sql容器的配置:

  • -v:在物理主机host上创建名为todo-mysql-data的named volume来存储mysql:5.7这个镜像run的容器中/var/lib/mysql的数据
  • -e:设置镜像中的环境变量,如密码为secret、名为todos的mysql数据库实例
  1. 在todo-app网络内run一个netshoot的image,得到netshoot容器,并查看该网络中别名为mysql的ip地址
docker run -it --network todo-app nicolaka/netshoot
dig mysql // 在netshoot容器中利用其提供的命令dig,来寻找网络中hostname为mysql容器对象的ip地址
  1. 在网络中创建一个app容器,让它先找到网络中的mysql容器,然后将app自身的数据保存到mysql数据库中的实例todos,带着用户名和密码
docker run -dp 3000:3000 \
  -w /app -v "$(pwd):/app" \
  --network todo-app \
  -e MYSQL_HOST=mysql \
  -e MYSQL_USER=root \
  -e MYSQL_PASSWORD=secret \
  -e MYSQL_DB=todos \
  node:12-alpine \
  sh -c "yarn install && yarn run dev"
  1. 查看信息
docker logs <container-id>//如果想查看某个container的信息
docker exec -it <mysql-container-id> mysql -p todos //查看app容器中的数据是否保存到mysql数据库实例todos
select * from todo_items; //查看todos具体数据库的内容

3.2 复杂的多容器部署

上面一个个容器的具体配置、创建实在是太复杂了,能不能把它复杂的流程写在一个文件内,下次替我主动构建这个app?可以。用Docker Compose

Linux 上安装Docker Compose,真的非常方便:

// 下载docker-compose二进制文件
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

//给安装的二进制文件添加执行权限
sudo chmod +x /usr/local/bin/docker-compose 

//测试安装是否成功
docker-compose --version

在app根目录创建一个docker-compose.yml文件来指定所有服务容器的部署:

version: "3.7" //在docker-compose的文档https://docs.docker.com/compose/compose-file/#volume-configuration-reference中查阅

services:
  app: //主动构建名为app_default的网络别名(network-alias)
    image: node:12-alpine //app服务的基础镜像
    command: sh -c "yarn install && yarn run dev"
    ports: //主机3000端口映射到容器3000端口
      - 3000:3000
    working_dir: /app // 容器工作目录
    volumes:  //主机当前路径./映射到容器/app路径,用的bind mounts模式
      - ./:/app  // bind mounts可以监控源代码的变化
    environment: //环境变量的设置舒服多了
      MYSQL_HOST: mysql
      MYSQL_USER: root
      MYSQL_PASSWORD: secret
      MYSQL_DB: todos

  mysql:
    image: mysql:5.7 // mysql基础镜像
    volumes: // 以named volume数据卷的方式存储mysql容器中/var/lib/mysql的数据
      - todo-mysql-data:/var/lib/mysql
    environment: //设置环境变量
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: todos

volumes: //在物理机上创建名为todo-mysql-data的数据卷
  todo-mysql-data:

定制好这份文件,运行前得注意端口不要被占用掉:

  • docker ps -a
  • docker rm -f <container_id>

运行docker-compose.yml文件:

  • docker-compose up -d:d是detach,后台方式运行

查看运行信息:

  • docker-compose logs -f :查看所有容器运行信息
  • docker-compose logs -f app:查看app容器运行信息

停止app的运行:

  • docker-compose down:停止容器运行,移除网络
  • docker-compose down --volumes:额外移除named volume数据卷

总结

所以单机上docker的关键在于两个:

  1. 如何多快好省地写dockerfile,使得由此构建的image,基于此image而运行的container具备体积小、速度快、安全高?(单容器的构建、改动、分享、删除)
  2. 如何写docker-compose.yml来操作多个容器之间的互通,来共同提供一个高可用、高并发、可靠性高的app服务?(多容器的互通)

如果想在多机上跑多个docker又怎么处理?(容器编排工具:Kubernetes, Swarm, Nomad, ECS)


  1. Docker Tutorial ↩︎