feat: add new docker-compose solution

pull/314/head
Zero 2021-05-15 21:27:14 +08:00
parent fdea7a6e4e
commit af2d92d030
17 changed files with 780 additions and 1 deletions

3
.gitignore vendored
View File

@ -1 +1,2 @@
/.idea/
.idea
.env

17
docker/.env.example Normal file
View File

@ -0,0 +1,17 @@
VERSION=2.3.16
CONFIG_DIR=./config
VOLUME_DIR=./data
SECRET_KEY=CHANGE_SECRET_KEY
DB_HOST=mysql
DB_PORT=3306
DB_USER=spug
DB_PASSWORD=CHANGE_DB_PASSWORD
DB_NAME=spug
DB_ROOT_PASSWORD_USER=CHANGE_DB_ROOT_PASSWORD
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=CHANGE_REDIS_PASSWORD

21
docker/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Eason
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

25
docker/README.md Normal file
View File

@ -0,0 +1,25 @@
# [Spug Docker Compose](https://github.com/whatwewant/spug-docker-compose)
![https://hub.docker.com/repository/docker/whatwewant/spug](https://img.shields.io/docker/v/whatwewant/spug)
![](https://img.shields.io/badge/docker%20build-automated-066da5)
### Getting Started
* Solution 1: Automatically Startup
* Run `./start.sh`
* Solution 2: Manual Startup (RECOMMEND)
* Step 1
* Copy `.env.example` to `.env`, then update environments
* Step 2
* Run `docker-compose up`
### Version Histories
* v2.3.16
### FAQ
* How to build fast
* Use Docker Image instead of building in `docker-compose.yml`
* How to connect exist existing mysql/redis ?
* Just update your `.env`
### License
[MIT](./LICENSE)

80
docker/build/Dockerfile Normal file
View File

@ -0,0 +1,80 @@
# Copyright: (c) OpenSpug Organization. https://github.com/openspug/spug
# Copyright: (c) <spug.dev@gmail.com>
# Released under the AGPL-3.0 License.
# Git
FROM alpine/git:v2.30.1 as git
ARG VERSION=v2.3.16
WORKDIR /
RUN git clone https://github.com/openspug/spug.git --depth 1 -b ${VERSION} spug
# Build Front
FROM node:14-alpine as builder-front
COPY --from=git /spug /spug
WORKDIR /spug/spug_web/
RUN yarn
RUN yarn run build
# Build Backend
FROM python:3.9.4-alpine3.13 as builder
RUN echo -e "http://mirrors.aliyun.com/alpine/v3.13/main\nhttp://mirrors.aliyun.com/alpine/v3.13/community" > /etc/apk/repositories \
&& apk update
RUN pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/
RUN apk add --no-cache \
bash \
git \
nginx \
supervisor \
openssh-client \
openldap-dev \
mariadb-dev # mysql_config for python mysqlclient
RUN apk add --no-cache --virtual .build-deps \
gcc \
make \
musl-dev \
libressl-dev \
libffi-dev \
rust \
cargo # python cryptography
ADD spug.ini /etc/supervisor.d/spug.ini
ADD default.conf /etc/nginx/conf.d/default.conf
ADD entrypoint.sh /entrypoint.sh
ADD create_admin /usr/bin/create_admin
ADD delete_admin /usr/bin/delete_admin
ADD generate_secret_key /usr/bin/generate_secret_key
COPY --from=git /spug /spug
COPY --from=builder-front /spug/spug_web/build /var/www/build
WORKDIR /spug/spug_api/
RUN pip install -r requirements.txt
RUN pip install gunicorn mysqlclient
# Clean
# 1. Build Dependencies
RUN apk del .build-deps
# 2. Cargo
RUN rm -rf /root/.cargo
# PATCH
COPY patches/overrides.py /spug/spug_api/spug/overrides.py
COPY patches/user.py /spug/spug_api/apps/account/management/commands/user.py
# COPY patches/settings.py /spug/spug_api/spug/settings.py
ENTRYPOINT ["sh", "/entrypoint.sh"]

31
docker/build/create_admin Executable file
View File

@ -0,0 +1,31 @@
#!/bin/sh
CMD="create_admin" #$0
USERNAME=$1
PASSWORD=$2
NICKNAME=${3:-ADMIN}
help() {
echo "How to:"
echo " $CMD YOUR_USERNAME YOUR_PASSWORD [YOUR_NICKNAME]"
echo ""
echo "Example:"
echo " $CMD admin spug.dev"
echo " $CMD admin spug.dev Zero"
echo ""
}
if [ "$1" = "" ] || [ "$2" = "" ]; then
echo "ERROR: username and password are required."
echo ""
help
exit
fi
# @DEPRIATED `useradd` instead of `user add`
# python manage.py useradd -u admin -p spug.dev -s -n 管理员
#
#
python manage.py user add -u $USERNAME -p ${PASSWORD} -s -n ${NICKNAME}

28
docker/build/default.conf Normal file
View File

@ -0,0 +1,28 @@
server {
listen 80;
server_name _; # 修改为自定义的访问域名
root /var/www/build/;
location ^~ /api/ {
rewrite ^/api(.*) $1 break;
proxy_pass http://127.0.0.1:9001;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location ^~ /api/ws/ {
rewrite ^/api(.*) $1 break;
proxy_pass http://127.0.0.1:9002;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
error_page 404 /index.html;
}

28
docker/build/delete_admin Executable file
View File

@ -0,0 +1,28 @@
#!/bin/sh
CMD="delete_admin" #$0
USERNAME=$1
help() {
echo "How to:"
echo " $CMD ADMIN_USERNAME"
echo ""
echo "Example:"
echo " $CMD admin"
echo ""
}
if [ "$1" = "" ]; then
echo "ERROR: username are required."
echo ""
help
exit
fi
# @DEPRIATED `useradd` instead of `user add`
# python manage.py useradd -u admin -p spug.dev -s -n 管理员
#
#
python manage.py user del -u $USERNAME

View File

@ -0,0 +1,25 @@
#!/bin/sh
# Copyright: (c) OpenSpug Organization. https://github.com/openspug/spug
# Copyright: (c) <spug.dev@gmail.com>
# Released under the AGPL-3.0 License.
set -e
# init nginx
if [ ! -d /run/nginx ]; then
mkdir -p /run/nginx
chown -R nginx.nginx /run/nginx
fi
# @TODO BUG, this will run every startup,
# because /spug/spug_api/db.sqlite3 has never been generated.
# init spug
if [ ! -f /spug/spug_api/db.sqlite3 ]; then
cd /spug/spug_api
python manage.py initdb
# python manage.py useradd -u admin -p spug.dev -s -n 管理员
create_admin admin spug.dev
fi
nginx
supervisord -c /etc/supervisord.conf

View File

@ -0,0 +1,3 @@
#!/bin/sh
openssl rand -base64 36

View File

@ -0,0 +1,64 @@
import os
# Environment
MYSQL_CONFIG = {
"HOST": os.getenv('DB_HOST', '127.0.0.1'),
"PORT": os.getenv('DB_PORT', '3306'),
"USER": os.getenv('DB_USER', 'spug'),
"PASSWORD": os.getenv('DB_PASSWORD', 'spug.dev'),
"DATABASE": os.getenv('DB_DATABASE', 'spug'),
}
REDIS_CONFIG = {
"HOST": os.getenv('REDIS_HOST', '127.0.0.1'),
"PORT": os.getenv('REDIS_PORT', '6379'),
"PASSWORD": os.getenv('REDIS_PASSWORD', ''),
"DB0": os.getenv('REDIS_DATABASE_0', '0'),
"DB1": os.getenv('REDIS_DATABASE_1', '1'),
}
# Configuration
DEBUG = False
ALLOWED_HOSTS = ['127.0.0.1']
SECRET_KEY = os.getenv('SECRET_KEY', 'SHOULD_BE_OVERRODE')
DATABASES = {
'default': {
'ATOMIC_REQUESTS': True,
'ENGINE': 'django.db.backends.mysql',
'NAME': MYSQL_CONFIG['DATABASE'],
'USER': MYSQL_CONFIG['USER'],
'PASSWORD': MYSQL_CONFIG['PASSWORD'],
'HOST': MYSQL_CONFIG['HOST'],
'PORT': MYSQL_CONFIG['PORT'],
'OPTIONS': {
# 'unix_socket': '/var/lib/mysql/mysql.sock',
'charset': 'utf8mb4',
'sql_mode': 'STRICT_TRANS_TABLES',
}
}
}
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://" + REDIS_CONFIG['HOST'] + ":" + REDIS_CONFIG['PORT'] + "/" + REDIS_CONFIG['DB1'],
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"PASSWORD": REDIS_CONFIG['PASSWORD'],
}
}
}
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("redis://:" + REDIS_CONFIG['PASSWORD'] + "@" + REDIS_CONFIG['HOST'] + ":" + REDIS_CONFIG['PORT'] + "/" + REDIS_CONFIG['DB0'])],
"capacity": 1000,
"expiry": 120,
},
},
}

