Skip to content

Commit a9bef44

Browse files
committed
Deploy scripts & dockerfile
1 parent 0be551e commit a9bef44

File tree

10 files changed

+262
-6
lines changed

10 files changed

+262
-6
lines changed

.dockerignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
statci/
2+
.travis.yml
3+
Dockerfile
4+
fabfile.py
5+
requirements-deploy.txt

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ celerybeat-schedule
8181
# virtualenv
8282
venv/
8383
ENV/
84+
env/
8485

8586
# Spyder project settings
8687
.spyderproject

Dockerfile

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
FROM ubuntu:16.04
2+
3+
ARG version=dev
4+
5+
RUN export DEBIAN_FRONTEND=noninteractive && \
6+
sed -i 's,http://archive.ubuntu.com/ubuntu/,mirror://mirrors.ubuntu.com/mirrors.txt,' /etc/apt/sources.list && \
7+
apt-get update -qq && apt-get upgrade -qq && \
8+
apt-get install -y --no-install-recommends \
9+
python3 \
10+
python3-setuptools \
11+
python3-pip \
12+
python3-psycopg2 \
13+
nginx \
14+
supervisor && \
15+
BUILD_DEPS='build-essential python3-dev libxml2-dev libxslt-dev python-lxml zlib1g-dev git' && \
16+
apt-get install -y --no-install-recommends ${BUILD_DEPS} && \
17+
pip3 install -U pip setuptools && \
18+
pip3 install -U uwsgi
19+
20+
RUN echo "daemon off;" >> /etc/nginx/nginx.conf
21+
COPY etc/ /etc/
22+
23+
ADD requirements.txt /opt/requirements.txt
24+
RUN pip3 install -r /opt/requirements.txt
25+
26+
COPY . /opt/app
27+
28+
WORKDIR /opt/app
29+
RUN mkdir -p /opt/staticfiles
30+
RUN python3 manage.py collectstatic --noinput
31+
32+
RUN apt-get autoremove -y ${BUILD_DEPS} \
33+
&& apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
34+
35+
EXPOSE 80
36+
CMD ["supervisord", "-n"]

etc/nginx/sites-available/default

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
upstream django {
2+
server unix:/var/lock/django-app.sock;
3+
}
4+
5+
server {
6+
listen 80 default_server;
7+
8+
server_name _;
9+
charset utf-8;
10+
11+
location /media {
12+
alias /opt/media;
13+
}
14+
15+
location /static {
16+
alias /opt/staticfiles;
17+
}
18+
19+
location / {
20+
uwsgi_pass django;
21+
include /etc/nginx/uwsgi_params;
22+
}
23+
}

etc/nginx/uwsgi_params

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
uwsgi_param QUERY_STRING $query_string;
2+
uwsgi_param REQUEST_METHOD $request_method;
3+
uwsgi_param CONTENT_TYPE $content_type;
4+
uwsgi_param CONTENT_LENGTH $content_length;
5+
6+
uwsgi_param REQUEST_URI $request_uri;
7+
uwsgi_param PATH_INFO $document_uri;
8+
uwsgi_param DOCUMENT_ROOT $document_root;
9+
uwsgi_param SERVER_PROTOCOL $server_protocol;
10+
uwsgi_param HTTPS $https if_not_empty;
11+
12+
uwsgi_param REMOTE_ADDR $remote_addr;
13+
uwsgi_param REMOTE_PORT $remote_port;
14+
uwsgi_param SERVER_PORT $server_port;
15+
uwsgi_param SERVER_NAME $server_name;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[program:app-uwsgi]
2+
command = /usr/local/bin/uwsgi --ini /etc/uwsgi.ini
3+
4+
[program:nginx-app]
5+
command = /usr/sbin/nginx

etc/uwsgi.ini

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[uwsgi]
2+
# this config will be loaded if nothing specific is specified
3+
# load base config from below
4+
ini = :base
5+
6+
# %d is the dir this configuration file is in
7+
socket = /var/lock/django-app.sock
8+
master = true
9+
processes = 4
10+
enable-threads = true
11+
logto = /var/log/uwsgi.log
12+
13+
[base]
14+
chdir = /opt/app/
15+
module=python_ru.wsgi:application
16+
chmod-socket=666

