Skip to content

Commit a1862ba

Browse files
committed
optimization many
1 parent 41d6c7e commit a1862ba

File tree

103 files changed

+7149
-406
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

103 files changed

+7149
-406
lines changed

Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ COPY ./sources.list /etc/apt
44
ADD . /devops
55
WORKDIR /devops
66
# RUN apt-get update && apt-get install python3-dev default-libmysqlclient-dev sshpass -y
7-
RUN cd /devops && pip install -i https://pypi.douban.com/simple -r requirements.txt
7+
RUN cd /devops && pip install -i https://mirrors.aliyun.com/pypi/simple -r requirements.txt
88
RUN cd /devops && echo_supervisord_conf > /etc/supervisord.conf && cat deamon.ini >> /etc/supervisord.conf && \
99
sed -i 's/nodaemon=false/nodaemon=true/g' /etc/supervisord.conf
1010
RUN dpkg -i sshpass_1.06-1_amd64.deb

README.md

+32-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# devops
22
基于 python 3.7 + django 2.2.3 + channels 2.2.0 + celery 4.3.0 + ansible 2.8.5 + AdminLTE-3.0.0 实现的运维 devops 管理系统。具体见 `screenshots` 文件夹中的效果预览图。
3-
本人为运维工程师,非专业开发,代码写得烂,不喜勿喷,欢迎指教。功能持续完善中。
3+
本人为运维工程师,非专业开发,此项目各个功能模块都是现学现用,可能有点地方没有考虑到合理和性能的问题,代码写得烂,不喜勿喷,欢迎 issue。功能持续完善中。
44

55

66
# 部署安装
@@ -26,13 +26,14 @@ docker run --name redis-server -p 6379:6379 -d redis:latest
2626
docker run --name guacd -p 4822:4822 -d guacamole/guacd
2727
```
2828
- rdp 与 vnc 连接支持所需,非必须
29+
- windows rdp 必须设置为`允许运行任意版本远程桌面的计算机连接(较不安全)(L)`才能连接
2930

3031
**安装 python 依赖库**
3132
```bash
3233
# 安装相关库
33-
pip3 install -i https://pypi.douban.com/simple -r requirements.txt
34+
pip3 install -i https://mirrors.aliyun.com/pypi/simple -r requirements.txt
3435
```
35-
- -i 指定豆瓣源,国外源慢得一逼,我大天朝威武
36+
- -i 指定阿里源,国外源慢得一逼,我大天朝威武
3637

3738
**修改 devops/settings.py 配置**
3839

@@ -43,14 +44,17 @@ pip3 install -i https://pypi.douban.com/simple -r requirements.txt
4344
DATABASES = {
4445
'default': {
4546
'ENGINE': 'django.db.backends.mysql',
47+
# 'ENGINE': 'db_pool.mysql', # db_pool.mysql 为重写 django 官方 mysql 连接库实现了真正的连接池
4648
'NAME': 'devops',
4749
'USER':'devops',
4850
'PASSWORD':'devops',
4951
'HOST':'127.0.0.1',
5052
'PORT':'3306',
53+
'CONN_MAX_AGE': 600, # 如果使用 db_pool.mysql 尽量不要设置此参数
54+
# 数据库连接池大小,mysql 总连接数大小为:连接池大小 * 服务进程数
55+
'DB_POOL_SIZE': 20, # 默认 5 个
5156
'OPTIONS': {
5257
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
53-
# 'init_command': "SET sql_mode=''",
5458
},
5559
}
5660
}
@@ -81,12 +85,14 @@ export PYTHONOPTIMIZE=1 # 解决 celery 不允许创建子进程的问题
8185
nohup gunicorn -c gunicorn.cfg devops.wsgi:application > logs/gunicorn.log 2>&1 &
8286
nohup daphne -b 0.0.0.0 -p 8001 --access-log=logs/daphne_access.log devops.asgi:application > logs/daphne.log 2>&1 &
8387
nohup python3 manage.py sshd > logs/sshd.log 2>&1 &
84-
nohup celery -A devops worker -l info -c 3 --max-tasks-per-child 40 --prefetch-multiplier 1 > logs/celery.log 2>&1 &
88+
nohup celery -A devops worker -l info -c 3 --max-tasks-per-child 40 --prefetch-multiplier 1 --pidfile logs/celery_worker.pid > logs/celery.log 2>&1 &
89+
nohup celery -A devops beat -l info --pidfile logs/celery_worker.pid > logs/celery_beat.log 2>&1 &
8590
```
8691
- gunicorn 处理 http 请求,监听 8000 端口
8792
- daphne 处理 websocket 请求,监听 8001 端口
8893
- sshd 为 ssh 代理服务器,监听 2222 端口,提供调用 securecrt、xshell、putty 以及 winscp 客户端支持,非必须
8994
- celery 后台任务处理,`export PYTHONOPTIMIZE=1` 此环境变量非常重要,不设置无法后台运行 ansible api
95+
- celery_beat 处理 `devops/settings.py` 中设置的 `CELERY_BEAT_SCHEDULE` 定时任务
9096

