Docker入门笔记(1)

容器技术入门

之前我的WIT问卷管理系统在阿里云上部署需要好多配置,各个环境耦合的比较紧密,花了不少时间去做部署和调配。

现在有了Docker以后,我们可以把各种组件配置好,然后打包成镜像使用Docker直接一键部署,实现开箱即用。

Docker部署

这里使用阿里云ECS作为Linux终端进行部署,系统为Ubuntu22.04。

  1. 安装工具
sudo apt-get install ca-certificates curl gnupg lsb-release
  1. 安装GPG Key
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
  1. 将Docker的库添加到apt资源列表
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
  1. 再次更新apt
  2. 安装Docker CE版本
  3. 将当前用户添加到Docker用户组
[案例]使用Docker部署Nginx

直接执行

sudo docker run -d -p 80:80 nginx

如果没有配置过安全组,记得在阿里云后台的安全组配置里开启HTTP的80接口访问权限,不然外网会访问失败

docker建议内存 docker 设置内存大小_docker建议内存

轻松地部署好了Docker

从虚拟机到Docker

一般来说,服务器具有远超家用PC的资源,比如说CPU核心数量,网络带宽,内存大小等,为了让这些资源更高效的利用,我们就需要用到虚拟机。

我们可以通过虚拟化技术把物理硬件变成可以按需分配给每一台虚拟机,然后在虚拟机上运行我们编写的服务程序。

如果我们要部署一个大型项目,为了安全稳定和高效,就需要部署服务器集群,如果我们采用传统的部署方式,需要逐台调试每台虚拟机的环境,最后还需要联调,这会耗费我们大量的时间精力。

之前上线WIT问卷系统到阿里云时,由于是第一次部署项目上云,需要配置很多环境,比如说mysql,java,redis,rabbitmq,nginx的环境全都需要逐一配置,非常繁琐

随着云服务的发展繁荣,容器技术走上历史舞台,Docker应运而生。

Docker的集装箱是一只驮着集装箱的鲸鱼,它也表现了Docker一个最大的特性,每个集装箱其实是一个程序的环境+应用程序,他们可以基于Docker提供的环境在多平台上平稳运行,无需额外配置环境

我们可以通过Docker将一个组件打包成一个轻量级、可移植、自包含的容器,其可以运行在几乎所有的OS上

Docker工作机制简述

docker建议内存 docker 设置内存大小_Docker_02

Docker主要分为三个部分

  • Docker客户端:我们之前的docker指令是在客户端上执行的,客户端会把操作发送到服务端处理
  • Docker服务端:服务器是启动容器的主体,一般也是作为服务在后台运行,其支持远程连接
  • Register仓库:其可以存放Docker镜像,都看Docker了应该没有人没学过Maven吧,和maven一样,Docker也分为公有和私有,当需要时,Docker会从远程仓库下载Docker镜像到本地运行

当我们需要部署一个已经打包好的应用和环境,只需要通过docker下载已经打包好的镜像即可

我们之前输入sudo docker run -d -p 80:80 nginx后,Docker其实执行了以下操作

  1. Docker客户端将操作发送给服务端
  2. Docker服务端在本地仓库查找对应镜像,若没有,则从服务器上下载到本地
  3. 加载本地的镜像,启动容器开始运行(容器之间,容器与外部之间都是互相隔离的,互不影响)

一句话,Docker开箱即用


容器与镜像

初识容器镜像

简单了解一下镜像的相关操作,以官方提供的hello-world为例子

docker pull hello-world

一个镜像的名称是有两个部分组成的,repository和tag,tag表示版本

docker pull 名称:版本

pull之后镜像会存放在本地,需要的话需要run

docker run hello-world

不想要立刻执行容器,则可以换成

docker create hello-world

启动成功以后,可以输入ps查看容器列表

docker ps -a

这里的-a是为了看到所有容器,若不加-a,则只会显示当前正在运行的容器

我们在对某个容器进行操作的时候,需要输入容器的id或者名称,如果我们采用默认的名称,会难以记录和区分,我们可以在run时添加--name参数来为容器命名

docker run --name=hantou hello-world

我们可以手动使用start来开启处于停止状态的容器(我们在run一个非持续性的程序时,它执行完了就结束了,就会被终止;比如说之前的hello-world)

docker start <容器名称/id>

如果想手动停止容器直接输入stop就行了

docker stop <容器名称/id>

重启容器

docker restart <容器名称/id>

删除容器(只有非运行状态的容器才能被删除)

docker rm <容器名称/id>

设置运行后自动删除容器

docker run --rm 镜像名称

删除镜像

docker rmi 镜像名称

查看本地镜像

docker images

镜像结构介绍

镜像是容器的基石,有了镜像才能创建容器实例,接下来我们就要浅浅研究一下镜像的基本结构

我们在打包项目的时候往往需要一个基本的环境,比如说数据库、缓存等等,这样我们才能基于这个系统来安装软件,这种基本的系统镜像我们称之为base镜像

一般的base镜像就是各个Linux系统的发行版,比如说Ubuntu、Centos等

我们还是以centos为例子

