DevOps is the combination of cultural philosophies, practices, and tools that increases an organization's ability to deliver applications and services at high velocity: evolving and improving products at a faster pace than organizations using traditional software development and infrastructure management processes. -- AWS
Docker is a computer program that performs operating-system-level virtualization, also known as "containerization". It was first released in 2013 and is developed by Docker, Inc. Docker is used to run software packages called "containers". -- Wikipedia
pip install pipenv --user
mkdir project
cd project
mkdir src
cd src
pipenv python --3.6
pipenv install django gunicorn psycopg2-binary
pipenv shell
django-admin startproject mysite .
# 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
)
# ...
# 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"]
# ...
# src/mysite/settings.py
# ...
if DEBUG == True:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
else:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'postgres',
'USER': 'postgres',
'HOST': 'db',
'PORT': 5432,
}
}
# ...
# 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/")
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
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')
# 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/mysite/settings.py
...
INSTALLED_APPS = [
'hello_world',
'django.contrib.admin',
'django.contrib.auth',
# ...
]
...
# 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')),
]
# Dockerfile
FROM 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.yml
version: '3'
services:
nginx:
build: Dockerfile.nginx
container_name: mysite_nginx
depends_on:
- web
ports:
- "80:8080"
web:
build: .
container_name: mysite_django
command: bash start_project.sh
depends_on:
- db
db:
image: postgres:latest
container_name: mysite_postgres
volumes:
- "db:/var/lib/postgresql/data"
volumes:
db: {}
# 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
FROM nginx:latest
ADD config/nginx /etc/nginx/conf.d
ADD src /src
EXPOSE 80
mkdir config
cd config
mkdir nginx
cd nginx
# config/nginx/django.conf
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;
}
sudo docker-compose build
sudo docker-compose up
git init
# .gitignore
__pycache__
db.sqlite3
secret.txt
# Devops Example
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
This will require a code from your_repo/settings/ci_cd > runners
# /etc/gitlab-runner/config.toml
...
volumes = ["cache", "/var/run/docker.sock:/var/run/docker.sock"]
...
sudo docker login registry.gitlab.com
sudo gitlab-runner start
sudo docker swarm init --advertise-addr <server-ip-address>
image: tmaier/docker-compose:latest
stages:
- build
- test
- deploy
variables:
WEB_CONTAINER_IMAGE: registry.gitlab.com/lukespademan/pyconuk18-devops-example:latest
NGINX_CONTAINER_IMAGE: registry.gitlab.com/lukespademan/pyconuk18-devops-example/nginx:latest
before_script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.gitlab.com
test:
stage: test
script:
- docker-compose run web python manage.py test --noinput
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
version: '3'
services:
services:
nginx:
image: registry.gitlab.com/lukespademan/pyconuk18-devops-example/nginx:latest
build:
context: .
dockerfile: Dockerfile.nginx
container_name: devops_nginx
depends_on:
- web
ports:
- "80:8080"
services:
...
web:
image: registry.gitlab.com/lukespademan/pyconuk18-devops-example:latest
build: .
container_name: mysite_django
command: bash start_django.sh
depends_on:
- db
services:
...
db:
image: postgres:latest
container_name: devops_postgres
volumes:
- "db:/var/lib/postgresql/data"
volumes:
db: {}