django入门到精通12 django2 + celery4自动化任务实现网站ssl证书的检测
我们在日常运维工作中如果管理的网站较多,经常会发生ssl证书过期而不能及时更新的问题,我们需要对域名证书的使用情况做检测,并且能及时知道什么时候续费证书并进行更新
这个项目只是个雏形,毕竟学了一段时间的django,用来小试牛刀,打通 前端 和 服务端及 redis,mysql的基本使用
功能如下:
a.可以添加、删除、修改网站
b.后台启用celery 任务对证书的信息进行更新
后续功能可以加入
1.如果证书过期实际小于10天发邮件报警
2.自动读取godaddy,aliyun等域名管理平台的域名信息并写入系统,还可以对二级域名进行批量检查,避免证书更新遗漏等
3.对网站的可用状态进行检查
4.对网站所在服务器的端口进行扫描汇总
...
celery原理
video 项目目录结构
# tree video
video
# 修改了默认的 vedeo 的settings.py 目录名为 config
├── config
│ ├── asgi.py
│ ├── celery.py
│ ├── __init__.py
│ ├── __pycache__
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── manage.py
├── __pycache__
├── ssl_check
│ ├── admin.py
│ ├── apps.py
│ ├── check_ssl.py
│ ├── __init__.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ ├── 0002_auto_20210222_2142.py
│ │ ├── __init__.py
│ │ └── __pycache__
│ ├── models.py
│ ├── __pycache__
│ ├── tasks.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
└── templates
├── add_domain.html
├── index.html
└── list_domains.html
1.搭建基本的开发环境
pip install virtualenv
pip install virtualenvwrapper-win
# 创建虚拟环境 python379_django2
mkvirtualenv python379_django2
# 安装依赖
workon python379_django2
pip install celery==4.4.2
pip install eventlet==0.25.2
pip install Django==2.0.4
pip install pymysql
pip install requests
pip install redis==3.4.1
2.配置基本的数据库app等信息
video/config/settings.py
import os from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent SECRET_KEY = ‘7qh#b#@‘ DEBUG = True ALLOWED_HOSTS = [‘*‘] INSTALLED_APPS = [ ‘django.contrib.admin‘, ‘django.contrib.auth‘, ‘django.contrib.contenttypes‘, ‘django.contrib.sessions‘, ‘django.contrib.messages‘, ‘django.contrib.staticfiles‘, ‘app‘, ‘ssl_check‘, ] MIDDLEWARE = [ ‘django.middleware.security.SecurityMiddleware‘, ‘django.contrib.sessions.middleware.SessionMiddleware‘, ‘django.middleware.common.CommonMiddleware‘, ‘django.middleware.csrf.CsrfViewMiddleware‘, ‘django.contrib.auth.middleware.AuthenticationMiddleware‘, ‘django.contrib.messages.middleware.MessageMiddleware‘, ‘django.middleware.clickjacking.XFrameOptionsMiddleware‘, ] ROOT_URLCONF = ‘config.urls‘ TEMPLATES = [ { ‘BACKEND‘: ‘django.template.backends.django.DjangoTemplates‘, ‘DIRS‘: [os.path.join(BASE_DIR, ‘templates‘)], ‘APP_DIRS‘: True, ‘OPTIONS‘: { ‘context_processors‘: [ ‘django.template.context_processors.debug‘, ‘django.template.context_processors.request‘, ‘django.contrib.auth.context_processors.auth‘, ‘django.contrib.messages.context_processors.messages‘, ], }, }, ] WSGI_APPLICATION = ‘config.wsgi.application‘ DATABASES = { ‘default‘: { ‘ENGINE‘: ‘django.db.backends.mysql‘, ‘NAME‘: ‘muke_video‘, ‘USER‘: ‘root‘, ‘PASSWORD‘: ‘root‘, ‘HOST‘: ‘10.9.4.199‘, ‘PORT‘: 3306 } } AUTH_PASSWORD_VALIDATORS = [ { ‘NAME‘: ‘django.contrib.auth.password_validation.UserAttributeSimilarityValidator‘, }, { ‘NAME‘: ‘django.contrib.auth.password_validation.MinimumLengthValidator‘, }, { ‘NAME‘: ‘django.contrib.auth.password_validation.CommonPasswordValidator‘, }, { ‘NAME‘: ‘django.contrib.auth.password_validation.NumericPasswordValidator‘, }, ] LANGUAGE_CODE = ‘zh-hans‘ TIME_ZONE = ‘Asia/Shanghai‘ USE_I18N = True USE_L10N = True USE_TZ = True STATIC_URL = ‘/static/‘ STATICFILES_DIRS = (os.path.join(BASE_DIR, ‘static‘), ) CELERY_BROKER_URL = ‘redis://10.9.4.199:6379/1‘ CELERY_RESULT_BACKEND = ‘redis://10.9.4.199:6379/2‘ CELERY_RESULT_SERIALIZER = ‘json‘ from celery.schedules import crontab CELERY_BEAT_SCHEDULE = { # 周期性任务 # ‘task-one‘: { # ‘task‘: ‘ssl_check.tasks.print_test‘, # ‘schedule‘: 5.0, # 每5秒执行一次 # # ‘args‘: () # }, ‘task-modify-ssl-info‘: { ‘task‘: ‘ssl_check.tasks.modify_domain_ssl_info_task‘, # ‘schedule‘: crontab(month_of_year=‘9‘, day_of_month=‘9‘, minute=‘*/1‘), # 设置9月9日,每一分钟执行一次 ‘schedule‘: crontab(minute=‘*/10‘), # 每一分钟执行一次 # ‘args‘: () } }
2.celery异步任务插件的引入
在settings.py同级目录创建celery.py
# coding:utf-8 from __future__ import absolute_import, unicode_literals import os from celery import Celery # 设置环境变量 os.environ.setdefault(‘DJANGO_SETTINGS_MODULE‘, ‘config.settings‘) # 注册Celery的APP, app = Celery(‘config‘) # 绑定配置文件 app.config_from_object(‘django.conf:settings‘, namespace=‘CELERY‘) # 自动发现各个app下的tasks.py文件 app.autodiscover_tasks()
注意 config 为你当前的django项目settings.py的目录名称
修改settings.py同级目录的 __init__.py文件
# coding:utf-8 from __future__ import absolute_import, unicode_literals from .celery import app as celery_app # 导包 import pymysql # 初始化 pymysql.install_as_MySQLdb() __all__ = [‘celery_app‘]
# 数据库models
video/ssl_check/models.py
from django.db import models # Create your models here. class Domain(models.Model): name = models.CharField(max_length=200, blank=False, unique=True) status = models.IntegerField(default=1) idc_id = models.IntegerField(default=0) company_id = models.IntegerField(default=0) ssl_start_date = models.CharField(max_length=200, blank=True, default="0000-00-00") ssl_expire_date = models.CharField(max_length=200, blank=True, default="0000-00-00") remaining_time = models.IntegerField(blank=True, default=0) def __str__(self): return "domain:{}".format(self.name)
在应用中创建tasks.py文件
video/ssl_check/tasks.py
# _*_ coding:utf-8 _*_ # __author__ == ‘jack‘ # __date__ == ‘2021-02-22 9:24 PM‘ import re import time import subprocess from datetime import datetime from io import StringIO from celery.task import task from .models import Domain # 自定义要执行的task任务,这个测试任务 @task def print_test(): print("nict try") return "hello" def modify_domain_ssl_info(domain): print("domain_name",domain.name) print(type(domain)) f = StringIO() comm = f"curl -Ivs https://{domain.name} --connect-timeout 10" print("comm:", comm) result = subprocess.getstatusoutput(comm) f.write(result[1]) m = re.search(‘start date: (.*?)\n.*?expire date: (.*?)\n.*?common name: (.*?)\n.*?issuer: CN=(.*?)\n‘, f.getvalue(), re.S) start_date = m.group(1) expire_date = m.group(2) common_name = m.group(3) issuer = m.group(4) # time 字符串转时间数组 start_date = time.strptime(start_date, "%b %d %H:%M:%S %Y GMT") start_date_st = time.strftime("%Y-%m-%d %H:%M:%S", start_date) # datetime 字符串转时间数组 expire_date = datetime.strptime(expire_date, "%b %d %H:%M:%S %Y GMT") expire_date_st = datetime.strftime(expire_date, "%Y-%m-%d %H:%M:%S") # 剩余天数 remaining = (expire_date - datetime.now()).days print(‘域名:‘, "domain") print(‘通用名:‘, common_name) print(‘开始时间:‘, start_date_st) print(‘到期时间:‘, expire_date_st) print(f‘剩余时间: {remaining}天‘) print(‘颁发机构:‘, issuer) print(‘*‘ * 30) domain.remaining_time = remaining domain.ssl_expire_date = expire_date_st domain.ssl_start_date = start_date_st # domain.update(remaining_time=remaining, ssl_expire_date=expire_date_st, ssl_start_date=start_date_st) domain.save() f.close() time.sleep(0.5) @task def modify_domain_ssl_info_task(): domains = Domain.objects.all() print("start check domain ssl celery task") for domain in domains: modify_domain_ssl_info(domain) print("end check domain ssl celery task") return "modify ssl"
可以在settings.py里将该任务配置为定时任务(周期任务)
from celery.schedules import crontab CELERY_BEAT_SCHEDULE = { # 周期性任务 # ‘task-one‘: { # ‘task‘: ‘ssl_check.tasks.print_test‘, # ‘schedule‘: 5.0, # 每5秒执行一次 # # ‘args‘: () # }, ‘task-modify-ssl-info‘: { ‘task‘: ‘ssl_check.tasks.modify_domain_ssl_info_task‘, # ‘schedule‘: crontab(month_of_year=‘9‘, day_of_month=‘9‘, minute=‘*/1‘), # 设置9月9日,每一分钟执行一次 ‘schedule‘: crontab(minute=‘*/10‘), # 每一分钟执行一次 # ‘args‘: () } }
同时异步任务也可以通过django的视图进行在线调用
# 这个是测试任务,可以通过django的web直接访问触发
class CetestView(View): def get(self, request, *args, **kwargs): res = print_test.delay() # 任务逻辑 return JsonResponse({‘status‘: ‘successful‘, ‘task_id‘: res.task_id}) # 可以通过web页面直接触发任务 class CheckSslView(View): def get(self, request): domains = Domain.objects.all() for domain in domains: self.check_ssl(domain) return redirect(reverse(‘list_domain‘)) def check_ssl(self, domain): print("domain_name",domain.name) print(type(domain)) f = StringIO() comm = f"curl -Ivs https://{domain.name} --connect-timeout 10" print("comm:", comm) result = subprocess.getstatusoutput(comm) f.write(result[1]) m = re.search(‘start date: (.*?)\n.*?expire date: (.*?)\n.*?common name: (.*?)\n.*?issuer: CN=(.*?)\n‘, f.getvalue(), re.S) start_date = m.group(1) expire_date = m.group(2) common_name = m.group(3) issuer = m.group(4) # time 字符串转时间数组 start_date = time.strptime(start_date, "%b %d %H:%M:%S %Y GMT") start_date_st = time.strftime("%Y-%m-%d %H:%M:%S", start_date) # datetime 字符串转时间数组 expire_date = datetime.strptime(expire_date, "%b %d %H:%M:%S %Y GMT") expire_date_st = datetime.strftime(expire_date, "%Y-%m-%d %H:%M:%S") # 剩余天数 remaining = (expire_date - datetime.now()).days print(‘域名:‘, "domain") print(‘通用名:‘, common_name) print(‘开始时间:‘, start_date_st) print(‘到期时间:‘, expire_date_st) print(f‘剩余时间: {remaining}天‘) print(‘颁发机构:‘, issuer) print(‘*‘ * 30) # 修改域名信息 domain.remaining_time = remaining domain.ssl_expire_date = expire_date_st domain.ssl_start_date = start_date_st domain.save() f.close() time.sleep(0.5)
视图信息 video/ssl_check/views.py
from django.shortcuts import render,reverse,redirect import re import time import subprocess from datetime import datetime from io import StringIO from django.views import View from .models import Domain from .tasks import * from django.http import JsonResponse ‘‘‘ 域名的增、删、查询 的视图 包括可以手动调取更新域名 ssl 证书信息的视图:CheckSslView ‘‘‘ class DomainRegisterView(View): def get(self, request): return render(request, "add_domain.html") class AddDomainView(View): def post(self, request): domain_name = request.POST.get("domain_name", "") Domain.objects.create( name=domain_name ) return redirect(reverse(‘list_domain‘)) class ListDomainView(View): def get(self, request): domains = Domain.objects.all() return render(request, ‘list_domains.html‘, {"domain_list":domains}) class DeleteDomainView(View): def get(self, request): domain_id = request.GET.get("domain_id", "") print("domain_id={}".format(domain_id)) Domain.objects.filter(id=domain_id).delete() return redirect(reverse(‘list_domain‘)) class DoChecksslTaskView(View): def get(self, request, *args, **kwargs): # 执行异步任务 print(‘start to check domain ssl‘) # modify_domain_ssl_info_task.delay() # modify_domain_ssl_info_task.apply_async(args=(‘check‘,), queue=‘work_queue‘) res = modify_domain_ssl_info_task.delay() print(‘end do check domain ssl‘) # 任务逻辑 return JsonResponse({‘status‘: ‘successful‘, ‘task_id‘: res.task_id}) # 这个是测试任务,可以通过django的web直接访问触发 class CetestView(View): def get(self, request, *args, **kwargs): res = print_test.delay() # 任务逻辑 return JsonResponse({‘status‘: ‘successful‘, ‘task_id‘: res.task_id}) # 可以通过web页面直接触发任务 class CheckSslView(View): def get(self, request): domains = Domain.objects.all() for domain in domains: self.check_ssl(domain) return redirect(reverse(‘list_domain‘)) def check_ssl(self, domain): print("domain_name",domain.name) print(type(domain)) f = StringIO() comm = f"curl -Ivs https://{domain.name} --connect-timeout 10" print("comm:", comm) result = subprocess.getstatusoutput(comm) f.write(result[1]) m = re.search(‘start date: (.*?)\n.*?expire date: (.*?)\n.*?common name: (.*?)\n.*?issuer: CN=(.*?)\n‘, f.getvalue(), re.S) start_date = m.group(1) expire_date = m.group(2) common_name = m.group(3) issuer = m.group(4) # time 字符串转时间数组 start_date = time.strptime(start_date, "%b %d %H:%M:%S %Y GMT") start_date_st = time.strftime("%Y-%m-%d %H:%M:%S", start_date) # datetime 字符串转时间数组 expire_date = datetime.strptime(expire_date, "%b %d %H:%M:%S %Y GMT") expire_date_st = datetime.strftime(expire_date, "%Y-%m-%d %H:%M:%S") # 剩余天数 remaining = (expire_date - datetime.now()).days print(‘域名:‘, "domain") print(‘通用名:‘, common_name) print(‘开始时间:‘, start_date_st) print(‘到期时间:‘, expire_date_st) print(f‘剩余时间: {remaining}天‘) print(‘颁发机构:‘, issuer) print(‘*‘ * 30) # 修改域名信息 domain.remaining_time = remaining domain.ssl_expire_date = expire_date_st domain.ssl_start_date = start_date_st domain.save() f.close() time.sleep(0.5)
4.模板文件
# 添加页面
templates/add_domain.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>add</title> </head> <body> <form action="/domain/add/" method="post"> {% csrf_token %} 域名:<input type="text" name="domain_name" /> <br/> <input type="submit" value="提交"> </form> </body> </html>
# 列出域名信息页面
templates/list_domain.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>域名列表</title> </head> <body> <table border="1" style="solid-color: black" cellpadding="0" cellspacing="0" width="30%"> <tr> <th>域名</th> <th>状态</th> <th>证书启用时间</th> <th>证书过期时间</th> <th>证书剩余过期时间</th> <th>操作</th> </tr> {% for domain in domain_list %} <tr> <td>{{ domain.name }}</td> <td>{{ domain.status }}</td> <td>{{ domain.ssl_expire_date }}</td> <td>{{ domain.ssl_start_date }}</td> <td>{{ domain.remaining_time }}</td> <td> <a href="/domain/delete/?domain_id={{ domain.id }}">删除</a></td> </tr> {% endfor %} </table> </body> </html>
页面
这里的delay方法就是异步方式请求,而非django默认的同步执行步骤
在manage.py的目录下启动celery服务
windows 中启动方式
(python37_django2) D:\python\django_imooc_xiaobai\muke_vedio_test\video>celery worker -A config -l info -P eventlet -------------- celery@SZ18052967C01 v4.4.2 (cliffs) --- ***** ----- -- ******* ---- Windows-10-10.0.19041-SP0 2021-02-23 17:03:40 - *** --- * --- - ** ---------- [config] - ** ---------- .> app: config:0x1ca2a3bf128 - ** ---------- .> transport: redis://localhost:6379/1 - ** ---------- .> results: redis://localhost:6379/2 - *** --- * --- .> concurrency: 8 (eventlet) -- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker) --- ***** ----- -------------- [queues] .> celery exchange=celery(direct) key=celery [tasks] . ssl_check.tasks.print_test [2021-02-23 17:03:40,585: INFO/MainProcess] Connected to redis://localhost:6379/1 [2021-02-23 17:03:40,604: INFO/MainProcess] mingle: searching for neighbors [2021-02-23 17:03:41,757: INFO/MainProcess] mingle: all alone [2021-02-23 17:03:41,777: INFO/MainProcess] pidbox: Connected to redis://localhost:6379/1. [2021-02-23 17:03:41,782: WARNING/MainProcess] c:\users\ws\envs\python37_django2\lib\site-packages\celery\fixups\django.py:203: UserWarning: Using settings.DEBUG leads to a memory leak, never use this setting in production environments! leak, never use this setting in production environments!‘‘‘) [2021-02-23 17:03:41,782: INFO/MainProcess] celery@SZ18052967C01 ready. [2021-02-23 17:03:41,784: INFO/MainProcess] Received task: ssl_check.tasks.print_test[383d9f23-1224-481f-8a3e-364b07dc2dc8] [2021-02-23 17:03:41,785: WARNING/MainProcess] nict try [2021-02-23 17:03:41,788: INFO/MainProcess] Received task: ssl_check.tasks.print_test[0959e985-d4ee-4439-8dd7-0a0e2377e610] [2021-02-23 17:03:41,790: WARNING/MainProcess] nict try [2021-02-23 17:03:41,792: INFO/MainProcess] Task ssl_check.tasks.print_test[383d9f23-1224-481f-8a3e-364b07dc2dc8] succeeded in 0.0s: ‘hello‘ [2021-02-23 17:03:41,793: INFO/MainProcess] Task ssl_check.tasks.print_test[0959e985-d4ee-4439-8dd7-0a0e2377e610] succeeded in 0.0s: ‘hello‘ [2021-02-23 17:03:41,793: INFO/MainProcess] Received task: ssl_check.tasks.print_test[f418314a-a01e-48df-834f-79220526eca2] [2021-02-23 17:03:41,794: WARNING/MainProcess] nict try [2021-02-23 17:03:41,795: INFO/MainProcess] Task ssl_check.tasks.print_test[f418314a-a01e-48df-834f-79220526eca2] succeeded in 0.0s: ‘hello‘ [2021-02-23 17:03:41,795: INFO/MainProcess] Received task: ssl_check.tasks.print_test[a6b95ed9-7a37-49ff-8445-2db250fd402b] [2021-02-23 17:03:41,796: WARNING/MainProcess] nict try [2021-02-23 17:03:41,796: INFO/MainProcess] Task ssl_check.tasks.print_test[a6b95ed9-7a37-49ff-8445-2db250fd402b] succeeded in 0.0s: ‘hello‘ [2021-02-23 17:03:41,797: INFO/MainProcess] Received task: ssl_check.tasks.print_test[0d18e1f3-7af3-4776-903b-97517c4d7d81] [2021-02-23 17:03:41,798: WARNING/MainProcess] nict try [2021-02-23 17:03:41,799: INFO/MainProcess] Task ssl_check.tasks.print_test[0d18e1f3-7af3-4776-903b-97517c4d7d81] succeeded in 0.0s: ‘hello‘ [2021-02-23 17:03:41,799: INFO/MainProcess] Received task: ssl_check.tasks.print_test[1b415bd2-e7da-49cc-810c-57c99f1b80c1] [2021-02-23 17:03:41,800: WARNING/MainProcess] nict try [2021-02-23 17:03:41,800: INFO/MainProcess] Task ssl_check.tasks.print_test[1b415bd2-e7da-49cc-810c-57c99f1b80c1] succeeded in 0.0s: ‘hello‘
linux中调用
celery -A config worker -l info
在浏览器中调用异步服务接口
127.0.0.1:8000/domain/ssl/
同时也可以在backend中查询任务结果
注意一点,redis中的key并不是单纯的task_id,而是需要加上前缀celery-task-meta-
最后,如果需要启动定时任务,就需要在manage.py所在的文件夹内单独启动beat服务
windows中调用方法
(python37_django2) D:\python\django_imooc_xiaobai\muke_vedio_test\video>celery -A config beat -l info celery beat v4.4.2 (cliffs) is starting. __ - ... __ - _ LocalTime -> 2021-02-23 17:15:23 Configuration -> . broker -> redis://localhost:6379/1 . loader -> celery.loaders.app.AppLoader . scheduler -> celery.beat.PersistentScheduler . db -> celerybeat-schedule . logfile -> [stderr]@%INFO . maxinterval -> 5.00 minutes (300s) [2021-02-23 17:15:23,454: INFO/MainProcess] beat: Starting... [2021-02-23 17:15:28,555: INFO/MainProcess] Scheduler: Sending due task task-one (ssl_check.tasks.print_test)
linux中调用
celery -A config beat -l info