Django - 云环境中的部署

容器的基础用法 – Docker 容器介绍

  • Docker

    • 码头工人,轻量级的,可移植,自包含的容器,来自动化、版本化应用的发布
    • Docker 上跑的容器是一个个的集装箱
  • Docker 的基础是 LXC

    • LXC 用于应用程序的隔离,每个应用程序分配独立的命名空间,隔离的 CPU, 内存,磁盘,网络资源
    • 每个应用内部可以单跑一套容器系统,功能上相当于传统的虚拟机,但本质上是内核层面对资源的隔离
  • Docker 容器的分层和版本管理

    • Docker 把应用和系统打包到一起(image 镜像),进行版本化管理
    • 应用之于 Docker,如同代码之于 Git/SVN,一个命令可以把应用部署到 docker 上
  • Docker 容器的几个重要概念

    Docker容器的几个重要概念

容器的基础用法

准备工作

  1. 安装 Docker
  2. 配置使用阿里云镜像加速
    • 阿里云提供有 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

容器的基础用法 – 示例

准备工作

  1. 安装 Docker
  2. 命令行 pull 镜像, 启动容器

示例:运行一个 gogs 代码管理系统

1
docker pull gogs/gogs
1
mkdir -p /data/gogs
1
2
# 挂载机器上的 /data/gogs 到容器的 /data 目录, 启动容器
docker run --name=gogs -p 10022:22 -p 10080:3000 -v /data/gogs:/data gogs/gogs
1
docker start gogs # 如果停止了, 运行 start 重新启动

容器化的 gogs 服务

容器化的gogs服务

设置

  1. 为项目创建目录

    1
    2
    $ mkdir composetest
    $ cd composetest
  2. 在项目目录中创建一个名为 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
    24
    import 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)

    @app.route('/')
    def hello():
    count = get_hit_count()
    return 'Hello World! I have been seen {} times.\n'.format(count)
  3. 在项目目录中创建另一个名为 requirements.txt 的文件,并将以下内容粘贴至其中

    1
    2
    flask
    redis

创建 Dockerfile

在您的项目目录中,创建一个名为的文件 Dockerfile 并粘贴以下内容

1
2
3
4
5
6
7
8
9
10
FROM python:3.7-alpine # 使用 Python 3.7 作为基础镜像
WORKDIR /code # 把 /code 设置为工作目录
ENV FLASK_APP=app.py # 设置 flask 命令运行的环境变量
ENV FLASK_RUN_HOST=0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers # 安装gcc和其它依赖
COPY requirements.txt requirements.txt # 拷贝requirements.txt
RUN pip install -r requirements.txt # 安装其它依赖包
EXPOSE 5000 # 向图像添加元数据以描述容器正在侦听端口5000
COPY . . # 拷贝当前目录下所有文件到镜像的工作目录
CMD ["flask", "run"] # 设置容器的默认运行命令flask run

Docker compose

在项目目录中创建一个名为 docker-compose.yml 的文件,然后粘贴以下内容

1
2
3
4
5
6
7
8
version: "3.9"
services:
web:
build: .
ports:
- "5000:5000"
redis:
image: "redis:alpine"
  • Web 服务
    web 服务使用从 Dockerfile 当前目录中构建的映像。然后,它将容器和主机绑定到暴露的端口 5000。此示例服务使用 Flask Web 服务器的默认端口 5000
  • Redis 服务
    redis 服务使用 从 Docker Hub 注册表中提取的公共 Redis 映像

启动程序

  1. 在项目目录中,运行该命令启动应用程序 docker-compose up
  2. 在浏览器中输入 http://localhost:5000/ 以查看应用程序正在运行
  3. 刷新页面:该数字应递增
  4. 切换到另一个终端窗口,然后键入 docker image ls 以列出本地图像
  5. 通过从第二个终端的项目目录中运行 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
    14
    FROM 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
version: "3.2"
services:
web:
build:
context: .
dockerfile: Dockerfile
command: /bin/sh /data/recruitment/start.local.bat
environment:
- server_params=--settings=settings.local
volumes:
- .:/data/recruitment
- /data/logs/recruitment/
ports:
- "8000:8000"
depends_on:
- redis
- celery
- flower
redis:
image: "redis:alpine"
container_name: recruit-redis
ports:
- "6379:6379"
celery:
image: "ihopeit/django-recruitment:0.8"
container_name: recruit-celery
volumes:
- .:/data/recruitment
- /data/logs/recruitment/
entrypoint: ["/bin/sh", "/data/recruitment/worker.start.sh"]
depends_on:
- redis
flower:
image: "ihopeit/django-recruitment:0.8"
container_name: recruit-flower
ports:
- "5555:5555"
volumes:
- .:/data/recruitment
- /data/logs/recruitment/
entrypoint: ["/bin/sh", "/data/recruitment/flower.start.sh"]
depends_on:
- redis

容器编排 - Docker、compose 与 k8s

  • 常常听说 Docker,docker-compose,Kubernetes/k8s,swarm
  • 那么它们之间到底有什么关系和区别
    区别

容器编排 – k8s 的架构

图示

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
2
3
curl -L
https://github.com/kubernetes/kompose/releases/download/v1.16.0/k
ompose-darwin-amd64 -o kompose
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 环境创建 & 部署流程

  1. 创建 Kubernetes 集群, 创建镜像仓库
  2. 配置本地 kubeconfig
  3. Docker login 到镜像仓库
  4. Docker build & docker push 推送镜像到镜像仓库
  5. K8s 部署到集群: kubectl apply -f k8s

访问阿里云 k8s 环境的应用

在阿里云 k8s 控制台, 创建路由(ingress 路由),指向 Django 应用,即可访问

访问阿里云k8s环境的应用

管理监控容器中的 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
  • 图示
    管理监控容器中的Django应用

应用日志收集与查询

云环境的复杂性

  • 应用被分布在了容器上运行,大量容器不断得创建销毁,升级
  • 应用的可观测性,可见性变得更加重要

日志收集 & 查询的不同方案

  • 使用 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 日志采集

  1. 为集群启用 Logtail,确保 logtail-ds 组件已安装
  2. 登陆 SLS, 确保能看到采集的 k8s 系统日志
  3. 在 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 中
  • 图示