容器和虚拟机
我们用的传统虚拟机如 VMware
, VisualBox
之类的需要模拟整台机器包括硬件,每台虚拟机都需要有自己的操作系统,虚拟机一旦被开启,预分配给它的资源将全部被占用。每一台虚拟机包括应用,必要的二进制和库,以及一个完整的用户操作系统。
而容器技术是和我们的宿主机共享硬件资源及操作系统,可以实现资源的动态分配。容器包含应用和其所有的依赖包,但是与其他容器共享内核。容器在宿主机操作系统中,在用户空间以分离的进程运行。
容器技术是实现操作系统虚拟化的一种途径,可以让您在资源受到隔离的进程中运行应用程序及其依赖关系。通过使用容器,我们可以轻松打包应用程序的代码、配置和依赖关系,将其变成容易使用的构建块,从而实现环境一致性、运营效率、开发人员生产力和版本控制等诸多目标。容器可以帮助保证应用程序快速、可靠、一致地部署,其间不受部署环境的影响。容器还赋予我们对资源更多的精细化控制能力,让我们的基础设施效率更高。通过下面这幅图我们可以很直观的反映出这两者的区别所在。
Docker 属于 Linux 容器的一种封装,提供简单易用的容器使用接口。它是目前最流行的 Linux
容器解决方案。
而 Linux
容器是 Linux
发展出了另一种虚拟化技术,简单来讲, Linux
容器不是模拟一个完整的操作系统,而是对进程进行隔离,相当于是在正常进程的外面套了一个保护层。对于容器里面的进程来说,它接触到的各种资源都是虚拟的,从而实现与底层系统的隔离。
Docker
将应用程序与该程序的依赖,打包在一个文件里面。运行这个文件,就会生成一个虚拟容器。程序在这个虚拟容器里运行,就好像在真实的物理机上运行一样。有了 Docker
,就不用担心环境问题。
总体来说, Docker
的接口相当简单,用户可以方便地创建和使用容器,把自己的应用放入容器。容器还可以进行版本管理、复制、分享、修改,就像管理普通的代码一样。
Docker的优势
Docker相比于传统虚拟化方式具有更多的优势:
docker
启动快速属于秒级别。虚拟机通常需要几分钟去启动docker
需要的资源更少,docker
在操作系统级别进行虚拟化,docker
容器和内核交互,几乎没有性能损耗,性能优于通过Hypervisor
层与内核层的虚拟化docker
更轻量,docker
的架构可以共用一个内核与共享应用程序库,所占内存极小。同样的硬件环境,Docker
运行的镜像数远多于虚拟机数量,对系统的利用率非常高- 与虚拟机相比,
docker
隔离性更弱,docker
属于进程之间的隔离,虚拟机可实现系统级别隔离 - 安全性:
docker
的安全性也更弱。Docker
的租户root
和宿主机root
等同,一旦容器内的用户从普通用户权限提升为root权限,它就直接具备了宿主机的root权限,进而可进行无限制的操作。虚拟机租户root
权限和宿主机的root
虚拟机权限是分离的,并且虚拟机利用如Intel
的VT-d
和VT-x
的ring-1
硬件隔离技术,这种隔离技术可以防止虚拟机突破和彼此交互,而容器至今还没有任何形式的硬件隔离,这使得容器容易受到攻击 - 可管理性:
docker
的集中化管理工具还不算成熟。各种虚拟化技术都有成熟的管理工具,例如VMware vCenter
提供完备的虚拟机管理能力 - 高可用和可恢复性:
docker
对业务的高可用支持是通过快速重新部署实现的。虚拟化具备负载均衡,高可用,容错,迁移和数据保护等经过生产实践检验的成熟保障机制,VMware
可承诺虚拟机99.999%
高可用,保证业务连续性 - 快速创建、删除:虚拟化创建是分钟级别的,
Docker
容器创建是秒级别的,Docker
的快速迭代性,决定了无论是开发、测试、部署都可以节约大量时间 - 交付、部署:虚拟机可以通过镜像实现环境交付的一致性,但镜像分发无法体系化。
Docker
在Dockerfile
中记录了容器构建过程,可在集群中实现快速分发和快速部署
我们可以从下面这张表格很清楚地看到容器相比于传统虚拟机的特性的优势所在:
特性 | 容器 | 虚拟机 |
---|---|---|
启动 | 秒级 | 分钟级 |
硬盘使用 | 一般为MB | 一般为GB |
性能 | 接近原生 | 弱于 |
系统支持量 | 单机支持上千个容器 | 一般是几十个 |
Docker的三个基本概念
从上图我们可以看到,Docker
中包括三个基本的概念:
Image
(镜像)Container
(容器)Repository
(仓库)
镜像是 Docker
运行容器的前提,仓库是存放镜像的场所,可见镜像更是 Docker
的核心。
那么镜像到底是什么呢?
Docker
镜像可以看作是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。
镜像(Image)
就是一堆只读层(read-only layer)
的统一视角,也许这个定义有些难以理解,下面的这张图能够帮助读者理解镜像的定义。
从左边我们看到了多个只读层,它们重叠在一起。除了最下面一层,其它层都会有一个指针指向下一层。这些层是Docker
内部的实现细节,并且能够在主机的文件系统上访问到。统一文件系统 (union file system)
技术能够将不同的层整合成一个文件系统,为这些层提供了一个统一的视角,这样就隐藏了多层的存在,在用户的角度看来,只存在一个文件系统。我们可以在图片的右边看到这个视角的形式。
Container (容器)
容器 (container)
的定义和镜像 (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] docker image rmi 2 | |
prune | 移除未使用的镜像,没有标记或补任何容器引用 | docker image prune | docker image prune |
tag | 标记本地镜像,将其归入某一仓库 | docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG] | docker tag centos:7 xxx/centos:v1 |
export | 将容器文件系统作为一个tar归档文件导出到STDOUT | docker export [OPTIONS] CONTAINER | docker export -o hello-world.tar b2712f1067a3 |
import | 导入容器快照文件系统tar归档文件并创建镜像 | docker import [OPTIONS] file/URL/- [REPOSITORY[:TAG]] | docker import hello-world.tar |
save | 将指定镜像保存成tar 文件 |
docker save [OPTIONS] IMAGE [IMAGE...] | docker save -o hello-world.tar hello-world:latest |
load | 加载tar文件并创建镜像 | docker load -i hello-world.tar | |
build | 根据Dockerfile构建镜像 | docker build [OPTIONS] PATH / URL / - | docker build -t zf/ubuntu:v1 . |
Docker应用场景
- 节省项目环境部署时间
- 单项目打包
- 整套项目打包
- 新开源技术
- 环境一致性
- 持续集成
- 微服务
- 弹性伸缩
Docker常用命令
命令
命令 | 含义 | 案例 |
---|---|---|
run | 从镜像运行一个容器 | docker run ubuntu /bin/echo 'hello-world' |
ls | 列出容器 | docker container ls |
inspect | 显示一个或多个容器详细信息 | docker inspect |
stats | 显示容器资源使用统计 | docker container stats |
top | 显示一个容器运行的进程 | docker container top |
update | 更新一个或多个容器配置 | docker update -m 500m --memory-swap -1 6d1a25f95132 |
port | 列出指定的容器的端口映射 | docker run -d -p 8080:80 nginx docker container port containerID |
ps | 查看当前运行的容器 | docker ps -a -l |
kill [containerId] | 终止容器(发送SIGKILL ) | docker kill [containerId] |
rm [containerId] | 删除容器 | docker rm [containerId] |
start [containerId] | 启动已经生成、已经停止运行的容器文件 | docker start [containerId] |
stop [containerId] | 终止容器运行 (发送 SIGTERM ) | docker stop [containerId] docker container stop $(docker container ps -aq) |
logs [containerId] | 查看 docker 容器的输出 | docker logs [containerId] |
exec [containerId] | 进入一个正在运行的 docker 容器执行命令 | docker container exec -it f6a53629488b /bin/bash |
cp [containerId] | 从正在运行的 Docker 容器里面,将文件拷贝到本机 | docker container cp f6a53629488b:/root/root.txt . |
commit [containerId] | 根据一个现有容器创建一个新的镜像 | docker commit -a "xxx" -m "mynginx" a404c6c174a2 mynginx:v1 |
启动容器
-
-t = --interactive 在新容器内指定一个伪终端或终端。
- -i = --tty 允许你对容器内的标准输入 (STDIN) 进行交互。
- -d = --detach 后台运行
docker run -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done"
- -e --env list 设置环境变量
docker run -d -p 1010:80 -e username="xxx" nginx docker container exec -it 3695dc5b9c2d /bin/bash
- -p --publish list 发布容器端口到主机
#运行交互式的容器
docker run -i -t ubuntu /bin/bash
#后台运行容器
docker run --detach centos ping www.baidu.com
docker ps
docker logs --follow ad04d9acde94 #follow:实时更新日志
docker stop ad04d9acde94
删除容器
- docker rm 删除容器
- docker rmi 删除镜像
- docker rm $(docker ps -a -q) 删除所有容器
docker start [containerId] #启动容器
docker stop [containerId] #停止容器
docker container -exec -it [containerID] /bin/bash #进入一个正在运行中的容器
docker container cp [containerID] /readme.md . #拷贝文件
docker run --rm ubuntu /bin/bash #自动删除
编写Dockerfile
- -t --tag list 镜像名称
- -f --file string 指定Dockerfile文件的位置
指令 | 含义 | 示例 |
---|---|---|
FROM | 构建的新镜像是基于哪个镜像 | FROM centos:6 |
MAINTAINER | 镜像维护者姓名或邮箱地址 | MAINTAINER xxx |
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 123456 |
ADD | 拷贝文件或目录到镜像中,如果是URL或者压缩包会自动下载和解压 | ADD ,ADD https://xxx.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 xxx |
WORKDIR | 为RUN CMD ENTRYPOINT COPY ADD 设置工作目录 | WORKDIR /data |
HEALTHCHECK | 健康检查 | HEALTHCHECK --interval=5m --timeout=3s --retries=3 CMS curl -f htp://localhost |
ARG | 在构建镜像时指定一些参数 | ARG user |
- cmd给出的是一个容器的默认的可执行体。也就是容器启动以后,默认的执行的命令。重点就是这个"默认"。意味着,如果
docker run
没有指定任何的执行命令或者dockerfile
里面也没有entrypoint
,那么,就会使用cmd指定的默认的执行命令执行。同时也从侧面说明了entrypoint
的含义,它才是真正的容器启动以后要执行命令
.dockerignore
表示要排除,不要打包到image中的文件路径
.git
node_modules
数据盘
- 删除容器的时候,容器层里创建的文件也会被删除掉,如果有些数据你想永久保存,比如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 xxxx #删除数据盘
docker volume ls #列出数据盘
Bind mounts
- 此方式与Linux系统的mount方式很相似,即是会覆盖容器内已存在的目录或文件,但并不会改变容器内原有的文件,当umount后容器内原有的文件就会还原
- 创建容器的时候我们可以通过
-v
或--volumn
给它指定一下数据盘 bind mounts
可以存储在宿主机系统的任意位置- 如果源文件/目录不存在,不会自动创建,会抛出一个错误
- 如果挂载目标在容器中非空目录,则该目录现有内容将被隐藏
默认数据盘
- -v 参数两种挂载数据方式都可以用
docker run -v /mnt:/mnt -it --name logs centos bash
cd /mnt
echo 1 > 1.txt
exit
docker inspect logs
"Mounts": [
{
"Source":"/mnt/sda1/var/lib/docker/volumes/dea6a8b3aefafa907d883895bbf931a502a51959f83d63b7ece8d7814cf5d489/_data",
"Destination": "/mnt",
}
]
Source
的值就是我们给容器指定的数据盘在主机上的位置Destination
的值是这个数据盘在容器上的位置
指定数据盘
mkdir ~/data
docker run -v ~/data:/mnt -it --name logs2 centos bash
cd /mnt
echo 3 > 3.txt
exit
cat ~/data/3.txt
- ~/data:/mnt 把当前用户目录中的
data
目录映射到/mnt
上
指定数据盘容器
- docker create [OPTIONS] IMAGE [COMMAND] [ARG...] 创建一个新的容器但不启动
docker create -v /mnt:/mnt --name logger centos
docker run --volumes-from logger --name logger3 -i -t centos bash
cd /mnt
touch logger3
docker run --volumes-from logger --name logger4 -i -t centos bash
cd /mnt
touch logger4
网络
- 安装Docker时,它会自动创建三个网络,bridge(创建容器默认连接到此网络)、 none 、host
- None:该模式关闭了容器的网络功能,对外界完全隔离
- host:容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。
- bridge 桥接网络,此模式会为每一个容器分配IP
- 可以使用该
--network
标志来指定容器应连接到哪些网络
bridge(桥接)
- bridge网络代表所有Docker安装中存在的网络
- 除非你使用该
docker run --network=<NETWORK>
选项指定,否则Docker守护程序默认将容器连接到此网络 - bridge模式使用
--net=bridge
指定,默认设置
docker network ls #列出当前的网络
docker inspect bridge #查看当前的桥连网络
docker run -d --name nginx1 nginx
docker run -d --name nginx2 --link nginx1 nginx
docker exec -it nginx2 bash
apt update
apt install -y inetutils-ping #ping
apt install -y dnsutils #nslookup
apt install -y net-tools #ifconfig
apt install -y iproute2 #ip
apt install -y curl #curl
cat /etc/hosts
ping nginx1
none
- none模式使用
--net=none
指定
# --net 指定无网络
docker run -d --name nginx_none --net none nginx
docker inspect none
docker exec -it nginx_none bash
ip addr
host
- host模式使用
--net=host
指定
docker run -d --name nginx_host --net host nginx
docker inspect host
docker exec -it nginx_host bash
ip addr
端口映射
# 查看镜像里暴露出的端口号
docker image inspect nginx
"ExposedPorts": {"80/tcp": {}}
# 让宿主机的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 port random_nginx
docker run -d --name randomall_nginx --publish-all nginx
docker run -d --name randomall_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 exec -it mynginx2 bash
ping mynginx1
连接到指定网络
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
,Compose 将启动并运行整个应用程序 配置文件组成 - services 可以定义需要的服务,每个服务都有自己的名字、使用的镜像、挂载的数据卷所属的网络和依赖的其它服务
- networks 是应用的网络,在它下面可以定义使用的网络名称,类性
- 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会创建默认的网络
命令 | 服务 |
---|---|
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 network ls | 查看网络网络不会删除 |
docker-compose down | 删除所有的网络和容器 |
删除所有的容器 docker container rm
docker container ps -a -q
网络互ping
docker-compose up -d
docker-compose exec nginx1 bash
apt update && apt install -y inetutils-ping
#可以通过服务的名字连接到对方
ping nginx2
配置数据卷
- networks 指定自定义网络
- volumes 指定数据卷
- 数据卷在宿主机的位置
/var/lib/docker/volumes/nginx-compose_data/_data
version: '3'
services:
nginx1:
image: nginx
ports:
- "8081:80"
networks:
- "newweb"
volumes:
- "data:/data"
- "./nginx1:/usr/share/nginx/html"
nginx2:
image: nginx
ports:
- "8082:80"
networks:
- "default"
volumes:
- "data:/data"
- "./nginx2:/usr/share/nginx/html"
nginx3:
image: nginx
ports:
- "8083:80"
networks:
- "default"
- "newweb"
volumes:
- "data:/data"
- "./nginx3:/usr/share/nginx/html"
networks:
newweb:
driver: bridge
volumes:
data:
driver: local
docker exec nginx-compose_nginx1_1 bash
cd /data
touch 1.txt
exit
cd /var/lib/docker/volumes/nginx-compose_data/_data
ls
node项目
- nodeapp 是一个用 Docker 搭建的本地 Node.js 应用开发与运行环境。
服务分类
- db:使用
mariadb
作为应用的数据库 - node:启动
node
服务 - web:使用
nginx
作为应用的 web 服务器
app目录结构
文件 | 说明 |
---|---|
docker-compose.yml | 定义本地开发环境需要的服务 |
images/nginx/config/default.conf | nginx 配置文件 |
images/node/Dockerfile | node的Dockfile配置文件 |
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/conf.d
- ./images/node/web/public:/public
depends_on:
- node
db:
image: mariadb
environment:
MYSQL_ROOT_PASSWORD: "root"
MYSQL_DATABASE: "node"
MYSQL_USER: "zfpx"
MYSQL_PASSWORD: "123456"
volumes:
- db:/var/lib/mysql
volumes:
db:
driver: local
images/node/web/server.js
let http=require('http');
var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'db',
user : 'zfpx',
password : '123456',
database : 'node'
});
connection.connect();
let server=http.createServer(function (req,res) {
connection.query('SELECT 2 + 2 AS solution', function (error, results, fields) {
if (error) throw error;
res.end(''+results[0].solution);
});
});
server.listen(3000);
images/node/web/package.json
{
"scripts": {
"start": "node server.js"
},
"dependencies": {
"mysql": "^2.16.0"
}
images/node/Dockerfile
FROM node
MAINTAINER xxx<[email protected]>
COPY ./web /web
WORKDIR /web
RUN npm install
CMD npm start
images/nginx/config/default.conf
upstream backend {
server node:3000;
}
server {
listen 80;
server_name localhost;
root /public;
index index.html index.htm;
location /api {
proxy_pass http://backend;
}
}
搭建LNMP网站
关闭防火墙
功能 | 命令 |
---|---|
停止防火墙 | systemctl stop firewalld.service |
永久关闭防火墙 | systemctl disable firewalld.service |
# 关闭防火墙之后docker需要重启
/bin/systemctl restart docker.service
创建自定义网络
docker network create lnmp
创建mysql数据库容器
docker run -itd --name lnmp_mysql --net lnmp -p 3306:3306 --mount src=mysql-vol,dst=/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 mysql:5.6 --character-set-server=utf8
创建数据库
docker exec lnmp_mysql bash -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e"create database wordpress"'
mysql -h127.0.0.1 -uroot -p123456
创建nginx+PHP容器
mkdir -p /app/wwwroot
docker run -itd --name lnmp_web --net lnmp -p 8888:80 --mount type=bind,src=/app/wwwroot,dst=/var/www/html richarvey/nginx-php-fpm
https://blog.csdn.net/deng624796905/article/details/86493330