随着分布式和微服务的概念越来越火爆,Docker--容器技术的运用也越来越广泛了。利用容器技术,我们可以忽略物理机上程序代码运行环境的差异,更快速高效的部署服务。利用K8s来编排容器,可以更高效的管理和运维服务。
什么是Docker
在物理机时代,我们的服务是直接部署在物理机上的。每次部署服务前,需要先对物理机的环境进行配置,保证操作系统
,环境变量
,相关依赖
都是统一且可用的,这就造成了运维极大的重复工作量,并且很容易出错(不能保证各个物理机的环境完全相同,可能在开发机器上完美运行,在线上机器就出bug了)。
同时,同一个物理机上如果部署了多个服务,服务直接会互相影响,抢占资源等。
随着虚拟化技术的成熟,我们可以在一台物理机上安装多个虚拟机
,每个虚拟机都有自己的操作系统,固定分配了从物理机分配来的资源,虚拟机之间相互隔离。
通过虚拟机,我们可以还原服务运行的原始环境,保证相同服务运行在一个相对一致的运行环境中(通过统一镜像),也可以隔离不同服务之间的干扰。但虚拟机技术太过于臃肿:
- 每台虚拟机需要独占一定物理机资源,在使用的时候其他虚拟机不可使用
- 虚拟机拥有完整的一套操作系统,整个启动时间和步骤较长
因此一般也不会用这种方式部署服务(双系统什么的会采用这种方式)
针对虚拟机这些缺点,就又发展出了容器化技术。
容器并不模拟一个完整的操作系统,而是对进程进行隔离。在正常进程的外面套了一个保护层。对于容器里面的进程来说,它接触到的各种资源都是虚拟的,从而实现与底层系统的隔离。
相比起虚拟机,容器有以下优点
- 启动快,相当于启动一个进程
- 占用资源小,只占用进程需要的资源,虚拟机操作系统需要占用较多资源用于分配
- 体积小,容器只需要将需要的文件(环境,配置,进程)打包起来,相比起打包操作系统小的多
而 Docker 在容器的基础上,进行了进一步的封装,从文件系统、网络互联到进程隔离等等,极大的简化了容器的创建和维护。使得 Docker
技术比虚拟机技术更为轻便、快捷。
Docker 将应用程序与该程序的依赖,打包在一个文件里面。运行这个文件,就会生成一个虚拟容器。程序在这个虚拟容器里运行,就好像在真实的物理机上运行一样。有了 Docker,就不用担心环境问题。
使用Docker,可以保证程序运行的环境一致,并且有利于程序的持续交付和部署(对运维而言,一套配置可以到处使用,减轻了工作量同时也减少出问题的几率,更新也只需要替换容器文件)
总结一下,使用Docker的好处主要有:
(1)提供一次性的环境。 比如,本地测试他人的软件、持续集成的时候提供单元测试和构建的环境。
(2)提供弹性的云服务。 因为 Docker 容器可以随开随关,很适合动态扩容和缩容。
(3)组建微服务架构。 通过多个容器,一台机器可以跑多个服务,因此在本机就可以模拟出微服务架构。
Docker的三个基本概念
镜像:
在虚拟机中我们也接触过镜像的概念,安装虚拟机时使用的ISO镜像文件,包含操作系统完整的文件系统以及一系列以依赖和组件。
Docker 镜像 是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像 不包含 任何动态数据,其内容在构建之后也不会被改变。
容器:
容器是镜像运行时的实体,一个镜像可以对应多个容器。
容器可以被创建、启动、停止、删除、暂停等。
容器的实质是进程
,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的命名空间。因此容器可以拥有自己的 root
文件系统、自己的网络配置
、自己的进程空间
,甚至自己的用户 ID 空间
。
容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。也因为这种隔离的特性,很多人初学 Docker 时常常会混淆容器和虚拟机。
在容器使用时,容器存储层要保持无状态化。所有的文件写入操作,都应该使用数据卷(Volume)或者 绑定宿主目录,在这些位置的读写直接对宿主(或网络存储)发生读写,其性能和稳定性更高(即把容器的文件或文件夹映射到宿主机的文件或文件夹,读写都会读写到宿主机上)。
数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡(在宿主机上)。因此,使用数据卷后,容器删除或者重新运行之后,数据却不会丢失。
Ps.需要显示绑定,命令行或docker-compose 文件中要写明数据卷等信息
仓库:
镜像构建完成后,可以很容易的在当前宿主机上运行,但是,如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,仓库(Docker Registry)就是这样的服务。
最常使用的仓库是官方的 Docker Hub,这也是默认的 Registry,并拥有大量的高质量的官方镜像。除此以外,还有 Red Hat 的 Quay.io;Google 的 Google Container Registry,Kubernetes 的镜像使用的就是这个服务;代码托管平台 GitHub 推出的 ghcr.io。
Ps.类似我们使用的github,需要什么镜像就上去拉对应需要的版本,拉到本地后再构建容器。由于网络原因,可能连不上官方仓库,因此有可能需要一些加速器或者国内的云服务商提供类似于 Docker Hub 的公开服务。比如 网易云镜像服务、DaoCloud 镜像市场、阿里云镜像库 等
Docker 安装
推荐直接到官方下载docker桌面程序:docs.docker.com/desktop/ins…
下载成功后你就获得了这只小鲸鱼。通过docker可视化界面可以更简单的查看镜像和容器,容易上手。
也可以通过命令行查看和验证是否安装成功
docker --version
基本命令
1、Docker 最基本的操作是获取镜像,到仓库获取镜像的命令为:
docker pull [OPTIONS] NAME[:TAG|@DIGEST]
比如我们要拉取一个私有仓库的java镜像,ip:port可以不加,则去默认仓库拉,8是tag,指定版本
docker pull ip:port/java:8
还可以添加参数获取指定平台的镜像(arm的,intel的等)
2、 运行镜像并生成一个容器
docker pull ubuntu:18.04
docker run -it --rm ubuntu:18.04 bash
比如我们要启动一个ubuntu系统的镜像
-
-it
:这是两个参数,一个是-i
:交互式操作,一个是-t
终端。我们这里打算进入bash
执行一些命令并查看返回结果,因此我们需要交互式终端。 -
--rm
:这个参数是说容器退出后随之将其删除。默认情况下,为了排障需求,退出的容器并不会立即删除,除非手动docker rm
。我们这里只是随便执行个命令,看看结果,不需要排障和保留结果,因此使用--rm
可以避免浪费空间。 -
ubuntu:18.04
:这是指用ubuntu:18.04
镜像为基础来启动容器。 -
bash
:放在镜像名后的是 命令,这里我们希望有个交互式 Shell,因此用的是bash
。
3、 列出当前的所有镜像
docker image ls
4、删除镜像
docker image rm id
5、在不使用卷的情况下,想要将容器内的改动保存成新的镜像
docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]]
Ps.
6、Docker file构造镜像
镜像的定制实际上就是定制镜像每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是 Dockerfile。
比如我们想定制一个nginx镜像
mkdir mynginx
cd mynginx
touch Dockerfile
vim Dockerfile 并添加下面两行
FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
然后使用docker file 构建镜像
docker build -t nginx:v3 .
可以看到,build还支持直接从url上构建镜像
DockerFile更详细的介绍可以参考yeasy.gitbook.io/docker_prac…
7、进入运行中的容器
docker ps 查看运行中的容器,选择要进入的容器
docker exec 可以在运行中的容器执行命令
docker exec -it +containerId bash 可以进入容器,执行bash
数据存储
Docker 的数据存储/持久化分为三类
- Volume
- Bind mount
- tmpfs
Volume 是数据卷挂载,在宿主的文件系统上的docker工作路径下创建一个文件夹(/var/lib/docker/volumes
)来存储数据,其他非docker进程是不能修改该路径下的文件,完全由docker来管理。
在执行docker run时没有指定数据卷名称,docker会默认创建一个匿名数据卷,用于数据存储。 比如你创建了一个mysql数据库容器,做了一些操作,然后删除容器。重新新建容器时如果显示挂载这个数据卷,那么数据仍然存在。
docker run -d --name mysql02 -v volume_name:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 mysql
bind mounts 是挂载到宿主机器任意地方,依赖宿主机器的目录结构,不能通过docker CLI 去直接管理,并且非docker进程和docker进程都可以修改该路径下的文件。
通过-v 或者 --mount方式都可以
docker run -d --name tomcat-bind --mount type=bind,source=/tmp,target=/user/local tomcat
tmpfs:无论是在Docker主机上还是在容器内,tmpfs挂载都不会持久保存在磁盘上,它会将信息存储在宿主机器内存里。 容器在其生存期内可以使用它来存储非持久状态或敏感信息。
docker run -d --name tomcat-tmpfs --mount type=tmpfs,target=/tmp tomcat
网络配置
Docker 之间是相互隔离的,但有时候希望各个容器之间可以互相访问,这时候可以通过配置网络解决。
Docker有四种网络类型
- None
即没有网络,Docker容器不会设置容器内网络的任何信息,不会对网络进行任何配置
- Bridge
Bridge类型的网络是Docker容器默认的网络类型,在这种模式下,Docker会为容器虚拟出一个网络,所有的Container容器都会分配一个处于这个网络的IP地址,不同的Container之间可以互相通信。
(类似一个局域网?在一个bridge的容器之间可以互相通信,通过NAT和外界通信)
- Container
Container类型的网络中,多个Docker容器共享网络设备。我们在一个Docker容器运行之后,再运行其他的Docker容器时,可以使该容器与之前已经运行的Docker容器共享网络,即拥有同样的IP地址、网卡设备。两个容器之间可以通过环回地址网卡进行通信,并且在文件系统、进程表等方面实现隔离。处于同一个Container网络中的容器,对于端口的占用机制是先来先占用的模式,哪个容器占用该端口,该容器就可以使用该端口。
(K8s中就是多个pod共享一个node网络)
- Host
与Container类型的网络类似,在Host类型的网络中,Docker容器直接使用物理机网络,拥有物理机的IP地址和网卡信息。同样的,在Host类型的网络模式中,Docker容器与物理机在文件系统、进程等方面是隔离的。
使用host模式的容器可以直接使用宿主机的IP地址与外界通信,容器内部的服务端口也可以使用宿主机的端口,不需要进行NAT,host最大的优势就是网络性能比较好,但是docker host上已经使用的端口就不能再用了,网络的隔离性不好。
例如,假如一个开启Web80端口服务的Docker容器处于Host类型的网络中(前提是该物理机没有先占用80端口),那么访问该容器只需要访问物理机IP地址即可。
Docker Compose
Docker Compose
是 Docker 官方编排(Orchestration)项目之一,负责快速的部署分布式应用。
前面使用 Dockerfile
模板文件,可以让用户很方便的定义一个单独的应用容器。然而,在日常工作中,经常会碰到需要多个容器相互配合来完成某项任务的情况。例如要实现一个 Web 项目,除了 Web 服务容器本身,往往还需要再加上后端的数据库服务容器,甚至还包括负载均衡容器等。
Compose
恰好满足了这样的需求。它允许用户通过一个单独的 docker-compose.yml
模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project)。
运行项目
docker-compose up
规则参考:yeasy.gitbook.io/docker_prac…