Docker 学习笔记 持续更新~

141 阅读12分钟

随着分布式和微服务的概念越来越火爆,Docker--容器技术的运用也越来越广泛了。利用容器技术,我们可以忽略物理机上程序代码运行环境的差异,更快速高效的部署服务。利用K8s来编排容器,可以更高效的管理和运维服务。

什么是Docker

在物理机时代,我们的服务是直接部署在物理机上的。每次部署服务前,需要先对物理机的环境进行配置,保证操作系统环境变量,相关依赖都是统一且可用的,这就造成了运维极大的重复工作量,并且很容易出错(不能保证各个物理机的环境完全相同,可能在开发机器上完美运行,在线上机器就出bug了)。

同时,同一个物理机上如果部署了多个服务,服务直接会互相影响,抢占资源等。

image.png

随着虚拟化技术的成熟,我们可以在一台物理机上安装多个虚拟机,每个虚拟机都有自己的操作系统,固定分配了从物理机分配来的资源,虚拟机之间相互隔离。

通过虚拟机,我们可以还原服务运行的原始环境,保证相同服务运行在一个相对一致的运行环境中(通过统一镜像),也可以隔离不同服务之间的干扰。但虚拟机技术太过于臃肿:

  • 每台虚拟机需要独占一定物理机资源,在使用的时候其他虚拟机不可使用
  • 虚拟机拥有完整的一套操作系统,整个启动时间和步骤较长

因此一般也不会用这种方式部署服务(双系统什么的会采用这种方式)

image.png

针对虚拟机这些缺点,就又发展出了容器化技术。

image.png

容器并不模拟一个完整的操作系统,而是对进程进行隔离。在正常进程的外面套了一个保护层。对于容器里面的进程来说,它接触到的各种资源都是虚拟的,从而实现与底层系统的隔离。

相比起虚拟机,容器有以下优点

  • 启动快,相当于启动一个进程
  • 占用资源小,只占用进程需要的资源,虚拟机操作系统需要占用较多资源用于分配
  • 体积小,容器只需要将需要的文件(环境,配置,进程)打包起来,相比起打包操作系统小的多

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 RegistryKubernetes 的镜像使用的就是这个服务;代码托管平台 GitHub 推出的 ghcr.io

Ps.类似我们使用的github,需要什么镜像就上去拉对应需要的版本,拉到本地后再构建容器。由于网络原因,可能连不上官方仓库,因此有可能需要一些加速器或者国内的云服务商提供类似于 Docker Hub 的公开服务。比如 网易云镜像服务DaoCloud 镜像市场阿里云镜像库

Docker 安装

推荐直接到官方下载docker桌面程序:docs.docker.com/desktop/ins…

image.png

下载成功后你就获得了这只小鲸鱼。通过docker可视化界面可以更简单的查看镜像和容器,容易上手。

image.png

也可以通过命令行查看和验证是否安装成功

 docker --version

image.png

基本命令

1、Docker 最基本的操作是获取镜像,到仓库获取镜像的命令为:

docker pull [OPTIONS] NAME[:TAG|@DIGEST]

比如我们要拉取一个私有仓库的java镜像,ip:port可以不加,则去默认仓库拉,8是tag,指定版本

docker pull ip:port/java:8

image.png

还可以添加参数获取指定平台的镜像(arm的,intel的等)

2、 运行镜像并生成一个容器

docker pull ubuntu:18.04
docker run -it --rm ubuntu:18.04 bash

image.png 比如我们要启动一个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

image.png

5、在不使用卷的情况下,想要将容器内的改动保存成新的镜像

docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]]

Ps.

image.png

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 .     

image.png 可以看到,build还支持直接从url上构建镜像

DockerFile更详细的介绍可以参考yeasy.gitbook.io/docker_prac…

7、进入运行中的容器

docker ps 查看运行中的容器,选择要进入的容器
docker exec 可以在运行中的容器执行命令
docker exec -it +containerId bash 可以进入容器,执行bash

image.png

image.png

image.png

数据存储

Docker 的数据存储/持久化分为三类

  • Volume
  • Bind mount
  • tmpfs

image.png

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

image.png

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

参考文章:juejin.cn/post/684490…

网络配置

Docker 之间是相互隔离的,但有时候希望各个容器之间可以互相访问,这时候可以通过配置网络解决。

Docker有四种网络类型

  • None

即没有网络,Docker容器不会设置容器内网络的任何信息,不会对网络进行任何配置

  • Bridge

Bridge类型的网络是Docker容器默认的网络类型,在这种模式下,Docker会为容器虚拟出一个网络,所有的Container容器都会分配一个处于这个网络的IP地址,不同的Container之间可以互相通信。

(类似一个局域网?在一个bridge的容器之间可以互相通信,通过NAT和外界通信)

image.png

image.png

  • Container

Container类型的网络中,多个Docker容器共享网络设备。我们在一个Docker容器运行之后,再运行其他的Docker容器时,可以使该容器与之前已经运行的Docker容器共享网络,即拥有同样的IP地址、网卡设备。两个容器之间可以通过环回地址网卡进行通信,并且在文件系统、进程表等方面实现隔离。处于同一个Container网络中的容器,对于端口的占用机制是先来先占用的模式,哪个容器占用该端口,该容器就可以使用该端口。

(K8s中就是多个pod共享一个node网络)

image.png

  • Host

与Container类型的网络类似,在Host类型的网络中,Docker容器直接使用物理机网络,拥有物理机的IP地址和网卡信息。同样的,在Host类型的网络模式中,Docker容器与物理机在文件系统、进程等方面是隔离的。

使用host模式的容器可以直接使用宿主机的IP地址与外界通信,容器内部的服务端口也可以使用宿主机的端口,不需要进行NAT,host最大的优势就是网络性能比较好,但是docker host上已经使用的端口就不能再用了,网络的隔离性不好。

例如,假如一个开启Web80端口服务的Docker容器处于Host类型的网络中(前提是该物理机没有先占用80端口),那么访问该容器只需要访问物理机IP地址即可。

image.png

Docker Compose

Docker Compose 是 Docker 官方编排(Orchestration)项目之一,负责快速的部署分布式应用。

前面使用 Dockerfile 模板文件,可以让用户很方便的定义一个单独的应用容器。然而,在日常工作中,经常会碰到需要多个容器相互配合来完成某项任务的情况。例如要实现一个 Web 项目,除了 Web 服务容器本身,往往还需要再加上后端的数据库服务容器,甚至还包括负载均衡容器等。

Compose 恰好满足了这样的需求。它允许用户通过一个单独的 docker-compose.yml 模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project)。

运行项目

docker-compose up

规则参考:yeasy.gitbook.io/docker_prac…

Reference

yeasy.gitbook.io/docker_prac…