View File

@ -0,0 +1,141 @@
"""
# Copyright: (c) OpenSpug Organization. https://github.com/openspug/spug
# Copyright: (c) <spug.dev@gmail.com>
# Released under the AGPL-3.0 License.
Django settings for spug project.
Generated by 'django-admin startproject' using Django 2.2.7.
For more information on this file, see
https://docs.djangoproject.com/en/2.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.2/ref/settings/
"""
import os
import re
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'vk0do47)egwzz!uk49%(y3s(fpx4+ha@ugt-hcv&%&d@hwr&p7'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ['127.0.0.1']
# Application definition
INSTALLED_APPS = [
'channels',
'apps.account',
'apps.host',
'apps.setting',
'apps.exec',
'apps.schedule',
'apps.monitor',
'apps.alarm',
'apps.config',
'apps.app',
'apps.deploy',
'apps.notify',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.middleware.common.CommonMiddleware',
'libs.middleware.AuthenticationMiddleware',
'libs.middleware.HandleExceptionMiddleware',
]
ROOT_URLCONF = 'spug.urls'
WSGI_APPLICATION = 'spug.wsgi.application'
ASGI_APPLICATION = 'spug.routing.application'
# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
DATABASES = {
'default': {
'ATOMIC_REQUESTS': True,
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
REDIS_CONFIG = {
"HOST": os.getenv('REDIS_HOST') or '127.0.0.1',
"PORT": os.getenv('REDIS_PORT') or '6379',
"PASSWORD": os.getenv('REDIS_PASSWORD') or '',
"DB0": os.getenv('REDIS_DATABASE_0') or '0',
"DB1": os.getenv('REDIS_DATABASE_1') or '1',
}
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://" + REDIS_CONFIG['HOST'] + ":" + REDIS_CONFIG['PORT'] + "/" + REDIS_CONFIG['DB1'],
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"PASSWORD": REDIS_CONFIG['PASSWORD'],
}
}
}
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("redis://:" + REDIS_CONFIG['PASSWORD'] + "@" + REDIS_CONFIG['HOST'] + ":" + REDIS_CONFIG['PORT'] + "/" + REDIS_CONFIG['DB0'])],
"capacity": 1000,
"expiry": 120,
},
},
}
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': False,
},
]
SCHEDULE_KEY = 'spug:schedule'
MONITOR_KEY = 'spug:monitor'
REQUEST_KEY = 'spug:request'
REPOS_DIR = os.path.join(BASE_DIR, 'repos')
# Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = True
AUTHENTICATION_EXCLUDES = (
'/account/login/',
re.compile('/apis/.*'),
)
SPUG_VERSION = 'v2.3.16'
# override default config
try:
from spug.overrides import *
except ImportError:
pass

