认识docker
——基于docker把一个django部署到服务器
带“※实操”项的为可操作内容,其他做了解
应用场景
现我有一个django框架下的前后端分离项目, 由 python + vuE写成。 现在把整个工程文件打包、布置到一台云服务器(购买阿里云方的Ubuntu 22.04)里,
个人理解思路
我注意到有一个概念叫“docker”,用现实生活中的例子比喻来通俗地解释它的原理。
![image-20231016101803545](https://img2023.cnblogs.com/blog/3232290/202310/3232290-20231018112916392-96368851.png)
docker的比喻
Docker 是一种容器化技术,它允许 应用程序及其所有依赖项(如库、配置、环境变量等)打包到一个独立的、可移植的容器中。这个容器可以在任何支持 Docker 的系统上运行,而不受底层操作系统的影响。我将其比喻为集装箱:
想象 一家货运公司, 需要将各种货物从A运送到B。在过去 需要不同类型和大小的货车,因为每种货物都有自己的要求。
Docker 就像 一种标准化的集装箱,可以将不同类型的货物(应用程序)放入这些集装箱中,而不用担心它们的具体要求。每个集装箱是相对独立的,里面包含了所有货物所需的一切:货物本身、包装材料、标签和说明书。
![image-20231015175017697](https://img2023.cnblogs.com/blog/3232290/202310/3232290-20231018112916762-1057150135.png)
现在 使用这些标准集装箱(Docker 容器)将货物(应用程序)从一台货车(操作系统)运输到另一台货车,而无需担心货车的类型和配置。
无论货车是运行在云服务器、本地服务器还是开发者的本地机器上。有助于简化开发、测试和生产环境之间的一致性,并提高整体的开发和部署效率。
几个参与元素的理解
有这样一句话:
“docker中的image(镜像)就好比python中的类,而container(容器)就好比python中实例化的对象”
镜像与容器
镜像(Image):镜像就好比是一个食谱。 食谱中包含的是制作方法和所需的食材清单。这些食谱是静态的,你可以随时查看它们,但它们不能用于烹饪。镜像是类似的,它是一个应用程序和其依赖项的说明书,但不能直接运行。
比如,在安装号docker后,使用docker search ubuntu来搜索所有包含ubuntu关键字的镜像,如图,赞数最多的首位是官方镜像,其他人可以将此官方镜像经由自己一定程度修改并再次上传到此搜索列表
![image-20231017102417619](https://img2023.cnblogs.com/blog/3232290/202310/3232290-20231018112917340-1194576867.png)
容器(Container):容器就好比是使用食谱烹饪出的一道菜。当 按照食谱的指示,将特定的食材和步骤组合在一起,最终你会得到一道实际的菜肴。这道菜是独立的,你可以品尝它,修改它,或者与其他人共享它。容器是类似的,它是根据镜像运行的一个实际实例,可以独立运行、修改或与其他容器互动。
所以,镜像是静态的规范,而容器是根据规范创建的可运行实体。
在 Python 中的 "类" 和 "实例" 之间也可以用类似的比喻来理解 Docker 中的 "镜像" 和 "容器" 的关系。
类(Class):在 Python 中,类 是一个对象的模板或蓝图。类定义了对象的属性和方法,但它本身不能直接使用,就像一个菜谱 。
实例(Instance):实例就好比是根据类创建的具体对象。 使用类来创建对象实例时, 可以访问该对象的属性和方法,就像 根据菜谱烹饪出一道具体的菜肴后,可以享用它
实际开发过程中,为了保护服务器的开发环境,就需要不同的开发者创建属于自己的container。服务器中一个个不同的container可以简单地理解为一个个不同的虚拟机(我个人的理解),因为是虚拟机所以就代表可以从本地端去利用SSH去连接不同的container,从而来完成远程开发与调试。
Hub
镜像被仓库(Hub)统一管理,Docker官方有自己的仓库Docker Hub,国内可以使用阿里云的镜像仓库,我用的是阿里的,需要在阿里后台生成自己的私有地址,然后在Docker中配置。从镜像仓库获取镜像到本地后,就可以基于镜像新建容器使用了。
![image-20231017102753319](https://img2023.cnblogs.com/blog/3232290/202310/3232290-20231018112918121-851243887.png)
【※】加速器
使用容器时一般需要首先下载一个容器镜像,例如Docker Hub官方提供的MySQL、WordPress等容器镜像。然而由于网络原因,下载一个Docker官方镜像可能会需要很长的时间,甚至下载失败。为此,阿里云容器镜像服务ACR提供了官方的镜像站点,从而加速官方镜像的下载。
在Ubuntu服务器的终端(对缩进不敏感)内,选择etc/docker/daemon.json使用EOF写入以下(两段地址应为自己的):
{
"registry-mirrors":[
"https://略.mirror.aliyuncs.com",
"http://略.m.daocloud.io"
]
}
EOF
官方镜像加速 教程 参考阿里技术手册 https://help.aliyun.com/zh/acr/user-guide/
或
使用网站 daocloud 提供的加速器,同样需要注册以使用
使用以下 回到 该位置检查是否写入:
root@CoalOctDevServer:~# cat /etc/docker/daemon.json
{
"registry-mirrors":[
"https://qqmyro8s.mirror.aliyuncs.com",
"http://f1361db2.m.daocloud.io"
]
}
【※】宿主机网卡转发
服务器要提供给 外部用户 访问的服务,但用户不知道机内有没有、有哪些服务(容器),用户将请求给到服务器,服务器转发此请求给执行该功能的特定容器(比如仅此A能做,B做不了)
流程原理如图示(假设),实现代码如下:
![image-20231017150954115](https://img2023.cnblogs.com/blog/3232290/202310/3232290-20231018112918493-1414565675.png)
# 写入内容
root@CoalOctDevServer:~# cat <<EOF > /etc/sysctl.d/docker.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.conf.default.rp_filter = 0
net.ipv4.conf.all.rp_filter = 0
net.ipv4.ip_forward=1
EOF
# 使其应用
root@CoalOctDevServer:~# sysctl -p /etc/sysctl.d/docker.conf
# 执行结果
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.conf.default.rp_filter = 0
net.ipv4.conf.all.rp_filter = 0
net.ipv4.ip_forward = 1
root@CoalOctDevServer:~#
uwsgi、wsgi
在python web开发中,我们经常使用uwsgi配合nginx部署一个web框架,如
Django
或flask
。同时我们又会说,框架和web服务器之间要符合wsgi协议。
web开发的两大块,web服务器和web框架,web服务器即用来接受客户端请求,建立连接,转发响应的程序。至于转发的内容是什么,交由web框架来处理,即处理这些业务逻辑。如查询数据库、生成实时信息等。Nginx就是一个web服务器,Django
或flask
就是web框架。
如何实现uWSGI和WSGI的配合呢?如何做到任意一个web服务器,都能搭配任意一个框架呢?这就产生了WSGI协议。只要web服务器和web框架满足WSGI协议,它们就能相互搭配。所以WSGI只是一个协议,一个约定。而不是python的模块、框架等具体的功能。
uWSGI(Web Server Gateway(网关,网关就是在协议之间进行转换) Interface,是为 Python 语言定义的 Web 服务器和 Web 应用程序或框架之间的一种简单而通用的接口。也可以认为WSGI是一种通信协议)则是实现了WSGI协议的一个web服务器。即用来接受客户端请求,转发响应的程序。实际上,一个uWSGI
的web服务器,再加上Django
这样的web框架,就已经可以实现网站的功能了。那为什么还需要Nginx呢:
nginx
一个普通的个人网站,访问量不大的话,当然可以由uWSGI和Django构成。但是一旦访问量过大,客户端请求连接就要进行长时间的等待。这个时候就出来了分布式服务器,我们可以多来几台web服务器,都能处理请求。但是谁来分配客户端的请求连接和web服务器呢?Nginx就是这样一个管家的存在,
沿用之前 货运公司和集装箱的比喻,让我们来解释 "Nginx" 在整个过程中的角色和作用:
Nginx 是类似于物流中的分拣中心 。它是一个高效的、轻量级的网络服务器和反向代理服务器。在 Docker 中,Nginx 可以扮演多种角色:
- 负载均衡:假设 有多个货运车辆,每辆车都装载着集装箱(Docker 容器),并且我希望均匀地将货物(网络请求)分配给这些车辆。 Nginx 在这里就像一个货物分拣中心,可以将请求均匀分发给不同的容器,以确保负载均衡。
- 反向代理:Nginx 还可以充当反向代理,就像一家货运公司的前台。它接收客户(用户)的请求,然后将请求转发到适当的容器,这些容器可能运行不同的应用程序。用户只需要与 Nginx 交互,而不需要了解后面的容器结构。
![image-20231016101056386](https://img2023.cnblogs.com/blog/3232290/202310/3232290-20231018112918889-2053103998.png)
下面以网购为情景再次解释一下nginx是如何 确保 用户的请求 被有效地处理、分发和路由
当一个人在进行网购过程时,Nginx 在整个过程中发挥多个职能 :
入口接待:Nginx 就像一名入口接待员,当用户开始浏览 商店(网站)时,他们将向 Nginx 发送请求。这是用户与网站的第一次接触。Nginx负责接收这些请求并将它们传递给正确的地方,就像接待员引导顾客到正确的部门或柜台。
负载均衡:假设 网站受到了很多用户的访问(商店进来了很多人),Nginx 可以充当负载均衡员的角色。它将用户请求均匀地分发给后端的多个服务器(如 Web 应用程序服务器),以确保流量均匀分布,从而防止服务器超载 。
反向代理:Nginx 可以用于反向代理,这就像用户进入了一家实际商店,但他们实际上是与店员而非产品直接互动。 Nginx 可以接收用户的请求,然后 将请求 代理到适当的后端服务器,这些服务器可能运行不同的应用程序或提供不同的服务。用户只需与 Nginx 互动,而不需要了解后端服务器的结构。
静态文件提供者:当用户在购物过程中需要访问网站上的静态文件,例如商品图片、CSS 文件或 JavaScript 文件时,Nginx 可以像物流中心一样提供这些文件。这些文件通常存储在特定的位置,Nginx 可以高效地提供它们,而不需要将请求发送给应用程序服务器,从而减轻服务器负担。
![image-20231016101704020](https://img2023.cnblogs.com/blog/3232290/202310/3232290-20231018112919274-451216319.png)
【※】实践案例
以下两个案例参考自教程
Flask网站
需求
规定使用docker,在VMware初始环境为centOS 7.9 创建一个 在 Ubuntu 18.04 上 运行我们开发好的Flask网站,其运行环境已知是python3
(解读:即 现在有一个网站需打包进虚拟机里,但虚拟机目前只装了ctOS,所以考虑如何给他准备py环境和ubt系统)
流程
1、获取
获取基础镜像Ubuntu18.04(强制规定版本号)
docker search ubuntu
之前命令默认是docker pull ubuntu:latest
现在指定拉取docker pull ubuntu:18.04
,使用docker images
看下载好没有
2、构建
在基础镜像上 构建自定义镜像ubuntu18.04+python+具体工程代码(此处为引例所以使用hello world)
如何构建镜像?
Dockerfile及其语法
写一个就叫Dockerfile的文件,内容(各个指令)如下
# Base images 基于哪个基础的镜像?
FROM ubuntu:18.04
# MAINTAINER 维护者信息,作者是谁
maintainer RMPA 123@123.com
# RUN 拿到之后 先执行以下命令,具体到此例就是先安python、flask框架、名为www的嵌套文件夹
RUN apt update
RUN apt install python3 python3-pip -y
RUN pip3 install flask
RUN mkdir -p /data/www/
# 拷贝 事先写好的网站文件 至 工作目录
COPY app.py /data/www/app.py
# 工作目录,表示“我进入哪个文件夹”;我进入系统之后到www这里,里面有app.py这个
WORKDIR /data/www/
# EXPOSE 映射端口
EXPOSE 80
# 容器启动时执行命令,即“用python3 来 运行 代码文件”
CMD ["python3","app.py"]
app.py 内容:
from flask import Flask
app = Flask(__name__)
@app.route("/index")
def index():
return "欢迎光临"
if __name__ == "__main__":
app.run(host="0.0.0.0",port=8000)
上面写好的 ①含各个语法的名为Dockerfile的文件;
②名为app.py内含具体代码的文件;
放在docker的宿主机ctOS7.9里面,
通过一些命令来构建自定义的镜像(模板):
[root@192 crm]# docker build -t 我是自定义的镜像的名字/我是自定义的后缀(可以是版本号) . -f Dockerfile
上面“-t”即表示tag
以(KeXUe上网登入)dockerhub内检索“ubuntu”为例,一旦自定义了属于自己的镜像,就可以被所有人在此功能下搜到:
![image-20231017155531204](https://img2023.cnblogs.com/blog/3232290/202310/3232290-20231018112919625-532112736.png)
(个人认为)这件事可以类比Git或微软的ppt:
当我们打开PowerPoint,首页可以选择直接使用现成的模板
![image-20231017155857661](https://img2023.cnblogs.com/blog/3232290/202310/3232290-20231018112919969-1153303453.png)
但如果不加以任何修改,不能完全为我所用,因为如下图并没有 我需要展示的,比如我想修改题目、修改作者名字、展示手机号或者公司名称
![image-20231017160453299](https://img2023.cnblogs.com/blog/3232290/202310/3232290-20231018112920379-1548026071.png)
上述“自定义镜像”一事同理,你可以直接拿官方提供的初始版本使用,也可以在之上根据自己需求(煤矿软件?外卖网购?图书管理?···)形成一套新的系统并上传
3、创建及运行
基于以上集成了自定义内容的镜像 创建容器、运行
当我真的让命令
docker build -t 我是自定义的镜像的名字/我是自定义的后缀(可以是版本号) . -f Dockerfile
实际执行的时候,解析如图(逐步进行):
![image-20231017161705227](https://img2023.cnblogs.com/blog/3232290/202310/3232290-20231018112920885-148275350.png)
最终执行结果可以再次通过指令查看(发现多了一个我们自己创建的镜像):
![image-20231017161831037](https://img2023.cnblogs.com/blog/3232290/202310/3232290-20231018112921359-1263291802.png)
端口转发
然后考虑端口的转发
服务器要提供给 外部用户 访问的服务,但用户不知道机内有没有、有哪些服务(容器),用户将请求给到服务器,服务器转发此请求给执行该功能的特定容器(比如仅此A能做,B做不了)
所以,
使用单纯的 docker run 你的镜像名/你的后缀
命令来启动运行 不具备转发端口的功能
docker run -p 80:8000 你的镜像名/你的后缀
(后缀若有版本号记得写全版本号)才可以,其中80是ctOS监听的端口,我们把80转发给8000,写8000是因为在app.py里它自己监听的是8000端口
二者均可运行起来:
![image-20231017163015883](https://img2023.cnblogs.com/blog/3232290/202310/3232290-20231018112921757-2143385525.png)
此时就可以在浏览器访问(即 “服务器对外提供服务”)了:
![image-20231017163151811](https://img2023.cnblogs.com/blog/3232290/202310/3232290-20231018112922120-745834971.png)
但以上状态下的服务在此次启动后,如果在shell内退出则全部停止,故再次改进启动的命令,使其在后台常驻:
docker run -d -p 80:8000 你的镜像名/你的后缀
停止和删除
如何停止?
先用ps
命令看哪些在运行着:
![image-20231017164011327](https://img2023.cnblogs.com/blog/3232290/202310/3232290-20231018112922514-1839948041.png)
如何删除(remove
,rm + 容器id)?
只能删除已经停止的
小结
理解:镜像=模板(基础的/官方的、自定义的/自己由Dockerfile编写的),根据模板创建容器,容器是隔离的环境(里面扔代码)
Django网站
需求
创建ctOS7.9 + python3.9.5 + django3.2 + 工程代码已提前推到Gitee
流程
1、获取
下载基础镜像ctOS7.9
docker pull centos:7.9.2009
2、构建
2.1
编写Dockerfile >> py3.9.5 >> django3.2 >> gitee拉代码 >> 保证前台线程运行django
2.2
构建镜像
Dockerfile
# Base images 基础镜像
FROM centos:7.9.2009
#MAINTAINER 维护者信息
MAINTAINER rmpa
# 装GCC编译器
RUN yum install gcc -y
# 装Python依赖
RUN yum install -y zlib zlib-devel bzip2 bzip2-devel ncurses ncurses-devel readline readline-devel openssl openssl-devel xz lzma xz-devel sqlite sqlite-devel gdbm gdbm-devel tk tk-devel mysql-devel python-devel libffi-devel
# 装wget
RUN yum install wget -y
# 目录
RUN mkdir -p /data/
WORKDIR /data/
# 把sqlite下到/data/
# SQLite升级(截至2023.6.2版本3.7,而django对sqlite版本有要求>3.9)
# 去sqlite官网、下载包解压、运行编译、虚拟环境(python后期寻找sqlite的时候会找这个环境变量)
RUN wget https://www.sqlite.org/2023/sqlite-autoconf-3420000.tar.gz --no-check-certificate
RUN tar -zxvf sqlite-autoconf-3420000.tar.gz
WORKDIR /data/sqlite-autoconf-3420000
RUN ./configure
RUN make && make install
ENV LD_LIBRARY_PATH="/usr/local/lib"
# Python3.9环境
WORKDIR /data/
RUN wget https://www.python.org/ftp/python/3.9.5/Python-3.9.5.tgz
RUN tar -xvf Python-3.9.5.tgz
WORKDIR /data/Python-3.9.5/
RUN ./configure
RUN make && make install
RUN pip3.9 config set global.index-url https://pypi.douban.com/simple/
# git(注意设为公有仓库,”是否开源“)
RUN yum install git -y
RUN git config --global user.name "言行一"
RUN git config --global user.email "12345@abc.com"
# git拉代码
WORKDIR /data/
RUN git clone https://gitee.com/wupeiqi/blog.git
# 基于3.9创建 虚拟环境
RUN pip3.9 install virtualenv
RUN virtualenv /envs/dj --python=python3.9
RUN /envs/dj/bin/pip3.9 install django==3.2
# 运行项目
WORKDIR /data/blog
CMD ["/envs/dj/bin/python","manage.py","runserver","0.0.0.0:9000"]
![image-20231018101724887](https://img2023.cnblogs.com/blog/3232290/202310/3232290-20231018112922878-2014865001.png)
等待,完成后使用images
检查是否被添加,
3、创建容器+运行
使用
docker run -d -p 80:9000 dj
启动(9000取决于django内部写的什么)
部署方案
【※】阿里云镜像在ubuntu上安装Docker
进云服务器查看版本
cat /proc/version
更新版本、安装系统工具、gpg证书、写入阿里镜像源地址 分别以下命令
apt-get update
apt-get -y install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | apt-key add -
add-apt-repository "deb [arch=amd64] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable"
再更新一次
apt-get -y update
![image-20231015111719013](https://img2023.cnblogs.com/blog/3232290/202310/3232290-20231018112923265-5193190.png)
开始安装docker
apt-get -y install docker-ce
测试docker是否正常语句
特别注意 Linux对大小写敏感 以下语句必须是小写
docker ps -a
测试回执如图
![image-20231015113046486](https://img2023.cnblogs.com/blog/3232290/202310/3232290-20231018112923719-672047363.png)
没拉取镜像,所以显示为空,看到这个绿色状态表示能使用docker了,就表示已经安装成功。可以用来建各种功能服务
Docker部署Django由浅入深系列(上):单容器部署Django + Uwsgi - 知乎 (zhihu.com)
Django + Nginx + uWSGI 搭建的启动
逻辑
参考[使用Docker容器化部署实践之Django应用部署(一)-阿里云开发者社区 (aliyun.com)](https://developer.aliyun.com/article/869579#:~:text=1.通过自动化脚本,把代码同步到线上服务器(通过fabric或者ansible等) 2.重启supervisord(进程管理工具,通过uWSGI,控制Django服务启动) 3.通过docker-compose 更新docker镜像(如果有镜像有变更,比如装了包到容器里面))
- 通过docker-compose配置文件,进行控制supervisor启动
- Supervisor控制uWSGI端口服务启动
- uWSGI把整个Django应用拉动起来(这里类似python manage.py runserver)
- 而我们浏览器请求到达Nginx之后被反向代理到uWSGI端口服务上面,从而访问到我们Django应用。
- 至此就是整个应用的部署配置(除了没有自动化发布脚本以外
具体搭建参考https://www.cnblogs.com/yebaofang/p/12171260.html
虚拟环境法
存疑:虚拟环境不必加进去,因为 docker 会自动去下载依赖,而且虚拟环境占用 100M+ 的空间,上传的时间5+分钟
步骤
-
更新(sudo)和安装依赖
-
python3和pip3
-
存放virtualenv环境和项目 和virtualrnv的安装
-
启动虚拟环境
-
上传项目 安装依赖软件库
-
uwsgi
-
nginx
一个完整的Docker有以下几个部分组成:
- docker Client客户端
- Docker Daemon守护进程
- Docker Image镜像
- Docker Container容器
我们通过客户端写命令,然后客户端将命令发送给守护进程,守护进程再将命令执行的结果返回给客户端,这就使我们能通过命令查看执行结果,镜像就是容器的源代码,容器通过镜像启动,使用仓库来保存用户构建的镜像,仓库分为共有和私有。