Devops
with Django Docker & Gitlab
whoami
Luke Spademan
0) Student
1) starting A levels
3) gave a lightening talk last year
2) @lukespademan: [twitter, git, etc]
- Maths
- Further Maths
- Computer Science
- Physics
Devops?
What is Devops?
Deops is trying to automate stuff you would normaly have to do manually.
Benifits
- Saves you time
- Stop you making mistakes
- Satisfying
Docker?
What is Docker
- Docker allows you to containerise your code
- Like a stripped down virtual machine
- If they have docker installed there should be no issue running your code
Example
Python
Python
pip install pipenv --user
mkdir project
cd project
mkdir src
cd src
pipenv python --3.6 # creates the virtual environment
pipenv install django gunicorn psycopg2-binary # installs pakcages to venv
pipenv shell # enteres the virtual environment
django-admin startproject mysite .
django-admin startproject mysite .
Python
# src/mysite/settings.py
# ...
try:
SECRET_KEY
except NameError:
SECRET_FILE = os.path.join(BASE_DIR, 'secret.txt')
try:
SECRET_KEY = open(SECRET_FILE).read().strip()
except IOError:
try:
import random
SECRET_KEY = ''.join([random.SystemRandom().choice(
'abcdefghijklmnopqrstuvxyz0123456789!@#$%^&*(-_=+)') for i in range(50)]
)
secret = open(SECRET_FILE, "w")
secret.write(SECRET_KEY)
secret.close()
except IOError:
Exception("Please create a %s file with random characters." % SECRET_FILE)
# ...
Python
# src/mysite/settings.py
# ...
# SECURITY WARNING: don't run with debug turned on in production!
if os.environ.get('DJANGO_DEBUG'):
print("Debug is enabled.")
DEBUG = True
ALLOWED_HOSTS = ["localhost", "127.0.0.1"]
else:
DEBUG = False
ALLOWED_HOSTS = ["example.com", "localhost"]
# ...
Python
# src/mysite/settings.py
# ...
else:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'postgres',
'USER': 'postgres',
'HOST': 'db',
'PORT': 5432,
}
}
# ...
if DEBUG == True:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
Python
# src/mysite/settings.py
# ...
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.0/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, "static/")
File Tree
.
└── src
├── db.sqlite3
├── main.py
├── manage.py
├── mysite
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── Pipfile
├── Pipfile.lock
└── secret.txt
Test
cd src
pipenv shell
DJANGO_DEBUG=True python manage.py makemigrations
DJANGO_DEBUG=True python manage.py migrate
DJANGO_DEBUG=True python manage.py runserver
localhost:8000
Hello, PyCon UK!
hello_world app
python manage.py startapp hello_world
# src/hello_world/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
]
# src/hello_world/views.py
from django.shortcuts import render
def index(request):
return render(request, 'hello_world/index.html')
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello, PyCon UK!</title>
</head>
<body>
<h1>Hello, PyCon UK!</h1>
</body>
</html>
# src/hello_world/templates/hello_world/index.html
Update settings.py
# src/mysite/settings.py
...
INSTALLED_APPS = [
'hello_world',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
...
Update urls.py
# src/mysite/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('hello_world.urls')),
]
Docker
Dockerfile
# Dockerfile
FORM python:3.6
RUN mkdir /src
ADD src/Pipfile /src
ADD src/Pipfile.lock /src
RUN pip install pipenv
WORKDIR /src
RUN pipenv install --system --deploy --ignore-pipfile
ADD src /src
Docker Compose
# docker-compose.yml
version: '3'
services:
nginx:
image: registry.gitlab.com/lukespademan/mysite/nginx:latest
build:
context: .
dockerfile: Dockerfile.nginx
container_name: mysite_nginx
depends_on:
- web
ports:
- "80:8080"
web:
image: registry.gitlab.com/lukespademan/mysite:latest
build: .
container_name: mysite_django
command: bash start_django.sh
depends_on:
- db
db:
image: postgres:latest
container_name: mysite_postgres
volumes:
- "db:/var/lib/postgresql/data"
volumes:
db: {}
services:
nginx:
build: Dockerfile.nginx
container_name: mysite_nginx
depends_on:
- web
ports:
- "80:8080"
web:
build: .
container_name: mysite_django
command: bash start_django.sh
depends_on:
- db
db:
image: postgres:latest
container_name: mysite_postgres
volumes:
- "db:/var/lib/postgresql/data"
volumes:
db: {}
Start Django
# src/start_django.sh
#!/bin/bash
ls
cat Pipfile
pip freeze
python manage.py migrate
python manage.py collectstatic --noinput
gunicorn mysite.wsgi -b 0.0.0.0:8000
Dockerfile.nginx
FROM nginx:latest
ADD config/nginx /etc/nginx/conf.d
ADD src /src
EXPOSE 80
Nginx Config
mkdir config
cd config
mkdir nginx
cd nginx
# config/nginx/my_django.conf
upstream web {
ip_hash;
server web:8000;
}
upstream web {
ip_hash;
server web:8000;
}
server {
location /robots.txt {alias /src/static/robots.txt;}
location /favicon.ico {alias /src/static/favicon.ico;}
location /static/ {
autoindex on;
alias /src/static/;
}
location / {
proxy_pass http://web/;
}
listen 8080;
}
File Tree
.
└── config
│ └── nginx
│ └── my_django.conf
├── docker-compose.yml
├── Dockerfile
└── src
├── db.sqlite3
├── main.py
├── manage.py
├── mysite
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── Pipfile
├── Pipfile.lock
├── secret.txt
└── start_django.sh
Test
sudo docker-compose build
sudo docker-compose up
localhost
Git
.gitignore
# .gitignore
__pycache__
db.sqlite3
secret.txt
REAME.md
# Devops Example
LISENCE
MIT License
Copyright (c) 2018 Luke Spademan
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.
Push to repo then
Runner
Runner
- Get a server
- Setup Respository
- Install gitlab-runner
- Register runner
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | \
sudo bash
sudo apt-get install gitlab-runner
sudo gitlab-runner register
Runner
# /etc/gitlab-runner/config.toml
...
volumes = ["cache", "/var/run/docker.sock:/var/run/docker.sock"]
...
Runner
- Login
- Start the Runner
- Start docker swarm
sudo docker login registry.gitlab.com
sudo gitlab-runner start
sudo docker warm init --advertise-addr 123.123.123.123
Disable Shared Runners
.gitlab-ci.yml
image: tmaier/docker-compose:latest
stages:
- build
- test
- deploy
variables:
WEB_CONTAINER_IMAGE: registry.gitlab.com/lukespademan/mysite:latest
NGINX_CONTAINER_IMAGE: registry.gitlab.com/lukespademan/mysite/nginx:latest
before_script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.gitlab.com
.gitlab-ci.yml
test:
stage: test
script:
- docker-compose run web python manage.py test --no-input
build:
stage: build
script:
- docker-compose build
- docker-compose push
tags:
- docker
deploy:
stage: deploy
script:
- docker stack deploy --compose-file docker-compose.yml stack-name
tags:
- docker
only:
- master
Done?
Testing
Navigate to http://server-domain-name.com
Devops
By Luke
Devops
- 896