作者:jakieli
一、为什么使用Docker
我们的业务需要使用公司内部的一个平台做报表展示,公司内部的一个平台支持的数据库都是正式环境或者IDC环境,这使得我们的业务逻辑也需要部署到正式环境或者IDC环境。自从自研资源上云后,鹅厂内部使用IDC资源时不再推荐使用方单独申请IDC资源,而是推荐使用容器服务进行资源的申请和使用。具体来说,就是我们需要把我们的业务先在测试环境上使用docker做成镜像,再把镜像上传到镜像仓库,再从容器服务中把我们的镜像给启动起来。因此,我们就有了使用docker的诉求。
二、Docker是什么
Docker是一个开源平台,它允许用户快速构建、测试和部署应用程序。Docker通过将软件及其依赖项打包到轻量级、可移植的容器中来实现这一目标。这些容器是独立的、可执行的软件包,它们包含了运行应用程序所需的所有内容,包括代码、运行时、库、环境变量和系统工具。
Docker的一个重要特点是它可以屏蔽环境差异。这意味着,只要应用程序及其依赖项被打包到Docker容器中,那么无论应用程序运行在哪个环境中,它的行为都是一致的。这消除了“在我的环境上可以运行”的问题,并实现了“build once, run everywhere”的目标。
Docker的另一个优势是快速部署。由于容器启动速度非常快,并且只要确保一个容器中的程序正确运行,就可以确信无论在生产环境部署多少个容器都能正确运行,因此Docker非常适合用于快速部署应用程序。
Docker的关键组件包括Dockerfile文件、Docker映像和Docker容器。Dockerfile文件是一个包含命令的脚本,用于组装Docker映像并定义其中的内容。Docker映像是一个只读的模板,用于创建Docker容器。Docker容器是从Docker映像启动的运行实例。
Docker被广泛使用,因为它简化了应用程序的部署过程,并为开发人员提供了可移植性、一致性和效率。Docker的创新性在于它将程序的所有依赖项一起打包,从而简化了运行环境管理的繁琐步骤。
三、怎么使用Docker
1. Docker的安装
在不同操作系统上,docker的安装命令不同,我们这次以Ubuntu系统为例来说明如何安装docker。下面的各个步骤都是来源于混元大语言模型的返回:
在Ubuntu系统上安装Docker,你可以按照以下步骤操作:
(1) 更新软件包索引:
sudo apt-get update
(2) 安装依赖项:
sudo apt-get install apt-transport-https ca-certificates curl software-properties-common
(3) 添加Docker官方GPG密钥:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
(4) 设置Docker稳定版本的存储库:
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
(5) 更新软件包索引:
sudo apt-get update
(6) 安装Docker:
sudo apt-get install docker-ce docker-ce-cli containerd.io
(7) 验证Docker是否已成功安装:
sudo docker --version
按照混元大模型给出来的步骤执行安装,问题出现在了第4-6步。环境中的lsb_release执行报错:
Traceback (most recent call last):
File "/usr/bin/lsb_release", line 25, in <module>
import lsb_release
ModuleNotFoundError: No module named 'lsb_release'
这个错误解决了半天也没有搞定,最后通过绕道而搞定。按照道理,第四步的目的是指定当前系统的稳定docker版本,这里可以通过下面的命令替代:
codename=$(cat /etc/os-release | grep "VERSION_CODENAME" | cut -d "=" -f 2)
把codename的值替换到第4步中的$(lsb_release -cs),再执行第6步就能成功了。如果还是执行失败的话,可以新建个文件:/etc/apt/sources.list.d/docker.list,并且把第4步的前半部分,即:
deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu bionic stable
写入到文件中,其中“bionic”就是前面提到的codename。注意,这个命令要写对,一定要以deb开头,在stable前一定要有codename值。
2. 生成镜像
(1) 创建Dockerfile
生成镜像的第一步是创建一个Dockerfile。在我这里,参考着官方文档,使用混元平台生成了一个大致可用的Dockerfile,我跟混元的对话如下(当然第一次没把需求提的这么清楚,经过多轮对话我的要求优化为):
写一个Dockerfile,满足下面的要求:
0、创建一个Ubuntu镜像
1、在docker中安装python3
2、使用requirements.txt安装依赖
3、把accuracy_recall_for_ehp、cos_log_download_process和test_rule_checker三个文件夹拷贝到docker中
4、写一个start.sh,在脚本中分别使用python来启动accuracy_recall_for_ehp、cos_log_download_process和test_rule_checker三个文件夹中的main.py,每个python脚本的执行记录都写到日志文件中,且每个程序保持在后台运行
5、使用start.sh作为docker的启动命令
混元的返回是:
# 0. 创建一个Ubuntu镜像
FROM ubuntu:latest
# 设置工作目录
WORKDIR /app
# 1. 在docker中安装python3
RUN apt-get update && \
apt-get install -y python3 python3-pip
# 2. 使用requirements.txt安装依赖
COPY requirements.txt .
RUN pip3 install -r requirements.txt
# 3. 把accuracy_recall_for_ehp、cos_log_download_process和test_rule_checker三个文件夹拷贝到docker中
COPY accuracy_recall_for_ehp cos_log_download_process test_rule_checker /app/
# 4. 写一个start.sh,在脚本中分别使用python来启动accuracy_recall_for_ehp、cos_log_download_process和test_rule_checker三个文件夹中的main.py,每个python脚本的执行记录都写到日志文件中,且每个程序保持在后台运行
RUN echo '#!/bin/bash' > start.sh && \
echo 'python3 /app/accuracy_recall_for_ehp/main.py > /app/accuracy_recall_for_ehp.log 2>&1 &' >> start.sh && \
echo 'python3 /app/cos_log_download_process/main.py > /app/cos_log_download_process.log 2>&1 &' >> start.sh && \
echo 'python3 /app/test_rule_checker/main.py > /app/test_rule_checker.log 2>&1 &' >> start.sh && \
chmod +x start.sh
# 5. 使用start.sh作为docker的启动命令
CMD ["/app/start.sh"]
经过测试,这个Dockerfile他是可用的。从一个侧面也说明了大语言模型对我们工作效率的提升还是比较有效的。
(2) 镜像仓库
在正式创建Docker镜像前,我们先来说下镜像仓库。
① 镜像仓库是什么
镜像仓库是一种存储、分发和检索软件镜像(即Docker镜像)的集中化服务。以下是为什么需要使用镜像仓库的一些原因:
- 便捷性:镜像仓库允许用户在不构建本地镜像的情况下,快速获取并运行预构建的镜像。这样可以节省时间和资源,提高开发效率。
- 版本控制:镜像仓库可以帮助用户跟踪和回溯软件的版本历史,并且可以轻松地将软件回滚到以前的版本。这对于在生产环境中进行故障排查和修复问题时非常有用。
- 安全性:镜像仓库可以帮助用户集中管理和控制对镜像的访问权限。这可以防止未经授权的访问和潜在的恶意攻击。
- 协作:镜像仓库支持多人合作开发同一个项目。通过将镜像存储在仓库中,团队成员可以轻松地共享和同步软件镜像,从而提高团队协作效率。
- 可扩展性:镜像仓库可以轻松地支持大量用户和镜像。这对于大型组织和企业在全球范围内分发和部署软件镜像非常有用。
综上所述,镜像仓库可以帮助用户更快速、安全、便捷地管理和分发软件镜像,从而提高开发效率和团队协作水平。
② 常用的镜像仓库
常用的Docker仓库包括以下几种:
- Docker Hub:Docker官方提供的公共仓库,包含了大量的官方和社区维护的镜像,用户可以通过docker pul命令从Docker Hub获取镜像。
- 私有仓库:可以在本地或私有网络中搭建的私有Docker仓库,用于存储和管理自定义的镜像。常见的私有仓库包括Docker Registry、Harbor等。
- 第三方仓库:除了Docker Hub和私有仓库,还有一些第三方的Docker仓库,提供了特定领域或特定用途的镜像。例如,Gogle Cloud Container Registry、AWS Elastic Container Registry等。
- 其他公共仓库:除了Docker Hub,还有一些其他的公共仓库,例如Quay.io、Azure Container Registry等,它们提供了各种不同的镜像和服务。
这些仓库为用户提供了方便、安全、高效的Docker镜像存储和分发解决方案,帮助用户更好地管理和部署应用程序。
(3) 使用Dockerfile创建镜像
在生成了Dockerfile之后,就是使用Dockerfile来生成镜像了。如果只是在本地测试,那么使用docker build命令就可以了:
sudo docker build -t my-image .
如果我们想要把镜像存入到镜像仓库中,我们在创建镜像时,就需要使用把镜像的名字生成的“讲究”、漂亮一些。具体来说,如果我们想把镜像存入到镜像仓库中以备后续在容器服务中使用的话,我们为镜像起名字时要把我们后续需要使用的镜像仓库地址以、个人文件夹写入到镜像名字中。建议先创建一个个人的镜像来做测试。在创建好自己的仓库地址后,在build镜像时需要指定仓库的地址为镜像的名称,即:
sudo docker build -t myusername/myimage:latest .
其中,latest为仓库的tag。在创建好镜像后,可以通过下面的命令来查看镜像是否创建成功(结果就不贴了,是否成功一目了然):
sudo docker images
3. 启动镜像
在创建好镜像后,可以通过下面的命令来启动镜像:
sudo docker run -d -p 80:80 --name test-container myusername/myimage
其中test-container为镜像启动后的容器名称。在启动后,可以通过下面的命令来判断当前容器的运行情况:
sudo docker ps -a
返回的格式如下:
建议使用ps -a而不是ps,使用ps -a能够看到全量的包括历史上运行已经停止了的容器的情况。
(1) 容器运行状态
上面提到了使用ps -a命令能看到容器当前的运行情况,其中STATUS这一列说明了不同容器当前的运行状态。STATUS可能的值包括:Up、Exited、Created、Restarting、Removing和Dead。这些值的具体含义如下:
- Up: 容器正在运行。
- Exited: 容器已退出,通常表示容器内的主进程已完成并正常退出。
- Created: 容器已创建,但尚未启动。
- Restarting: 容器正在重启。
- Removing: 容器正在被删除。
- Dead: 容器已死亡,通常表示容器内的主进程已崩溃或被终止。
① inspect命令
上面的值可能会因为Docker版本和操作系统的不同而有所不同。要获取更详细的信息,你可以使用docker inspect命令查看容器的详细信息。例如:
docker inspect 9c436ea546b9
inspect命令的返回值是个json文件,包含对象的所有属性和配置,一些常见的字段包括:“Id”、“Name”、“State”、“Mounts”、“NetworkSettings”等值。这里就不展开说明,感兴趣的小伙伴们可以自己试试。
② Exited原因解析
继续说回STATUS中的Exited状态,这个状态的不同值能反应容器退出的原因,当Docker ps命令的STATUS列显示为Exited时,表示容器已经退出。Exited后面的数字表示容器退出时的状态码。状态码0表示容器正常退出,而非0的状态码表示容器异常退出。
非0的状态码通常表示容器在执行过程中遇到了错误,以下是一些常见的非0状态码及其含义:
- 1:一般性未知错误
- 2:不适用的shell或者语法错误
- 126:命令不可执行
- 127:未找到命令
- 128:无效的退出参数
- 128+n:通过信号n终止的程序
- 130:通过Ctrl+C终止的程序
- 255:退出状态码超出有效范围
需要注意的是,这些状态码并不是唯一的,具体的状态码可能会因应用程序的不同而有所不同。
③ logs命令
在实际使用中,建议查看容器的日志以获取更多关于错误的详细信息。如果容器运行起来后就处于Exited状态,我们除了可以通过inspect命令来查看当前退出可能的原因外,我们也可以通过docker logs命令来查看容器运行起来时的日志,具体的命令如下:
sudo docker logs 9c436ea546b9
其中“9c436ea546b9”为Container_Id。在实际应用中,我使用logs命令发现了我的进程异常退出的原因:
ubuntu@VM-77-147-ubuntu:~$ sudo docker logs 5ade7c784050
start.sh: line 7: ture: command not found
脚本写错了,把true写成了ture(这是在混元返回的基础上自己写的少量代码之一,这也说明了人没那么靠谱?)。LOL。
另外,建议在启动脚本中,启动应用时,以nohup启动并将程序的运行日志重定向到文件中。如果脚本中只是启动一个守护进程,可以在启动脚本后面增加一个循环语句或者wait命令,让镜像能持续的运行。
4. 进入容器
可以使用下面的命令进入到容器中:
sudo docker exec -it “CONTAINER ID” /bin/sh
其中CONTAINER ID为上面docker ps命令返回的第一列。进入到容器后,可以像在本地的系统一样进行调试,可以通过启动脚本或者其他命令来查看我们想要执行的命令是否正确。
5. 关闭容器
一般来说,在本地调试时,可以通过下面的命令来进行关闭容器:
sudo docker stop “CONTAINER ID”
其中CONTAINER ID为上面docker ps命令返回的第一列。在使用stop命令后,可以再次调用ps -a命令查看当前的docker进程情况
6. 推送镜像
在本地测试通过后,可以通过下面的命令将生成的镜像推送到镜像仓库中:
sudo docker push myusername/myimage:latest
这时在3.2.3中提到的-t的创建镜像时指定名称就起作用了,在这里指定好镜像的命令,进行推送。推送成功后,仓库中就能见到myusername目录下的标签是latest的镜像了。
7. 更新镜像
在测试的过程中遇到了一些环境问题(这里举例说明如何更新镜像,后来环境问题通过其他方式解决了),比如我们需要使用Python 3.12,而现在Python的官网上并未正式的支持Python3.12,我们不能通过apt-install的方式进行安装,并且使用下面的命令在执行时也失败了:
sudo add-apt-repository ppa:deadsnakes/ppa
ppa安装失败。花了一些功夫没有解决掉。这时候想到了一个可以绕道的办法,像在本地安装Python3.12这样,下载Python3.12的安装文件夹,把这个文件夹拷贝到镜像中,使用3.4中提到的方法进入到容器中,再进入到Python3.12的路径下通过./configure,mak -j和make altintall的方式进行安装。安装成功后的结果:
这时退出容器,通过docker的commit方法把刚才的操作给存起来,我们从docker --help能看到docker commit的释义:
commit Create a new image from a container's changes
从这个能看出来,commit的作用是把现在容器中的变化给存储起来。具体的命令是:
sudo docker commit 10074a786927 myusername/myimage:v2
其中“10074a786927”是前面提到的“CONTAINER ID”。提交成功后,再push到镜像仓库中。
8. 删除容器
在3.3中提到了如何启动容器,在3.4中提到了如何进入到容器中。除了这两点外,我们还可以使用docker ps -a命令查看当前的容器运行情况,在命令的返回结果中我们能看到容器的名字,如果我们想要删除某些容器,可以使用下面的命令来删除:
sudo docker rm my_container
其中my_container是容器的名字。
9. 删除镜像
在前面3.2.3中我们看到可以使用docker images来查看当前的镜像的状态,如果我们有些镜像不用了,需要删除掉这些镜像,我们可以使用下面的命令来进行删除:
sudo docker rmi image_id
其中image_id是镜像的id。
四、镜像部署
在把镜像上传到仓库后,就可以在自己的容器配置平台进行镜像部署了。由于大家的镜像部署环境和平台都不相同,大家可以参考自己的平台配置指引进行部署,这里就不给大家举例子了。