docker pull centos

这里解释下base镜像的机制

docker建议内存 docker 设置内存大小_docker_03

Linux操作体系由内核和用户空间组成,内核空间是Linux的核心,Linux启动后会先加载bootfs文件系统,然后在加载完毕后卸载bootfs,然后再加载用户空间的文件系统,而这一层正是我们可以操作的部分

  • bootfs包含了BootLoader和Linux内核,用户无权修改
  • rootfs则包含了我们常常操作的较为熟悉的linux目录结构

base镜像底层会直接调用宿主机的内核,也就是说,镜像与宿主机linux内核保持一致

可以使用uname指令来查看当前内核版本

我们来启动下刚才下载的centos的base镜像

docker run -it centos

这里的-it是开启一个标准的数据接口并且分配一个伪tty设备,其可以支持终端登录,如果不同时使用这两个的话,base容器会在启动后自动终止

我们输入exit就可以退出base容器了

再次启动时需要加入-i才能在前台启动

docker start -i <容器名称/id>

docker建议内存 docker 设置内存大小_docker_04

我们在基于base镜像的基础上,可以进行软件的安装与适配,我们可以一层一层的对镜像进行封装,这样的多层结构可以让软件环境自由的组合,让他们互相之间可以拼装使用。

可以看见图上的最上层有一层可写容器层,它有什么作用呢?

所有的镜像在组合后应该要叠加形成一个统一的文件系统,如果镜像之间的文件系统保持独立的话,那么一层一层的叠起来就很难操作

所以Docker为我们提供了可写容器层,我们可以通过修改读写层中的数据来访问下面镜像中的文件,各个操作如下

  • 文件读取:要读取一个文件,Docker会最上层往下依次寻找,找到后则打开文件。
  • 文件创建和修改:创建新文件会直接添加到可写容器层中,修改文件会从上往下依次寻找各个镜像中的文件,如果找到,则将其复制到可写容器层,再进行修改。
  • 删除文件:删除文件也会从上往下依次寻找各个镜像中的文件,一旦找到,并不会直接删除镜像中的文件,而是在可写容器层标记这个删除操作。

我们对容器内文件的操作,都是基于最上层的可写容器层来进行的,这样就可以保护镜像的完整性

构建镜像

我们可以手动的构建自己需要的镜像,构建镜像有两种方式

  • 使用commit命令来完成
  • 使用DockerFIle来完成

这里先看第一种

我们以Ubuntu镜像为例

首先从仓库中拉取ubuntu镜像

docker pull ubuntu

然后直接启动

docker run -it ubuntu

接着安装一下jdk8

apt install openjdk-8-jdk

完成安装后我们就可以使用commit来提交当前的容器,并保存为新的镜像

docker commit 容器名称/id 新的镜像名称

再查看一下镜像仓库,就可以看见我们刚才创建的带有jdk8的centos镜像了

docker建议内存 docker 设置内存大小_容器_05

可以看见装了jdk的镜像比原来的镜像大了好几倍,但我们可以直接通过这个镜像来启动一个带有java环境的容器了。

虽然但是,Docker官方并不推荐这样创建新镜像,因为这种容器就是一个黑箱了,使用者很难知道这个镜像内有什么,安全性低;同时要一个一个的构建这样的镜像也很费事。

下面使用Dockerfile的形式来创建一个同样的镜像

首先新建一个名为Dockerfile的文件

vim Dockerfile

然后在里面编写如下的指令来告诉Docker我们需要什么

FROM ubuntu
RUN apt update
RUN apt install -y openjdk-8-jdk

其实文件里的内容刚跟之前手动执行的差不多

我们保存文件后来告诉Docker要按照这个文件来创建镜像

docker build -t <镜像名称> <构建目录>

按回车,Docker就会执行我们的文件内的指令

docker建议内存 docker 设置内存大小_docker建议内存_06

执行中的每一步我们都可以很清晰直观的看见(Docker镜像有缓存机制,就算现在中途退出了,然后重新build一遍,Docker也会将之前构建好的镜像拿来用,需要注意的是,底层发生变化会导致其所有上层的缓存失效)

构建好了我们就可以在本地镜像仓库看见它了

docker建议内存 docker 设置内存大小_docker_07

因为实际山执行的步骤和手动设置是一样的,所以他们打包的大小也几乎一致

但是我们可以使用histry来查看它的构建历史,它保存了我们刚才执行的操作和之前他人的操作

docker建议内存 docker 设置内存大小_docker_08

而我们手动通过commit来创建的镜像则没有保存我们刚才手动提交的记录

docker建议内存 docker 设置内存大小_容器_09

发布远程镜像到仓库

Docker这玩意某种意义上是不是跟Git很像?Git有GitHub,那我们Docker自然也得安排上DockerHub

首先在DockerHub官网上注册一个账号,然后创建一个仓库,创建过程不多赘述了,都学Docker了应该都用过github吧,大差不差的

这里我们创建好Docker仓库后,就可以上传本地的镜像了,最好把名称改的规范一点,这里使用tag来打标签

docker tag ubuntu-java-file:latest 用户名/仓库名称:版本

