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?
- Docker Client一般是一个命令行工具(Docker CLI),通过API与 Docker Dameon通信
- Docker Compose 是Docker Client 其中一种,适用于多容器集成,提供一种服务的场景。
- Docker Client 与 Docker Dameon的通信方式的是REST API,基于sockets 或 网络接口的方式
- Docker Hub是官方镜像库,是Docker Registry(注册镜像的仓库)的一种,因此可构建自己的docker registry
- Docker Engine是对Docker Client 、Docker Dameon、Docker API的一种统称
- Docker Dameon是创建和管理Docker对象的进程,对象主要有:镜像(images)、容器(containers)、存储(volume)、网络(network)
解释:运行一个docker build命令,是在Docker Client上(一般是Docker CLI 或 Docker Compose)发送REST API到Docker管理程序Docker Daemon,一可从镜像注册库Registry获取构建好的镜像Image,如官方Docker Hub;或自行根据Dockerfile文件在Docker Daemon下构建镜像;然后run一个镜像,变成容器,管理容器的主要对象:存储、网络
一、朴素的流程
1.1 在container中运行应用程序
- 首先有一份关于某app的源代码。
- 为该源代码写一份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
- 有了Dockerfile后,构建image,docker运行得到container,在container中跑程序
-
docker build -t getting-started .
:在当前路径.
下寻找dockerfile,根据dockerfilebuild
一个标签-t
为getting-started
的镜像 -
docker run -dp 3000:3000 getting-started
:以后台-d(detached)
的方式,并配置镜像getting-started
中的app应用映射端口-p
,3000:3000
主机端口3000映射到容器端口3000,得到container,该container执行node src/index.js
启动源代码的程序
1.2 修改源代码后更新应用程序
运行docker ps
,查看1.1中运行了程序的container:
修改源代码后,停止运行中的容器——删掉容器——重新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。
- 准备工作:
- 在Docker Hub注册账号
YOUR-USER-NAME
并创建一个仓库repo -
docker login -u YOUR-USER-NAME
登陆到Docker Hub
- 分享镜像getting-started到仓库repo的操作:
-
docker tag getting-started YOUR-USER-NAME/getting-started
:用tag给镜像改名字 -
docker push YOUR-USER-NAME/getting-started
:push镜像到repo
- 另一部物理机拉取镜像并运行
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上拉去镜像并运行
于是我们创建了一个网页端的服务:
二、在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上任意的存储位置映射到容器内的特定位置,使它们共享同一个物理存储,达到保存容器数据变化的目的。
因此上面两个例子,一用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查询
- 创建一个名为todo-app的网络(容器之间通信)
docker network create todo-app
- 在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数据库实例
- 在todo-app网络内run一个netshoot的image,得到netshoot容器,并查看该网络中别名为mysql的ip地址
docker run -it --network todo-app nicolaka/netshoot
dig mysql // 在netshoot容器中利用其提供的命令dig,来寻找网络中hostname为mysql容器对象的ip地址
- 在网络中创建一个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"
- 查看信息
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的关键在于两个:
- 如何多快好省地写dockerfile,使得由此构建的image,基于此image而运行的container具备体积小、速度快、安全高?(单容器的构建、改动、分享、删除)
- 如何写docker-compose.yml来操作多个容器之间的互通,来共同提供一个高可用、高并发、可靠性高的app服务?(多容器的互通)
如果想在多机上跑多个docker又怎么处理?(容器编排工具:Kubernetes, Swarm, Nomad, ECS)