View File

@ -0,0 +1,100 @@
# Copyright: (c) OpenSpug Organization. https://github.com/openspug/spug
# Copyright: (c) <spug.dev@gmail.com>
# Released under the AGPL-3.0 License.
from django.core.management.base import BaseCommand
from django.core.cache import cache
from apps.account.models import User
class Command(BaseCommand):
help = '账户管理'
def add_arguments(self, parser):
parser.add_argument('action', type=str, help='执行动作')
parser.add_argument('-u', required=False, help='账户名称')
parser.add_argument('-p', required=False, help='账户密码')
parser.add_argument('-n', required=False, help='账户昵称')
parser.add_argument('-s', default=False, action='store_true', help='是否是超级用户(默认否)')
def echo_success(self, msg):
self.stdout.write(self.style.SUCCESS(msg))
def echo_error(self, msg):
self.stderr.write(self.style.ERROR(msg))
def print_help(self, *args):
message = '''
账户管理命令用法
user add 创建账户例如user add -u admin -p 123 -n 管理员 -s
user del 删除账户例如user del -u admin
user reset 重置账户密码例如user reset -u admin -p 123
user enable 启用被禁用的账户例如user enable -u admin
user disable 禁用的账户例如user disable -u admin
'''
self.stdout.write(message)
def handle(self, *args, **options):
action = options['action']
if action == 'add':
if not all((options['u'], options['p'], options['n'])):
self.echo_error('缺少参数')
self.print_help()
elif User.objects.filter(username=options['u'], deleted_by_id__isnull=True).exists():
self.echo_error(f'已存在登录名为【{options["u"]}】的用户')
else:
User.objects.create(
username=options['u'],
nickname=options['n'],
password_hash=User.make_password(options['p']),
is_supper=options['s'],
)
self.echo_success('创建用户成功')
elif action == 'del':
if not options['u']:
self.echo_error('缺少参数')
self.print_help()
user = User.objects.filter(username=options['u'], deleted_by_id__isnull=True).first()
if not user:
return self.echo_error(f'未找到登录名为【{options["u"]}】的账户')
user.delete()
cache.delete(user.username)
self.echo_success('账户已删除')
elif action == 'enable':
if not options['u']:
self.echo_error('缺少参数')
self.print_help()
user = User.objects.filter(username=options['u'], deleted_by_id__isnull=True).first()
if not user:
return self.echo_error(f'未找到登录名为【{options["u"]}】的账户')
user.is_active = True
user.save()
cache.delete(user.username)
self.echo_success('账户已启用')
elif action == 'disable':
if not options['u']:
self.echo_error('缺少参数')
self.print_help()
user = User.objects.filter(username=options['u'], deleted_by_id__isnull=True).first()
if not user:
return self.echo_error(f'未找到登录名为【{options["u"]}】的账户')
user.is_active = False
user.save()
cache.delete(user.username)
self.echo_success('账户已禁用')
elif action == 'reset':
if not all((options['u'], options['p'])):
self.echo_error('缺少参数')
self.print_help()
user = User.objects.filter(username=options['u'], deleted_by_id__isnull=True).first()
if not user:
return self.echo_error(f'未找到登录名为【{options["u"]}】的账户')
user.password_hash = User.make_password(options['p'])
user.save()
self.echo_success('账户密码已重置')
else:
self.echo_error('未识别的操作')
self.print_help()

