应用场景
节省项目环境部署时间
- 单项目打包
- 整套项目打包
- 新开源技术
- 环境一致性
- 持续集成
- 微服务
- 弹性伸缩
Docker 体系结构
- containerd 是一个守护进程,使用 runc 管理容器,向 Docker Engine 提供接口
- shim 只负责管理一个容器
- runc 是一个轻量级工具,只用来运行容器
Docker 内部组件
namespaces
命名空间,Linux 内核提供的一种对进程资源隔离的机制,例如进程、网络、挂载等资源cgroups
控制组,Linux 内核提供的一种限制进程资源的机制,如 cpu、内存等资源unonFS
联合文件系统,支持将不同位置的目录挂载到同一虚拟文件系统,形成一种分层的模型
Docker 架构
Docker 安装
安装
yum install -y yum-utils device-mapper-persistent-data lvm2 yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo yum install docker-ce docker-ce-cli containerd.io
阿里云加速
sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": ["https://6c3hbnlf.mirror.aliyuncs.com"] } EOF sudo systemctl daemon-reload sudo systemctl restart docker
image 镜像
- Docker 将应用程序及其依赖,打包在 image 文件里。只有通过该文件,才能生成 Docker 容器
- image 可以看做是容器的模板
- Docker 工具 image 生成容器的实例,同一个 image 文件,可以生成多个同时运行的容器实例
- 镜像不是一个单一的文件,而是有多层
- 容器其实就是在镜像最上面加了一层读写层,在运行容器里做的任何文件改动,都会写到这个读写层里。如果容器删除了,最上面的读写层也就删除了,改动也就丢失了
- 可以通过
docker history <ID/NAME>
查看镜像中各层内容及大小,每层对应着Dockerfile
中的一条指令
命令 | 示例 | 语法 |
---|---|---|
ls | 查看全部镜像 | docker image ls |
search | 查找镜像 | docker search [imageName] |
history | 查看镜像历史 | docker history [imageName] |
inspect | 显示一个或多个镜像详细信息 | docker inspect [imageName] |
pull | 拉取镜像 | docker pull [imageName] |
push | 推送一个镜像到镜像仓库 | docker push [imageName] |
rmi | 删除镜像 | docker rmi [imageName] |
prune | 移除未使用的镜像,没有标记或补任何容器引用 | docker image prune |
tag | 标记本地镜像,将其归入某一仓库 | docker tag [OPTIONS] IMAGE[:TAG] REGISTRYHOST/NAME[:TAG] |
export | 将容器文件系统作为一个 tar 归档文件导出到 stdout | docker export [OPTIONS] CONTAINER |
import | 导入容器快照文件系统 tar 归档文件并创建镜像 | docker import [OPTIONS] file/URL/-[REGISTRY[:TAG]] |
save | 将指定镜像保存成 tar |
docker save [OPTIONS] IMAGE [IMAGE...] |
load | 加载 tar 文件并创建镜像 |
docker load -i xx.tar |
build | 根据 Dockerfile 构建镜像 | docker build [OPTIONS] PATH / URL / - |
- 用户既可以用
docker load
来导入镜像存储文件到本地镜像库,也可以使用docker import
来导入一个容器快照到本地镜像库 - 两者区别在于容器(import)快照文件将丢弃所有的历史记录和元数据信息(即仅保存当时的快照状态),而镜像(load)存储文件将保存完整记录,体积较大
- 从容器(import)快照文件导入时可以重新指定标签等元数据信息
# 查看镜像
docker image ls
# 拉取镜像
docker pull hello-world
# 删除镜像
docker rmi hello-world
# 查看镜像历史
docker history hello-world
commit 制作个性化镜像
docker container commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
从容器创建一个新的镜像- -a 作者
- -c 使用 Dockerfile 指令来创建镜像
- -m 提交时的说明
- -p 在 commit 时,将容器暂停
- 停止容器后不会自动删除这个容器,除非在启动容器时指定了 --rm 标志
- 使用 docker ps -a 命令查看 Docker 主机上包含停止的容器在内的所有容器
- 停止状态的容器的可写层仍然占用磁盘空间,可以用
docker container prune
清理 提交远程仓库
docker login docker push
制作 Dockerfile
- Docker 的镜像使用一层一层的文件组成
docker inspect
可以查看镜像或容器- Layers 就是镜像的层文件,只读不能修改,基于镜像创建的容器会共享这些文件层
docker inspect centos
编写 Dockerfile
- -t --tag list 镜像名称
- -f --file string 指定 Dockerfile 文件的位置
指令 | 含义 | 示例 |
---|---|---|
FROM | 构建的新镜像是基于哪个镜像 | FROM centos:6 |
MAINTAINER | 镜像作者 | MAINTAINER cell |
RUN | 构建镜像时运行的 shell 命令 | RUN yum install httpd |
CMD | CMD 设置容器启动后默认执行的命令及其参数,但 CMD 能够被 docker run 后面跟的命令行参数替换 | CMD /usr/sbin/sshd -D |
EXPOSE | 声明容器运行的服务器端口 | EXPOSE 80 443 |
ENV | 设置容器内的环境变量 | ENV MYSQL_ROOT_PASSWORD 12345 |
ADD | 拷贝文件或目录到镜像中,如果是 URL 或者压缩包会自动下载和解压 | ADD https://x.com/html.tar.gz /var/www.html ADD html.tar.gz /var/www.html |
COPY | 拷贝文件或目录到镜像 | COPY ./start.sh /start.sh |
ENTRYPOINT | 配置容器启动时运行的命令 | ENTRYPOINT /bin/bash -c '/start.sh' |
VOLUME | 指定容器挂载点到宿主自动生成的目录或其他容器 | VOLUME ["/var/lib/mysql"] |
USER | 为 RUN CMD 和 ENTRYPOINT 执行命令指定运行用户 | USER cell |
WORKDIR | 为 RUN CMD ENTRYPOINT COPY ADD 设置工作目录 | WORKDIR /data |
HEALTHCHECK | 健康检查 | HEALTHCHECK --interval=5m --timeout=3s |
ARG | 在构建镜像时指定一些参数 | ARG user |
- cmd 给出的是一个容器的默认的可执行体。即容器启动后,默认执行的命令。如果
docker run
没有指定任何执行命令或者 dockerfile 里也没有entrypoint
,那就会使用 cmd 指定的默认的执行命令执行。同时也从侧面说明了entrypoint
的含义,它才是真正的容器启动以后要执行的命令。
dockerignore
排除,不打包到 image 中的文件路径
.git
node_modules
Dockerfile
- 安装 node
# 安装 nvm
wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash # 下载命令
source /root/.bashrc # 加入系统变量
nvm install stable
node -v
npm i cnpm -g
npm i nrm -g
- 制作
下载基础镜像
docker pull node
创建项目
npm i express-gernerator
express myapp
cd myapp
npm i
npm run start
制作 Dockerfile
FROM node
COPY ./myapp /myapp
WORKDIR /myapp
RUN npm install # 构建镜像时运行的
EXPOSE 3000
CMD npm start # 启动容器时运行
制作忽略文件 .dockerignore
.git
node_modules
构建镜像
docker build -t myapp:1.0.0 .
查看镜像
docker image ls
启动容器
# -d 后台运行
# -p 3333 端口映射到容器 3000 端口
docker run -d -p 3333:3000 myapp:1.0.0
容器
docker run
可以从 image 生成一个正在运行的容器实例docker container run
命令具有自动抓取 image 文件的功能,如果发现本地没有指定的 image,就会从仓库自动抓取- 有些容器会自动通知,有些不会自动停止
- image 生成的容器实例,本身也是一个文件,称为容器文件
- 容器生成,就会同时存在两个文件:image 和 容器文件
- 关闭容器不会删除容器文件,只是容器停止运行
- docker 容器的主线程(dockerfile 中 CMD 执行的命令)结束,容器会退出
命令 | 含义 | 示例 |
---|---|---|
run | 从镜像运行一个容器 | docker run ubuntu /bin/echo 'hello world' |
ls | 列出容器 (-a 列出所有) | docker container ls |
inspect | 显示一个或多个容器详细信息 | docker inspect |
attach | 要 attach 上去的容器必须正在运行,可以同时连接上同一个 container 来共享屏幕 | docker attach [OPTIONS] CONTAINER |
stats | 显示容器资源使用情况 | docker container stats |
top | 显示一个容器运行的进程 | docker container top |
update | 更新一个或多个容器配置 | docker update -m 500m --memory-swap -16d1a25f95132 |
port | 列出指定的容器的端口映射 | docker run -d -p 8080:80 nginx docker container port containerID |
ps | 查看当前运行的容器 | docker container ps -a -l |
kill [containerId] | 终止容器(发送 SIGKILL) | docker kill [containerId] |
rm [containerId] | 删除容器 | docker container rm [containerId] |
start [containerId] | 启动已经生成、已经停止运行的容器文件 | docker start [containerId] |
stop [containerId] | 终止容器运行(发送 SIGTERM) | docker stop [containerId] |
logs [containerId] | 查看 docker 容器的输出 | docker logs [containerId] |
exec [containerId] | 进入一个正在运行的 docker 容器执行命令 | docker container exec -it xx /bin/bash |
cp [containerId] | 从正在运行的 Docker 容器里面,将文件拷贝到本机 | docker container cp xx:/root/root.txt |
commit [containerId] | 根据一个现有容器创建一个新的镜像 | docker commit -a "name" -m "myimg" xxx myimg:v1 |
数据盘 VOLUME
删除容器的时候,容器里面创建的文件也会被删除,如果有些数据想永久保存,如 web 服务器日志,数据库管理系统中的数据库,可以为容器创建一个数据盘
volume
- volumes Docker 管理宿主机文件系统的一部分(/var/lib/docker/volumes)
- 如果没有指定卷,会自动创建
- 建议使用 --mount 更通用
- 创建数据卷
docker volume --help
docker volume create nginx-vol
docker volume ls
docker volume inspect nginx-vol
# 将 nginx-vol 数据卷挂载到 /usr/share/nginx/html,挂载后容器内的文件会同步到数据卷中
docker run -d --name=nginx1 --mount src=nginx-vol,dst=/usr/share/nginx/html nginx
docker run -d --name=nginx2 -v nginx-vol:/usr/share/nginx/html -p 3000:80 nginx
- 删除数据卷
docker container stop nginx1 # 停止容器
docker container rm nginx1 # 删除容器
docker volume rm nginx-vol # 删除数据卷
- 管理数据卷
docker volume ls # 列出所有数据盘
docker volume ls -f dangling=true # 列出已经孤立的数据盘
docker volume rm xx # 删除数据卷
Bind mounts
- 该方式与 Linux 系统的 mount 方式很相似,即会覆盖容器内已存在的目录或文件,但不会改变容器内原有的文件,当 umount 后,容器内原有的文件就会还原
- 创建容器的时候可以通过
-v
或--volume
给它指定数据卷 bind mounts
可以存储在宿主机系统的任意位置- 如果源文件 / 目录不存在,不会自动创建,会抛出错误
- 如果挂载目标在容器中非空目录,则该目录现有内容将被隐藏
网络
安装 Docker 时,会自动创建三个网络,bridge (创建容器默认连接到此网络)、none、host。可以使用 --network
来指定容器应该连接到哪些网络
- None:关闭容器网络功能,对外界完全隔离(
--net none
) - host:不会虚拟出自己网卡,配置自己的 IP 等,而是使用宿主机的 IP 和端口 (
--net host
) - Bridge: 桥接网络,会为每一个容器分配 IP (默认,或
--net bridge
)
docker inspect bridge # 查看网络信息
端口映射
# 查看镜像暴露的端口
docker image inspect nginx
# 让宿主机的 8080 映射到 docker 容器的 80
docker run -d --name port_nginx -p 8080:80 nginx
# 查看主机绑定的端口
docker container port port_nginx
# 指向主机的随机端口
docker run -d --name random_nginx --publish 80 nginx
docker run -d --name random_nginx --publish-all nginx
docker run -d --name random_nginx --P nginx
创建自定义网络
- 可以创建多个网络,每个网络 IP 范围均不相同
- docker 的自定义网络里面有一个 DNS 服务,可以通过容器名称服务主机
# 创建自定义网络
docker network create --driver bridge myweb
# 查看自定义网络中的主机
docker network inspect myweb
# 创建容器时指定网络
docker run -d --name mynginx1 --net myweb nginx
docker run -d --name mynginx2 --net myweb nginx
# 连接到指定网络
docker run -d --name mynginx3 nginx
docker network connect myweb mynginx3
docker network disconnect myweb mynginx3
# 删除网络
docker network rm myweb
compose
- Compose 通过一个配置文件来管理多个 Docker 容器
- 在配置文件中,所有的容器都通过 services 来定义,然后使用 docker-compose 脚本来启动、停止和重启应用和应用中的服务以及所有依赖服务的容器
步骤
- 运行
docker-compose up
,Conpose 将启动并运行这个应用程序配置文件组成 - services 可以定义需要的服务,每个服务都有自己的名字、使用的镜像、挂载的数据卷、所属的网络和依赖的其他服务
- volumes 定义数据卷,然后挂载到不同的服务上使用
- 运行
- 安装 compose
yum -y install epel-release
yum -y install python-pip
yum clean all
pip install docker-compose
- 编写 docker-compose.yml
- 在
docker-compose.yml
中定义组成应用程序的服务,以便它们可以在隔离的环境中一起工作 - 空格缩进表示层次
- 冒号空格后面有空格
version: '2'
services:
nginx1:
image: nginx
ports:
- "8080:80"
nginx2:
image: nginx
ports:
- "8081:80"
- 启动服务
docker-compose up # 执行当前目录下 docker-compose.yml
命令 | 服务 |
---|---|
docker-compose up | 启动所有服务 |
docker-compose up -d | 后台启动所有服务 |
docker-compose ps | 打印所有的容器 |
docker-compose stop | 停止所有服务 |
docker-compose logs -f | 实时跟踪日志 |
docker-compose exec nginx1 bash | 进入 nginx1 服务系统 |
docker-compose rm nginx1 | 删除服务容器 |
docker-compose down | 删除所有的网络和容器 |
- 配置数据卷
version: '3'
services:
nginx1:
image: nginx
ports:
- "8080:80"
networks:
- "myweb"
volumes:
- "data:/data"
- "/nginx1:/usr/share/nginx/html"
nginx2:
image: nginx
ports:
- "8081:80"
networks:
- "myweb"
volumes:
- "data:/data"
- "/nginx2:/usr/share/nginx/html"
networks:
myweb:
driver: bridge
volumes:
data:
driver: local
node 项目
服务分类
- db 使用
mariadb
作为数据库 - node 启动
node
服务 - web 使用
nginx
作为应用的 web 服务器
app 目录结构
文件 | 说明 |
---|---|
docker-compose.yml | 定义开发环境需要的服务 |
images/nginx/config/default.conf | nginx 配置文件 |
images/node/Dockerfile | node 的 Dockerfile 配置文件 |
images/node/web/package.json | 项目文件 |
images/node/web/public/index.html | 静态首页 |
images/node/web/server.js | node 服务 |
|-- docker-compose.yml
|-- images
|-- nginx
| |-- config
| |-- default.conf
|-- node
|-- Dockerfile
|-- web
|-- package.json
|-- public
| |-- index.html
|-- server.js
- docker-compose.yml
version: "2"
services:
node:
build:
context: "./images/node"
dockerfile: Dockerfile
depends_on:
- db
web:
image: nginx
ports:
- "8080:80"
volumes:
- "./images/nginx/config:/etc/nginx/config.d"
- "./images/node/web/public:/public"
depends_on:
- node
db:
image: mariadb
environment:
MYSQL_ROOT_PASSWORD: "root"
MYSQL_USER: "node"
MYSQL_PASSWORD: "123456"
MYSQL_DATABASE: "nodedb"
volumes:
- "db:/var/lib/mysql"
volumes:
db:
driver: local
- Dockerfile
FROM node
COPY ./web /web
WORKDIR /web
EXPOSE 8080
RUN npm install
CMD npm start
- nginx/config/default.config
server {
listen 80;
server_name localhost;
root /public;
index index.html index.htm;
location /api {
proxy_pass http://node:8080;
}
}
- server.js
const http = require('http');
http.createServer(function (req, res) {
res.send('hello docker');
}).listen(8080);
- package.json
{
"scripts": {
"start": "node server.js"
}
}
实战应用
简单 Nginx 服务
# 拉取镜像
docker pull nginx
# 创建目录及页面
mkdir www
echo 'Hello Nginx' >> www/index.html
# 启动容器
docker run -p 8000:80 -v $PWD/www:/usr/share/nginx/html -d nginx
# 查看
docker ps
# 停止
docker stop [containerId]
# 进入容器
docker exec -it [containerId] /bin/bash
# 退出容器
exit
# 删除容器 先停后删
docker stop [containerId]
docker rm [containerId]
定制镜像
简单定制
# 创建目录 mkdir nginx cd nginx # 创建 Dockerfile vi Dockerfile # 定制镜像 docker build -t nginx:cellinlab . # 查看镜像 docker images # 启动容器 docker run -p 80:80 -d nginx:cellinlab
Dockerfile
FROM nginx:latest RUN echo '<h1>Hello Nginx @ cellinlab</h1>' > /usr/share/nginx/html/index.html
定制 Node.js
# 创建目录 mkdir node cd node # 初始化 npm 项目 npm init -y npm i koa -S # 创建应用程序 vi app.js # 定制镜像 vi Dockerfile docker build -t mynode . # 启动容器 docker run -p 3000:3000 -d mynode
app.js
const Koa = require('koa'); const app = new Koa(); app.use(ctx => { ctx.body = 'Hello Node width Docker'; }); app.listen(3000, () => { console.log('app started at 3000'); });
Dockerfile
FROM node:10-alpine ADD . /app/ WORKDIR /app RUN npm install EXPOSE 3000 CMD ["node", "app.js"]
定制 PM2
# 拷贝上一个内容 cp -R node pm2 cd pm2 # 新建 yml vi process.yml # 修改 Dockerfile vi Dockerfile # 构建和启动 docker build -t mypm2 . docker run -p 3000:3000 -d mypm2
process.yml
apps: - script: app.js instances: 2 watch: true env: NODE_ENV: production
Dockerfile
FROM keymetrics/pm2:latest-alpine WORKDIR /usr/src/app ADD . /usr/src/app RUN npm config set registry https://registry.npm.taobao.org/ && \ npm i EXPOSE 3000 CMD ["pm2-runtime", "start", "process.yml"]
Docker Compose
mongo-express 示例
# 创建项目
mkdir mongo
cd mongo
# 创建 yml
vi docker-compose.yml
# 运行
docker-compose up
docker-compose.yml
version: '3.1'
services:
mongo:
image: mongo
restart: always
ports:
- 27017:27017
mongo-express:
image: mongo-express
restart: always
ports:
- 8000:8081