Docker入门笔记(1)
容器技术入门
之前我的WIT问卷管理系统在阿里云上部署需要好多配置,各个环境耦合的比较紧密,花了不少时间去做部署和调配。
现在有了Docker以后,我们可以把各种组件配置好,然后打包成镜像使用Docker直接一键部署,实现开箱即用。
Docker部署
这里使用阿里云ECS作为Linux终端进行部署,系统为Ubuntu22.04。
- 安装工具
sudo apt-get install ca-certificates curl gnupg lsb-release
- 安装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
- 将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
- 再次更新apt
- 安装Docker CE版本
- 将当前用户添加到Docker用户组
[案例]使用Docker部署Nginx
直接执行
sudo docker run -d -p 80:80 nginx
如果没有配置过安全组,记得在阿里云后台的安全组配置里开启HTTP的80接口访问权限,不然外网会访问失败
轻松地部署好了Docker
从虚拟机到Docker
一般来说,服务器具有远超家用PC的资源,比如说CPU核心数量,网络带宽,内存大小等,为了让这些资源更高效的利用,我们就需要用到虚拟机。
我们可以通过虚拟化技术把物理硬件变成可以按需分配给每一台虚拟机,然后在虚拟机上运行我们编写的服务程序。
如果我们要部署一个大型项目,为了安全稳定和高效,就需要部署服务器集群,如果我们采用传统的部署方式,需要逐台调试每台虚拟机的环境,最后还需要联调,这会耗费我们大量的时间精力。
之前上线WIT问卷系统到阿里云时,由于是第一次部署项目上云,需要配置很多环境,比如说mysql,java,redis,rabbitmq,nginx的环境全都需要逐一配置,非常繁琐
随着云服务的发展繁荣,容器技术走上历史舞台,Docker应运而生。
Docker的集装箱是一只驮着集装箱的鲸鱼,它也表现了Docker一个最大的特性,每个集装箱其实是一个程序的环境+应用程序,他们可以基于Docker提供的环境在多平台上平稳运行,无需额外配置环境
我们可以通过Docker将一个组件打包成一个轻量级、可移植、自包含的容器,其可以运行在几乎所有的OS上
Docker工作机制简述
Docker主要分为三个部分
- Docker客户端:我们之前的docker指令是在客户端上执行的,客户端会把操作发送到服务端处理
- Docker服务端:服务器是启动容器的主体,一般也是作为服务在后台运行,其支持远程连接
- Register仓库:其可以存放Docker镜像,都看Docker了应该没有人没学过Maven吧,和maven一样,Docker也分为公有和私有,当需要时,Docker会从远程仓库下载Docker镜像到本地运行
当我们需要部署一个已经打包好的应用和环境,只需要通过docker下载已经打包好的镜像即可
我们之前输入sudo docker run -d -p 80:80 nginx
后,Docker其实执行了以下操作
- Docker客户端将操作发送给服务端
- Docker服务端在本地仓库查找对应镜像,若没有,则从服务器上下载到本地
- 加载本地的镜像,启动容器开始运行(容器之间,容器与外部之间都是互相隔离的,互不影响)
一句话,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镜像的机制
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>
我们在基于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镜像了
可以看见装了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镜像有缓存机制,就算现在中途退出了,然后重新build一遍,Docker也会将之前构建好的镜像拿来用,需要注意的是,底层发生变化会导致其所有上层的缓存失效)
构建好了我们就可以在本地镜像仓库看见它了
因为实际山执行的步骤和手动设置是一样的,所以他们打包的大小也几乎一致
但是我们可以使用histry
来查看它的构建历史,它保存了我们刚才执行的操作和之前他人的操作
而我们手动通过commit
来创建的镜像则没有保存我们刚才手动提交的记录
发布远程镜像到仓库
Docker这玩意某种意义上是不是跟Git很像?Git有GitHub,那我们Docker自然也得安排上DockerHub
首先在DockerHub官网上注册一个账号,然后创建一个仓库,创建过程不多赘述了,都学Docker了应该都用过github吧,大差不差的
这里我们创建好Docker仓库后,就可以上传本地的镜像了,最好把名称改的规范一点,这里使用tag
来打标签
docker tag ubuntu-java-file:latest 用户名/仓库名称:版本
接着我们需要登录一下docker
docker login user -u <用户名>
登录成功后就可以上传了
docker push <镜像名>:版本
阿里云的小水管上传了好久。。。
最后上传好了就可以在之前创建的DockerHub仓库中看见了
刚才创建的仓库如果是公开仓库的话就可以被随意的下载,这里就可以先删除本地的镜像,然后从我们刚才创建的仓库中拉取镜像
先搜索一下仓库里存不存在这个镜像
docker search hantou/hantou_repo
接下来选择我们要下载的
docker pull hantou/hantou_repo:1.0
由于镜像在上传后会被压缩,所以拉取下来的镜像比我们实际上传的600多m要小不少
这就是大概的DockerHub的基本操作
实战:使用IDEA构建SpringBoot程序的镜像
这里就以我之前写的WIT问卷管理系统为例子吧。
首先在之前写好的SpringBoot项目中新建一个DockerFile
在Dockerfile中写入我们的base镜像,之前我已经创建好了并且传到docker仓库了,这里就直接拉取
接下来需要连接我们虚拟机/云服务器下的docker服务
这里我用的是阿里云
这里需要配置Docker的服务器,也就是我们在Ubuntu服务器安装的Docker,这里我们填写服务器相关信息,我们首选需要去修改一下Docker的一些配置,开启远程客户端访问:
sudo vim /etc/systemd/system/multi-user.target.wants/docker.service
打开后,添加高亮部分:
修改完成后,重启Docker服务
sudo systemctl daemon-reload
sudo systemctl restart docker.service
由于我们用的是云服务器,需要在后台开放2375 TCP连接端口
现在接着在IDEA中进行配置:
先在edit中添加一个服务器链接
新增一个server,选择tcp链接,输入tcp://IP:2375
即可
添加好以后我们接着对Springboot包用maven打包
打包完成后我们将构建好的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 服务器中看到我们刚才传上来的新镜像了
在镜像中可以看见我们刚才添加Springboot的操作
接着在IDEA中启动镜像
点击启动即可,这里有点小翻车,因为我的项目用的是jdk17,而我们得镜像里只安装了jdk8
只需要把之前添加jdk的操作版本换成17即可
更换后再次启动,就发现成功运行在docker里了,不过我这个项目还用到了Redis,Mysql和RabbitMQ,那些还没有在base镜像里配置,所以功能还无法完全实现,但是总而言之确实在docker里跑起来了
我们尝试访问一下8080端口,会发现无法访问,这是因为docker容器内部和外部的网络是隔离的,如果我们想要访问容器的服务器,就需要将对应端口绑定在宿主机上,同时让宿主机也开放这个端口,这样才能实现容器内的网络连接
docker run -p 8080:8080 -d wit-questionnaire:1.1
配置完成后,点击重新创建容器
这下端口映射成功了,这里再次强调,如果是云服务器,需要去安全组中开启对应的端口
为了方便以后使用,我们还是可以把它推送到DockerHub去
直接在IDEA上操作
填写之前Dockerhub仓库的对应信息,点击ok就可以push到远程仓库了