docker建议内存 docker 设置内存大小_容器_10

接着我们需要登录一下docker

docker login user -u <用户名>

登录成功后就可以上传了

docker push <镜像名>:版本

阿里云的小水管上传了好久。。。

docker建议内存 docker 设置内存大小_docker_11

最后上传好了就可以在之前创建的DockerHub仓库中看见了

刚才创建的仓库如果是公开仓库的话就可以被随意的下载,这里就可以先删除本地的镜像,然后从我们刚才创建的仓库中拉取镜像

先搜索一下仓库里存不存在这个镜像

docker search hantou/hantou_repo

docker建议内存 docker 设置内存大小_docker_12

接下来选择我们要下载的

docker pull hantou/hantou_repo:1.0

由于镜像在上传后会被压缩,所以拉取下来的镜像比我们实际上传的600多m要小不少

这就是大概的DockerHub的基本操作


实战:使用IDEA构建SpringBoot程序的镜像

这里就以我之前写的WIT问卷管理系统为例子吧。

首先在之前写好的SpringBoot项目中新建一个DockerFile

在Dockerfile中写入我们的base镜像,之前我已经创建好了并且传到docker仓库了,这里就直接拉取

docker建议内存 docker 设置内存大小_笔记_13

接下来需要连接我们虚拟机/云服务器下的docker服务

这里我用的是阿里云

这里需要配置Docker的服务器,也就是我们在Ubuntu服务器安装的Docker,这里我们填写服务器相关信息,我们首选需要去修改一下Docker的一些配置,开启远程客户端访问:

sudo vim /etc/systemd/system/multi-user.target.wants/docker.service

打开后,添加高亮部分:

docker建议内存 docker 设置内存大小_笔记_14

修改完成后,重启Docker服务

sudo systemctl daemon-reload
sudo systemctl restart docker.service

由于我们用的是云服务器,需要在后台开放2375 TCP连接端口

docker建议内存 docker 设置内存大小_Docker_15

现在接着在IDEA中进行配置:

docker建议内存 docker 设置内存大小_笔记_16

先在edit中添加一个服务器链接

新增一个server,选择tcp链接,输入tcp://IP:2375即可

docker建议内存 docker 设置内存大小_容器_17

添加好以后我们接着对Springboot包用maven打包

docker建议内存 docker 设置内存大小_笔记_18

打包完成后我们将构建好的jar包导入Dockerfile

COPY target/my-project-backend-0.0.1-SNAPSHOT.jar app.jar

这里是使用copy指令将文件拷贝到docker的默认路径中

然后设置在容器启动时开启我们的java程序,这里通过CMD完成

CMD java -jar app.jar

CMD命令可以设定容器启动后执行的命令,EXPOSE可以指定容器需要暴露的端口,但是现在我们还没有学习网络相关的知识,所以暂时不使用,这里指定为我们启动Java项目的命令。配置完成后在左侧点击运行,如果出现Failed to deploy XXX Dockerfile: Dockerfile': Not connected to docker错误,在排除服务器未开放的情况下,可能是idea权限不足导致的,退出idea再以管理员身份重新运行即可

docker建议内存 docker 设置内存大小_docker建议内存_19

在构建成功后我们就可以在Docker 服务器中看到我们刚才传上来的新镜像了

docker建议内存 docker 设置内存大小_Docker_20

在镜像中可以看见我们刚才添加Springboot的操作

docker建议内存 docker 设置内存大小_docker建议内存_21

接着在IDEA中启动镜像

docker建议内存 docker 设置内存大小_docker建议内存_22

docker建议内存 docker 设置内存大小_容器_23

点击启动即可,这里有点小翻车,因为我的项目用的是jdk17,而我们得镜像里只安装了jdk8

docker建议内存 docker 设置内存大小_容器_24

只需要把之前添加jdk的操作版本换成17即可

docker建议内存 docker 设置内存大小_容器_25

更换后再次启动,就发现成功运行在docker里了,不过我这个项目还用到了Redis,Mysql和RabbitMQ,那些还没有在base镜像里配置,所以功能还无法完全实现,但是总而言之确实在docker里跑起来了

docker建议内存 docker 设置内存大小_笔记_26

我们尝试访问一下8080端口,会发现无法访问,这是因为docker容器内部和外部的网络是隔离的,如果我们想要访问容器的服务器,就需要将对应端口绑定在宿主机上,同时让宿主机也开放这个端口,这样才能实现容器内的网络连接

docker run -p 8080:8080 -d wit-questionnaire:1.1

配置完成后,点击重新创建容器

docker建议内存 docker 设置内存大小_docker建议内存_27

这下端口映射成功了,这里再次强调,如果是云服务器,需要去安全组中开启对应的端口

docker建议内存 docker 设置内存大小_docker建议内存_28

为了方便以后使用,我们还是可以把它推送到DockerHub去

直接在IDEA上操作

docker建议内存 docker 设置内存大小_docker建议内存_29

docker建议内存 docker 设置内存大小_容器_30

填写之前Dockerhub仓库的对应信息,点击ok就可以push到远程仓库了