9197
**nginx 前端代理**
9298
```
@@ -244,6 +250,21 @@ systemctl start nginx
244250

245251
# 升级日志
246252

253+
### ver1.8.6
254+
优化执行 playbook 逻辑:允许指定组;
255+
256+
优化终端登陆后 su 跳转逻辑:由管理员能够跳转变更为有权限就可跳转;
257+
258+
~~新增 apscheduler 在启动时执行清空 TerminalSession 表;~~
259+
260+
新增 django mysql 连接 ENGINE 优化版本:真正的支持连接池;
261+
262+
修正 clissh 使用 Zmodem 使用 sz 和 rz 时的 BUG;
263+
264+
更新 xterm.js 到 v3.14.5 版本,支持 webssh 与 webtelnet 复制文本内容;
265+
266+
- 此版本新增了一个任务调度模块,目前还是一个不完善的功能,不要太在意,仅供参考
267+
247268
### ver1.8.5
248269
新增批量操作,比如批量删除,批量更新等;
249270

@@ -294,7 +315,7 @@ systemctl start nginx
294315
优化 UI 界面,加入动态效果;
295316

296317
### ver1.7.5
297-
修正强制断开 clissh 后保存 2 次终端日志的BUG
318+
修正强制断开 clissh 后保存 2 次终端日志的 BUG
298319

299320
新增会话在一定时间内无操作自动断开功能(默认 30 分钟,settings.py 中可配置);
300321

@@ -384,6 +405,11 @@ linux 平台下使用 celery 任务保存终端会话日志与录像(windows
384405
- [ ] docker 容器管理
385406
- [ ] k8s 集群管理
386407
- [ ] 自动化部署CI/CD
408+
- [ ] webssh 与 webtelnet 的 zmodem(sz, rz) 支持
387409

410+
# MIT License
411+
```
412+
Copyright (c) 2019-2020 leffss
413+
```
388414

389415
更多新功能不断探索发现中.
9 Bytes
Binary file not shown.
1.26 KB
Binary file not shown.

apps/batch/websocket_layer.py

+9-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from channels.generic.websocket import WebsocketConsumer
22
from django.conf import settings
33
from server.models import RemoteUserBindHost
4+
from user.models import User
45
from django.db.models import Q
56
from asgiref.sync import async_to_sync
67
from util.tool import gen_rand_char
@@ -20,7 +21,7 @@
2021

2122

2223
def get_hosts(issuperuser, ids, username):
23-
if issuperuser:
24+
if issuperuser and username == 'admin':
2425
return RemoteUserBindHost.objects.filter(
2526
Q(enabled=True),
2627
Q(id__in=ids.split(',')),
@@ -138,7 +139,7 @@ def receive(self, text_data=None, bytes_data=None):
138139
user=self.session.get('username'),
139140
user_agent=self.user_agent,
140141
client=self.client,
141-
issuperuser=self.session.get('issuperuser', False),
142+
issuperuser=True if '登陆后su跳转超级用户' in self.session[settings.INIT_PERMISSION]['titles'] else False,
142143
) # 执行
143144

144145
else:
@@ -270,7 +271,7 @@ def receive(self, text_data=None, bytes_data=None):
270271
user=self.session.get('username'),
271272
user_agent=self.user_agent,
272273
client=self.client,
273-
issuperuser=self.session.get('issuperuser', False),
274+
issuperuser=True if '登陆后su跳转超级用户' in self.session[settings.INIT_PERMISSION]['titles'] else False,
274275
) # 执行
275276

276277
else:
@@ -410,7 +411,7 @@ def receive(self, text_data=None, bytes_data=None):
410411
user=self.session.get('username'),
411412
user_agent=self.user_agent,
412413
client=self.client,
413-
issuperuser=self.session.get('issuperuser', False),
414+
issuperuser=True if '登陆后su跳转超级用户' in self.session[settings.INIT_PERMISSION]['titles'] else False,
414415
) # 执行
415416
else:
416417
self.message['status'] = 1
@@ -529,6 +530,8 @@ def receive(self, text_data=None, bytes_data=None):
529530
hostinfo['port'] = host.port
530531
hostinfo['username'] = host.remote_user.username
531532
hostinfo['password'] = host.remote_user.password
533+
user = User.objects.get(id=int(self.session.get('userid')))
534+
hostinfo['groups'] = [x.group_name for x in host.host_group.filter(user=user)]
532535
if host.remote_user.enabled:
533536
hostinfo['superusername'] = host.remote_user.superusername
534537
hostinfo['superpassword'] = host.remote_user.superpassword
@@ -541,7 +544,7 @@ def receive(self, text_data=None, bytes_data=None):
541544
user=self.session.get('username'),
542545
user_agent=self.user_agent,
543546
client=self.client,
544-
issuperuser=self.session.get('issuperuser', False),
547+
issuperuser=True if '登陆后su跳转超级用户' in self.session[settings.INIT_PERMISSION]['titles'] else False,
545548
) # 执行
546549
else:
547550
self.message['status'] = 1
@@ -672,7 +675,7 @@ def receive(self, text_data=None, bytes_data=None):
672675
user=self.session.get('username'),
673676
user_agent=self.user_agent,
674677
client=self.client,
675-
issuperuser=self.session.get('issuperuser', False),
678+
issuperuser=True if '登陆后su跳转超级用户' in self.session[settings.INIT_PERMISSION]['titles'] else False,
676679
) # 执行
677680
else:
678681
self.message['status'] = 1

apps/scheduler/__init__.py

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from os import path
2+
from django.apps import AppConfig
3+
4+
VERBOSE_APP_NAME = "scheduler"
5+
6+
7+
def get_current_app_name(file):
8+
return path.dirname(file).replace('\\', '/').split('/')[-1]
9+
10+
11+
class AppVerboseNameConfig(AppConfig):
12+
name = get_current_app_name(__file__)
13+
verbose_name = '任务调度'
14+
15+
16+
default_app_config = get_current_app_name(__file__) + '.__init__.AppVerboseNameConfig'

apps/scheduler/admin.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from django.contrib import admin
2+
3+
# Register your models here.

apps/scheduler/apps.py

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from django.apps import AppConfig
2+
3+
4+
class SchedulerConfig(AppConfig):
5+
name = 'scheduler'

apps/scheduler/migrations/__init__.py

Whitespace-only changes.

apps/scheduler/models.py

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from django.db import models
2+
3+
# Create your models here.
4+
5+
6+
class SchedulerHost(models.Model):
7+
PROTOCOL_CHOICES = (
8+
(1, 'http'),
9+
(2, 'https'),
10+
)
11+
hostname = models.CharField(max_length=128, blank=True, null=True, verbose_name='主机名')
12+
ip = models.GenericIPAddressField(verbose_name='主机IP')
13+
protocol = models.SmallIntegerField(default=2, choices=PROTOCOL_CHOICES, verbose_name='协议')
14+
port = models.SmallIntegerField(default=443, verbose_name='端口')
15+
token = models.CharField(max_length=512, verbose_name='token')
16+
status = models.BooleanField(default=False, verbose_name='状态')
17+
cron = models.IntegerField(default=0, verbose_name='cron任务数')
18+
interval = models.IntegerField(default=0, verbose_name='interval任务数')
19+
date = models.IntegerField(default=0, verbose_name='date任务数')
20+
executed = models.IntegerField(default=0, verbose_name='总共执行任务数')
21+
failed = models.IntegerField(default=0, verbose_name='总共失败任务数')
22+
memo = models.TextField(blank=True, null=True, verbose_name='备注')
23+
update_time = models.DateTimeField('更新时间', blank=True, null=True, auto_now=True)
24+
create_time = models.DateTimeField('添加时间', auto_now_add=True)
25+
26+
def __str__(self):
27+
return self.hostname
28+
29+
class Meta:
30+
ordering = ["-create_time"]
31+
verbose_name = '调度主机'
32+
verbose_name_plural = '调度主机'
33+
unique_together = ('ip', 'port')

apps/scheduler/tests.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from django.test import TestCase
2+
3+
# Create your tests here.

apps/scheduler/urls.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from django.urls import path
2+
from . import views
3+
4+
5+
app_name = "scheduler"
6+
urlpatterns = [
7+
path('hosts/', views.hosts, name='hosts'),
8+
path('host/<int:host_id>/', views.host, name='host'),
9+
path('host/<int:host_id>/jobs/', views.jobs, name='jobs'),
10+
]

apps/scheduler/urls_api.py

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from django.urls import path
2+
from . import views_api
3+
4+
5+
app_name = "scheduler"
6+
urlpatterns = [
7+
path('client/upload/', views_api.client_upload, name='client_upload'),
8+
]

apps/scheduler/views.py

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
from django.shortcuts import render, get_object_or_404
2+
from util.tool import login_required
3+
from .models import SchedulerHost
4+
from django.db.models import Q
5+
from ratelimit.decorators import ratelimit # 限速
6+
from ratelimit import ALL
7+
from util.rate import rate, key
8+
import requests
9+
import urllib3
10+
import json
11+
import traceback
12+
urllib3.disable_warnings()
13+
# Create your views here.
14+
15+
16+
@ratelimit(key=key, rate=rate, method=ALL, block=True)
17+
@login_required
18+
def hosts(request):
19+
hosts = SchedulerHost.objects.all()
20+
return render(request, 'scheduler/hosts.html', locals())
21+
22+
23+
@ratelimit(key=key, rate=rate, method=ALL, block=True)
24+
@login_required
25+
def host(request, host_id):
26+
host = get_object_or_404(
27+
SchedulerHost,
28+
pk=host_id,
29+
)
30+
return render(request, 'scheduler/host.html', locals())
31+
32+
33+
@ratelimit(key=key, rate=rate, method=ALL, block=True)
34+
@login_required
35+
def jobs(request, host_id):
36+
host = get_object_or_404(
37+
SchedulerHost,
38+
pk=host_id,
39+
)
40+
retry = 2
41+
timeout = 5
42+
attempts = 0
43+
success = False
44+
jobs = []
45+
while attempts <= retry and not success:
46+
try:
47+
headers = dict()
48+
headers['AUTHORIZATION'] = host.token
49+
headers['user-agent'] = 'requests/devops'
50+
url = '{protocol}://{ip}:{port}{url}'.format(
51+
protocol=host.get_protocol_display(), ip=host.ip, port=host.port, url='/api/job/lists'
52+
)
53+
res = requests.get(
54+
url=url,
55+
headers=headers,
56+
timeout=timeout,
57+
allow_redirects=True,
58+
verify=False,
59+
)
60+
if res.status_code == 200:
61+
r = json.loads(res.text)
62+
if r['status'] == 0:
63+
jobs = r['data']
64+
success = True
65+
else:
66+
attempts += 1
67+
else:
68+
attempts += 1
69+
except Exception as e:
70+
print(traceback.format_exc())
71+
attempts += 1
72+
return render(request, 'scheduler/jobs.html', {'host': host, 'jobs': jobs})

0 commit comments

Comments
 (0)