40
docker/build/spug.ini Normal file
View File

@ -0,0 +1,40 @@
; # Copyright: (c) OpenSpug Organization. https://github.com/openspug/spug
; # Copyright: (c) <spug.dev@gmail.com>
; # Released under the AGPL-3.0 License.
[supervisord]
nodaemon=true
; [program:redis]
; command = bash -c 'redis-server'
; autostart = true
[program:spug-api]
command = bash /spug/spug_api/tools/start-api.sh
autostart = true
stdout_logfile = /spug/spug_api/logs/api.log
redirect_stderr = true
[program:spug-ws]
command = bash /spug/spug_api/tools/start-ws.sh
autostart = true
stdout_logfile = /spug/spug_api/logs/ws.log
redirect_stderr = true
[program:spug-worker]
command = bash /spug/spug_api/tools/start-worker.sh
autostart = true
stdout_logfile = /spug/spug_api/logs/worker.log
redirect_stderr = true
[program:spug-monitor]
command = bash /spug/spug_api/tools/start-monitor.sh
autostart = true
stdout_logfile = /spug/spug_api/logs/monitor.log
redirect_stderr = true
[program:spug-scheduler]
command = bash /spug/spug_api/tools/start-scheduler.sh
autostart = true
stdout_logfile = /spug/spug_api/logs/scheduler.log
redirect_stderr = true

105
docker/config/mysql/my.cnf Normal file
View File

