Django-云环境中的部署
容器的基础用法 – Docker 容器介绍
-
Docker
- 码头工人,轻量级的,可移植,自包含的容器,来自动化、版本化应用的发布
- Docker上跑的容器是一个个的集装箱
-
Docker的基础是LXC
- LXC用于应用程序的隔离,每个应用程序分配独立的命名空间,隔离的CPU, 内存,磁盘,网络资源
- 每个应用内部可以单跑一套容器系统,功能上相当于传统的虚拟机,但本质上是内核层面对资源的隔离
-
Docker 容器的分层和版本管理
- Docker把应用和系统打包到一起(image镜像),进行版本化管理
- 应用之于Docker,如同代码之于Git/SVN,一个命令可以把应用部署到docker上
-
Docker容器的几个重要概念
容器的基础用法
准备工作
Docker 常用命令
docker pull <image-name>
:下拉远程镜像docker images
:查看本地有哪些镜像docker run<image-name> <command>
:运行镜像docker ps
:查看有哪些镜像正在运行docker logs
:查看容器运行过程中的日志
Port forwarding
docker run -d -p host:container django-docker
Volumes mount folder - host/container
docker run -v host_path:container_path django
容器的基础用法 – 示例
准备工作
- 安装 Docker
- 命令行 pull 镜像, 启动容器
示例:运行一个 gogs 代码管理系统
1 | docker pull gogs/gogs |
1 | mkdir -p /data/gogs |
1 | # 挂载机器上的 /data/gogs 到容器的 /data 目录, 启动容器 |
1 | docker start gogs # 如果停止了, 运行 start 重新启动 |
容器化的 gogs 服务
设置
-
为项目创建目录
1
2$ mkdir composetest
$ cd composetest -
在项目目录中创建一个名为
app.py
的文件,并将以下代码粘贴至其中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24import time
import redis
from flask import Flask
app = Flask(__name__)
# host:IP地址,默认本地IP;port:Redis端口6379;password:redis登录密码
cache = redis.Redis(host='redis', port=6379, password='xxx')
def get_hit_count():
retries = 5
while True:
try:
return cache.incr('hits')
except redis.exceptions.ConnectionError as exc:
if retries == 0:
raise exc
retries -= 1
time.sleep(0.5)
def hello():
count = get_hit_count()
return 'Hello World! I have been seen {} times.\n'.format(count) -
在项目目录中创建另一个名为
requirements.txt
的文件,并将以下内容粘贴至其中1
2flask
redis
创建Dockerfile
在您的项目目录中,创建一个名为的文件Dockerfile
并粘贴以下内容
1 | FROM python:3.7-alpine # 使用 Python 3.7 作为基础镜像 |
Docker compose
在项目目录中创建一个名为docker-compose.yml
的文件,然后粘贴以下内容
1 | version: "3.9" |
- Web 服务
该web
服务使用从Dockerfile
当前目录中构建的映像。然后,它将容器和主机绑定到暴露的端口5000
。此示例服务使用Flask Web服务器的默认端口5000
- Redis 服务
该redis
服务使用 从Docker Hub注册表中提取的公共Redis映像
启动程序
- 在项目目录中,运行该命令启动应用程序
docker-compose up
- 在浏览器中输入 http://localhost:5000/ 以查看应用程序正在运行
- 刷新页面:该数字应递增
- 切换到另一个终端窗口,然后键入
docker image ls
以列出本地图像 - 通过从第二个终端的项目目录中运行
docker-compose down
,或在启动该应用的原始终端中按CTRL + C来停止该应用
容器化 Django 应用 – 构建小镜像
-
Django 应用容器化步骤
使得开发环境可以使用容器
-
优势
- 效率提升:开发环境可以复用
- 简单:一个命令搭建可以运行的开发环境
- 每个应用隔离的容器环境,无Python/Pip包版本冲突
-
代码调整
-
settings.py 配置
1
ALLOWED_HOSTS = ['*']
-
-
配置项放到环境变量中
-
start.local.bat
的改动
-
Django 应用容器化 Dockerfile
-
构建小镜像 Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14FROM python:3.9-alpine # 引用python3.9基础镜像
WORKDIR /data/recruitment # 容器中的工作目录
ENV server_params= # 环境变量,默认为空,启动容器时传入
COPY requirements.txt ./
RUN apk add --update --no-cache curl jq py3-configobj py3-pip py3-setuptools python3 python3-dev \
&& apk add --no-cache gcc g++ jpeg-dev zlib-dev libc-dev libressl-dev musl-dev libffi-dev \
&& python -m pip install --upgrade pip \
&& pip install -r requirements.txt \
&& apk del gcc g++ libressl-dev musl-dev libffi-dev python3-dev \
&& apk del curl jq py3-configobj py3-pip py3-setuptools \
&& rm -rf /var/cache/apk/*
COPY . .
EXPOSE 8000
CMD ["/bin/sh", "/data/recruitment/start.local.bat"] -
构建小镜像要点
- 使用 alpine 的基础镜像
- 多个命令使用一个 RUN 命令,这样只会产生一个layer(层),占用空间很小
- 在 RUN 命令的最后面删除不用的包,以及cache的包,减小镜像大小
- 使用
.dockerigonre
文件,把不需要打包到镜像的文件剔除,镜像会小很多
-
运行容器
-
构建镜像
1
2# -t表示给镜像打一个tag
docker build -t ihopeit/recruitment-base:0.8.1 . -
交互运行
1
2# -it 表示已交互式运行
docker run -it --rm -p 8000:8000 --entrypoint /bin/sh ihopeit/recruitment-base:0.8.1 -
开发环境, 开发阶段,指定本地源码目录
1
docker run -it --rm -p 8000:8000 -v "$(pwd)":/data/recruitment --entrypoint /bin/sh ihopeit/recruitment-base:0.8.1
-
指定加载源码 && 环境变量
1
docker run --rm -p 8000:8000 -v "$(pwd)":/data/recruitment --env server_params="--settings=settings.local" ihopeit/recruitment-base:0.8.1
-
容器编排 – docker compose
Docker compose 单机编排
- 想要在一台主机上, 一下子跑起来多个容器, 且容器之间有调用关系
在一个 docker-compose.yml
文件中配置4个容器
- web:django 的应用, 使用
start.local.bat
来启动 - redis:缓存,以及 Celery 的broker要用到的数据存储
- celery:异步任务 worker (用于异步发送钉钉消息通知)
- flower:异步任务的监控应用 (监控异步任务的执行情况)
docker-compose.yml
docker-compse up –d
:启动一系列容器docker-compose stop
:停止一系列容器docker-compose rm
:删除一系列容器
1 | version: "3.2" |
容器编排 - Docker、compose 与 k8s
- 常常听说 Docker,docker-compose,Kubernetes/k8s,swarm
- 那么它们之间到底有什么关系和区别
容器编排 – k8s 的架构
图示
- 客户端 kubectl 命令
- 集群节点
- Pod运行在节点内
- Pod里面运行容器
分层架构
- 核心层
Kubernetes 最核心的功能,对外提供 API 构建高层的应用,对内提供插件式应用执行环境 - 应用层
部署(无状态、有状态应用、job 等) 路由 - 管理层
系统度量(如基础设施、容器和网络的度量) 自动化(如自动扩展、动态 Provision 等) 策略管理(RBAC、Quota、PSP、 NetworkPolicy 等) - 图示
核心组件
核心概念
- 声明式管理
- 通过 yml 声明期望的状态,k8s 自动根据定义的状态进行调度 (相对命令式管理而言, k8s 也提供命令式管理的方式)
- 声明式: kubectl apply -f
- 命令式: kubectl run xxx ,kubectl expose …
- Master node:集群主控节点,上面运行调度容器,管理容器
- Worker node:工作节点,上面运行应用的容器
- Pods:容器,k8s 对容器进行管理,自动创建、销毁容器
- Service:使用 标签 选择算符(selectors)标识的一组 Pod,在集群内有固定 IP;可以用于为集群内部容器/应用提供 稳定的访问入口,可以通过 service 名称访问到服务
- Deployment:一套部署的容器集合
- ReplicationController:可以复制的容器,功能集比 ReplicaSet 少,已不推荐使用
- ReplicaSet:可以扩容、缩容的容器副本集,在容器集不需要状态,也不需要被其它容器访问的时候, 可以使用 ReplicaSet。其它容器无法直接访问 RS,可以通过 Service 来访问
- StatefullSet:有状态的服务,比如 zookeeper,集群被外部使用的时候, 需要指定多个 zookeeper 服务的 ip 或者 hostname。由于是有状态的,比如 3 个节点的 zookeeper,不论如何扩缩容(包括宕机恢复),3个节点容器名称/主机名总是 zk-0, zk-1, zk-2
- Ingress:对外暴露可以访问的入口,为服务提供外网(从集群外)的访问入口
- Namespace:资源可以放在 namespace 下面, 不同 namespace 之间相互隔离
- Controller:包括有节点控制器 , 路由控制器 , 服务控制器
创建 k8s 声明式配置
把 compose 文件转换为 k8s 声明式配置文件。 on mac:
1 | curl -L |
1 | chmod +x compose && sudo mv kompose /usr/local/bin/ |
1 | kompose convert |
1 | mkidr k8s |
1 | mv *.yaml k8s |
然后安装 应用到 k8s 集群
1 | kubectl apply -f k8s |
k8s 部署流程 – 阿里云 k8s 使用示例
K8s 环境创建 & 部署流程
- 创建 Kubernetes 集群, 创建镜像仓库
- 配置本地 kubeconfig
- Docker login 到镜像仓库
- Docker build & docker push 推送镜像到镜像仓库
- K8s 部署到集群: kubectl apply -f k8s
访问阿里云 k8s 环境的应用
在阿里云 k8s 控制台, 创建路由(ingress 路由),指向 Django 应用,即可访问
管理监控容器中的Django应用
云环境的复杂性
- 应用被分布在了容器上运行,大量容器不断得创建销毁,升级
- 应用的可观测性,可见性变得更加重要
监控方案
-
kubectl 命令行
-
可视化监控方案
- GUI 的 kubernetes dashboard
- 云厂商的控制台
- Sentry
- ELK
- Prometheus
阿里云环境部署
- 手工安装 ack-prometheus-operator
- 执行以下命令,将集群中的Prometheus映射到本地9090端口
1
kubectl -n monitoring port-forward svc/ack-prometheus-operator-prometheus 9090:9090
- Grafana 查看与展示数据聚合
- 执行以下命令,将集群中的Grafana映射到本地3000端口(admin/prom-operator)
1
kubectl -n monitoring port-forward svc/ack-prometheus-operator-grafana 3000:80
- 图示
应用日志收集与查询
云环境的复杂性
- 应用被分布在了容器上运行,大量容器不断得创建销毁,升级
- 应用的可观测性,可见性变得更加重要
日志收集 & 查询的不同方案
- 使用Kubelet收集容器化应用输出到标准输出的日志
- 使用 sidecar 收集输出到文件中的日志,输出到标准输出 && tail –f
- ELK/EFK 采集日志
- 阿里云Logtail 日志采集
k8s 下面的各种日志
- Pod logs
- Node logs -> 宿主机的 /var/log/containers目录
- K8s components logging (api server ,scheduler …)
- K8s events
- Audit logs
- k8s 默认会将容器的stdout和stderr录入node的/var/log/containers目录下
- 而 k8s 组件的日志默认放置在/var/log目录下
阿里云 logtail 日志采集
- 为集群启用Logtail,确保 logtail-ds 组件已安装
- 登陆 SLS, 确保能看到采集的 k8s 系统日志
- 在 deployment.yaml 配置中指定 Logtail 相关配置变量
代码中的调整
- 日志输出到独立的目录中
- 方便采集
- Settings 文件中更改日志路径
- /data/logs/recruitment
- 图示
- 通过环境变量来创建您的采集配置和自定义Tag,所有与配置相关的环境变量都 采用aliyun_logs_作为前缀
- 创建采集配置的规则如下
- name: aliyun_logs_{Logstore名称} value: {日志采集路径}
- 签名创建了两个采集配置
- 其中 aliyun_logs_recruitment-web 这个env表示创建一个Logstore名字为 recruitment-web,日志采集路径为stdout的配置,从而将容器的标准输出采集 到 recruitment-web 这个Logstore中
- 图示