fabfile.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import os
2+
import tempfile
3+
import yaml
4+
from colors import green
5+
6+
from fabric.api import get, execute, put, roles
7+
from fabric.context_managers import cd
8+
from fabric.decorators import task
9+
from fabric.operations import run
10+
from fabric.state import env
11+
from fabric.contrib import files
12+
13+
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
14+
15+
config = {
16+
"name": "Python.ru dev deploy",
17+
18+
"build_server": "dev.python.ru",
19+
"repo_url": "git@github.com:moscowpython/python.ru.git",
20+
"app_path": "~/python.ru",
21+
"app_branch": "develop",
22+
23+
"docker_image": "korneevm/pythonru",
24+
"image_type": "dev",
25+
"image_version": "latest",
26+
27+
"deploy_server": "dev.python.ru",
28+
"compose_path": "/opt/servers",
29+
"compose_block_name": "pythonru-dev",
30+
31+
"post_deploy": "docker exec -i -t servers_pythonru-dev_1 python3 manage.py migrate --noinput"
32+
}
33+
34+
35+
env.user = "deployer"
36+
env.hosts = ["event-test.nendazfreeride.ch"]
37+
env.forward_agent = False
38+
39+
env.roledefs = {
40+
'build': [],
41+
'deploy': []
42+
}
43+
44+
45+
@task
46+
def deploy():
47+
"""
48+
Make full step-by-step deploy
49+
50+
:param config: Configuration dict, loaded by slug
51+
:return: Nothing, if deploy passed
52+
:raises: Various exceptions, if something went wrong during deploy
53+
"""
54+
env.roledefs['build'] = [config["build_server"]]
55+
env.roledefs['deploy'] = [config["deploy_server"]]
56+
execute(get_code, config)
57+
execute(build_container, config)
58+
execute(push_container, config)
59+
execute(update_compose, config)
60+
execute(reload_docker, config)
61+
62+
if config.get('post_deploy'):
63+
execute(post_deploy, config)
64+
65+
print(green("""
66+
SUCCESS
67+
"""))
68+
print("""{docker_image}:{image_type}-{image_version} ready
69+
""".format(**config))
70+
71+
72+
@roles('build')
73+
@task
74+
def get_code(config):
75+
"""
76+
Pulls update from remote repository
77+
78+
:param config: Configuration dict
79+
:return: Nothing, if process passed
80+
"""
81+
if files.exists(config["app_path"]):
82+
# Update buld repo
83+
with cd(config["app_path"]):
84+
run("git checkout {}".format(config["app_branch"]))
85+
run("git pull origin {}".format(config["app_branch"]))
86+
config["image_version"] = run("git rev-parse --short {}".format(config["app_branch"]))
87+
else:
88+
raise Exception("App repo not found")
89+
90+
91+
@roles('build')
92+
@task
93+
def build_container(config):
94+
"""
95+
Builds docker image
96+
97+
:param config: Configuration dict
98+
:return: Nothing, if process passed
99+
"""
100+
if files.exists(config["app_path"]):
101+
# Update buld repo
102+
with cd(config["app_path"]):
103+
command = "docker build --build-arg version={image_type} -t {docker_image}:{image_type}-{image_version} ."
104+
run(command.format(**config))
105+
106+
107+
@roles('build')
108+
@task
109+
def push_container(config):
110+
"""
111+
Pushes docker image to gitlab
112+
113+
:param config: Configuration dict
114+
:return: Nothing, if process passed
115+
"""
116+
if files.exists(config["app_path"]):
117+
# Update buld repo
118+
with cd(config["app_path"]):
119+
command = "docker push {docker_image}:{image_type}-{image_version}"
120+
run(command.format(**config))
121+
122+
123+
@roles('deploy')
124+
@task
125+
def update_compose(config):
126+
filename = os.path.join(config['compose_path'], "docker-compose.yml")
127+
image = "{docker_image}:{image_type}-{image_version}".format(**config)
128+
129+
with tempfile.TemporaryFile() as fd:
130+
get(filename, fd)
131+
fd.seek(0)
132+
data = yaml.load(fd.read())
133+
data['services'][config['compose_block_name']]['image'] = image
134+
fd.seek(0)
135+
fd.truncate()
136+
fd.write(yaml.dump(data).encode('utf-8'))
137+
fd.flush()
138+
put(fd, filename)
139+
140+
141+
@roles('deploy')
142+
@task
143+
def reload_docker(config):
144+
with cd(config["compose_path"]):
145+
run('docker-compose up -d')
146+
147+
148+
@roles('deploy')
149+
@task
150+
def post_deploy(config):
151+
with cd(config["compose_path"]):
152+
run(config["post_deploy"])

python_ru/settings.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
# Disable Django's own staticfiles handling in favour of WhiteNoise, for
4242
# greater consistency between gunicorn and `./manage.py runserver`. See:
4343
# http://whitenoise.evans.io/en/stable/django.html#using-whitenoise-in-development
44-
'whitenoise.runserver_nostatic',
44+
# 'whitenoise.runserver_nostatic',
4545
'django.contrib.staticfiles',
4646

4747
'apps.content',
@@ -60,7 +60,7 @@
6060
'django.contrib.auth.middleware.AuthenticationMiddleware',
6161
'django.contrib.messages.middleware.MessageMiddleware',
6262
'django.middleware.clickjacking.XFrameOptionsMiddleware',
63-
'whitenoise.middleware.WhiteNoiseMiddleware',
63+
# 'whitenoise.middleware.WhiteNoiseMiddleware',
6464
)
6565

6666
ROOT_URLCONF = 'python_ru.urls'
@@ -135,7 +135,7 @@
135135
# https://docs.djangoproject.com/en/1.8/howto/static-files/
136136

137137

138-
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
138+
STATIC_ROOT = os.path.join(BASE_DIR, '../staticfiles')
139139

140140
STATICFILES_DIRS = (
141141
os.path.join(BASE_DIR, 'assets'),
@@ -144,11 +144,11 @@
144144

145145
# Simplified static file serving.
146146
# https://warehouse.python.org/project/whitenoise/
147-
if not sys.argv[0].endswith('py.test'):
148-
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
147+
# if not sys.argv[0].endswith('py.test'):
148+
# STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
149149

150150
MEDIA_ROOT = (
151-
os.path.join(BASE_DIR, 'media'),
151+
os.path.join(BASE_DIR, '../media'),
152152
)
153153
MEDIA_URL = '/media/'
154154
CKEDITOR_UPLOAD_PATH = 'uploads/'

requirements-deploy.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fabric3
2+
PyYaml
3+
ansicolors

0 commit comments

Comments
 (0)