@ -0,0 +1,105 @@
[mysqld]
basedir = /usr/
datadir = /var/lib/mysql
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
port = 3306
user = mysql
log_error = /var/lib/mysql/mysql-error.log
slow-query-log-file = /var/lib/mysql/mysql-slow.log
log_bin = /var/lib/mysql/mysql-bin.log
relay-log = /var/lib/mysql/mysql-relay-bin
server-id = 1
#read_only = 1
innodb_buffer_pool_size = 1024M
innodb_log_buffer_size = 16M
#key_buffer_size = 64M
key_buffer_size = 128M
query_cache_size = 256M
tmp_table_size = 128M
#lower_case_table_names = 1
binlog_format = mixed
#binlog_format = statement
skip-external-locking
skip-name-resolve
character-set-server = utf8
collation-server = utf8_bin
#collation-server = utf8_general_ci
max_allowed_packet = 16M
thread_cache_size = 256
table_open_cache = 4096
back_log = 1024
max_connect_errors = 100000
#wait_timeout = 864000
interactive_timeout = 1800
wait_timeout = 1800
max_connections = 2048
sort_buffer_size = 16M
join_buffer_size = 4M
read_buffer_size = 4M
#read_rnd_buffer_size = 8M
read_rnd_buffer_size = 16M
binlog_cache_size = 2M
thread_stack = 192K
max_heap_table_size = 128M
myisam_sort_buffer_size = 128M
bulk_insert_buffer_size = 256M
open_files_limit = 65535
query_cache_limit = 2M
slow-query-log
long_query_time = 2
expire_logs_days = 3
max_binlog_size = 1000M
slave_parallel_workers = 4
log-slave-updates
#slave-skip-errors =1062,1053,1146,1032
binlog_ignore_db = mysql
replicate_wild_ignore_table = mysql.%
sync_binlog = 1
innodb_file_per_table = 1
innodb_flush_method = O_DIRECT
innodb_buffer_pool_instances = 4
innodb_log_file_size = 512M
innodb_log_files_in_group = 3
innodb_open_files = 4000
innodb_read_io_threads = 8
innodb_write_io_threads = 8
innodb_thread_concurrency = 8
innodb_io_capacity = 2000
innodb_io_capacity_max = 6000
innodb_lru_scan_depth = 2000
innodb_max_dirty_pages_pct = 85
innodb_flush_log_at_trx_commit = 2
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
[mysqldump]
quick
quote-names
max_allowed_packet = 16M
[client]
default-character-set = utf8
[mysql]
default-character-set = utf8
[isamchk]
key_buffer = 128M
sort_buffer_size = 4M
read_buffer = 2M
write_buffer = 2M
[myisamchk]
key_buffer = 128M
sort_buffer_size = 4M
read_buffer = 2M
write_buffer = 2M

65
docker/docker-compose.yml Normal file
View File

@ -0,0 +1,65 @@
###
# 初始化: docker-compose exec spug create_admin
#
# ADMIN 用户名/密码admin/spug.dev
#
# 注意: 从 v2.3.15 开始,默认创建 ADMIN 用户
# 关于管理员:
# 你可以使用自己创建或删除其他超级管理员用户
# 创建docker-compose exec spug create_admin USERNAME PASSWORD
# 删除: docker-compose exec spug delete_admin USERNAME
#
# 关于 SECRET_KEY:
# 生成docker-compose exec spug generate_secret_key
###
version: '3.4'
services:
spug:
restart: always
build:
context: ./build
# image: whatwewant/spug:${VERSION}
ports:
- 8080:80
environment:
SECRET_KEY: $SECRET_KEY
DB_HOST: $DB_HOST
DB_PORT: $DB_PORT
DB_USER: $DB_USER
DB_PASSWORD: $DB_PASSWORD
DB_NAME: $DB_NAME
REDIS_HOST: $REDIS_HOST
REDIS_PORT: $REDIS_PORT
REDIS_PASSWORD: $REDIS_PASSWORD
depends_on:
- mysql
- redis
networks:
- spug
mysql:
restart: always
image: mysql:5.7
environment:
# MYSQL_ROOT_HOST: '0.0.0.0'
MYSQL_ROOT_PASSWORD: $DB_ROOT_PASSWORD_USER
MYSQL_USER: $DB_USER
MYSQL_PASSWORD: $DB_PASSWORD
MYSQL_DATABASE: $DB_NAME
volumes:
- ${CONFIG_DIR}/mysql/my.cnf:/etc/mysql/my.cnf
- ${VOLUME_DIR}/mysql/lib:/var/lib/mysql
networks:
- spug
redis:
image: redis:5-alpine
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- ${VOLUME_DIR}/redis:/data
networks:
- spug
networks:
spug:

5
docker/start.sh Executable file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
cp .env.example .env
docker-compose up