My Docker
Ok, here goes another attempt (here is the previous one ⏳) of automation of my Docker server. In the previous article I failed, but now it looks much more promising and this article is being updated and expanded as I progress with this project 📈. However, the original reason for the ability to easily migrate my Docker server to more powerful instance is gone, as my kids are not interested to play on my Minecraft server 😭. Never mind, at least it is opportunity to learn something new.
The New Beginning 🌅
This time my semi-automated deployment consists of three main components:
- Terraform
- Ansible
- My Docker files
Here is how this project looks like in the tree listing.
Terraform
I’m using pretty much the same Terraform plan as in the [previous](/terraform-handson/ article and my goal stays the same, provision the infrastructure as much as possible using Terraform. So here goes the current terraform.tf
:
### Variables
variable "scaleway_access_key" {}
variable "scaleway_secret_key" {}
variable "swtype" {
default = "DEV1-M"
}
variable "hostname" {
default = "docker-sw1"
}
variable "env" {
default = "test"
}
variable "cloudflare_email" {}
variable "cloudflare_token" {}
variable "base_domain" {}
### Scaleway
provider "scaleway" {
access_key = var.scaleway_access_key
secret_key = var.scaleway_secret_key
zone = "fr-par-1"
region = "fr-par"
organization_id = "zyx"
}
resource "scaleway_instance_ip" "public_ip" {}
resource "scaleway_instance_server" "docker" {
name = format("%s-%s.%s", var.hostname, var.env, var.base_domain)
type = var.swtype
image = "docker"
tags = [ var.env, "linux", "docker" ]
# attach the public_ip to instance
ip_id = scaleway_instance_ip.public_ip.id
}
### Cloudflare
provider "cloudflare" {
version = "~> 2.0"
email = var.cloudflare_email
api_key = var.cloudflare_token
}
resource "cloudflare_record" "vaulttec" {
zone_id = "xyz"
name = format("%s-%s", var.hostname, var.env)
value = scaleway_instance_ip.public_ip.address
type = "A"
proxied = false
}
The terraform.tfvars
contains basic variables:
scaleway_access_key = "xyz"
scaleway_secret_key = "xyz"
cloudflare_email = "lubos@klokner.sk"
cloudflare_token = "xyz"
base_domain = "vault-tec.sk"
And finally the output.tf
:
# Outputs
output "ssh_to_instance" {
value = [ format("ssh root@%s-%s.%s", var.hostname, var.env, var.base_domain)]
}
If I remember correctly, the only difference is, that I decided to add a variable env
which represent the deployment type. TEST or PROD and it generates a subdomain when registering FQDN in the domain at Cloudflare.
To conclude this section, running tf init
, tf plan
and tf apply
(I have created an alias for terraform
command) should:
- create a Docker instance at ScaleWay with defined resources assigned (swtype)
- register a DNS record at Cloudflare under base_domain as combination of variables: hostname and env
Easy
Docker
Moving to my custom Docker files that I host locally on my MBP 👨🏻💻 from where I run all this. There are two of them at this moment:
./traefik
Here is corresponding docker-compose.yml
:
version: '3.7'
services:
traefik:
image: traefik:v1.7
container_name: traefik
restart: always
command: |
--web \
--docker \
--docker.watch \
--docker.exposedbydefault=false
ports:
- 80:80
- 443:443
- 8080:8080
volumes:
- ./traefik.toml:/traefik.toml
- ./acme.json:/acme.json
- /var/run/docker.sock:/var/run/docker.sock
networks:
- traefik
networks:
traefik:
name: traefik
…and Traefik configuration file. As I’m extremely lazy, I’m still running older version 1.7 as you can see in the docker-compose.yml
file.
traefik.toml
:
debug = false
logLevel = "DEBUG"
defaultEntryPoints = ["https","http"]
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
minVersion = "VersionTLS12"
cipherSuites = [
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305"
]
[retry]
[acme]
email = "lubos@klokner.sk"
storage = "acme.json"
entryPoint = "https"
onHostRule = true
[acme.httpChallenge]
entryPoint = "http"
[web]
address = ":8080"
[web.auth.basic]
users = ["admin:xyz]%
Nothing special here, it just redirect every page published on Docker using traefik
labels to https, ask the Let’s Encrypt for the Cert (if needed) and sends traffic to the particular Docker container. Admin Dashboard is publised on port 8080, without any encryption 🤦🏻♂️ (please don’t tell anybody).
f5-demo-httpd
Just generic F5 demo application…
Dockerfile
:
FROM f5devcentral/f5-demo-httpd
EXPOSE 80
docker-compose.yaml
:
version: '3.7'
services:
f5-demo-httpd:
build: .
# container_name:
# ports:
# - ${PORT:-80}:80
restart: always
labels:
traefik.enable: true
traefik.frontend.rule: Host:demo.f5demo.app
traefik.docker.network: traefik
networks:
- traefik
networks:
traefik:
external:
name: traefik
The rest of Docker configuration files is hosted on Github, so the description is available below in the Ansible section.
Ansible
As I’ve learned each tool is better for different task. For customization of a server, Ansible can do better job than Terraform. As you can see on the screenshot below the Ansible project consists of several files.
_environment.sh
Contains basic environment variables that I want to have set before I start Ansible. Right now it is only:
export ANSIBLE_HOST_KEY_CHECKING=False
inventory.ini
Ansible inventory file which contains the hosts referenced in the playbooks. In my case it is only the IP address which Terraform returns in the ssh_to_instance
Output section.
[docker_host]
docker-sw1-test.vault-tec.sk ansible_user=root
Then the actual playbooks continue.
0_default.yml
Here customize the system and prepare it also for the tasks I’ll need later. The main steps are:
- Update apt repo and cache
- Upgrade all packages on server
- Install required system packages: apt-transport-https, ca-certificates, curl, software-properties-common, python3-pip, virtualenv, python3-setuptools, ctop
- Update PIP
- Install Docker Modules for Python
- Install my Github private ssh key in order to be able to fetch my private repos later on
- Get and Deploy my ubuntu-deploy-scripts from Github
- Reboot the server if needed after the updates
---
- hosts: docker_host
gather_facts: no
tasks:
- name: Update apt repo and cache
apt: update_cache=yes force_apt_get=yes cache_valid_time=3600
- name: Upgrade all packages on server
apt: upgrade=dist force_apt_get=yes
- name: Install required system packages
apt: name= state=latest update_cache=yes
loop: [ 'apt-transport-https', 'ca-certificates', 'curl', 'software-properties-common', 'python3-pip', 'virtualenv', 'python3-setuptools', 'ctop' ]
- name: pip self-update
pip:
name: pip
state: latest
- name: Install Docker Modules for Python
pip: name=
loop: [ 'docker', 'docker-compose', 'docker-pretty-ps' ]
- name: Ensure .ssh directory exists
file:
path: /root/.ssh
state: directory
mode: 0700
owner: root
group: root
- name: Ensure GitHub deploy key is present on the server
copy:
src: /Users/erkac/.ssh/id_rsa_github
dest: /root/.ssh/id_rsa_github
mode: 0600
owner: root
group: root
- name: Clone the deploy scripts from GitHub
git:
repo: git@github.com:erkac/ubuntu-deploy-scripts.git
dest: /root/
accept_hostkey: yes
key_file: /root/.ssh/id_rsa_github
- name: Run the ubuntu-deploy-scripts
shell: ubuntu-deploy.sh -s >> ubuntu-deploy.log
args:
- chdir: ubuntu-deploy-scripts/
- name: Check if a reboot is needed on all server
register: reboot_required_file
stat: path=/var/run/reboot-required get_md5=no
- name: Reboot the box if kernel updated
reboot:
msg: "Reboot initiated by Ansible for kernel updates"
connect_timeout: 5
reboot_timeout: 300
pre_reboot_delay: 0
post_reboot_delay: 30
test_command: uptime
when: reboot_required_file.stat.exists
1_traeffik.yml
- Transfers local Docker files
- Starts Traeffik
---
- hosts: docker_host
gather_facts: no
tasks:
- name: create build directory
file:
path: /root/docker
state: directory
owner: root
group: root
mode: '0755'
- name: copy container data
copy:
src: ../docker/traefik
dest: /root/docker
owner: root
group: root
mode: '0644'
- name: create acme.json file
file:
path: /root/docker/traefik/acme.json
state: touch
owner: root
group: root
mode: '600'
- name: start Traeffik
docker_compose:
project_src: /root/docker/traefik
build: no
register: output
3_lubos-klokner-sk.yml
Next I would like only add the example how to get the source code from git and start the project. The remaining ones are the same. I’m either copying local data or pulling code from git.
In this example Ansible:
- creates the directory
- gets the data from Github
- Pulls private repo of this web from Github using private SSH key
- fixes the permissions of the website static content
- build
jekyll-build
and start the NGINX to serveweb-lubos
this site
---
- hosts: docker_host
gather_facts: no
tasks:
- name: create build directory
file:
path: /root/docker
state: directory
owner: root
group: root
mode: '0755'
- name: Get data from github
git:
repo: git@github.com:erkac/web-lubos-klokner-sk.git
dest: /root/docker/www-lubos-klokner-sk
accept_hostkey: yes
key_file: /root/.ssh/id_rsa_github
- name: fix permissions on site sub-folder
file:
path: /root/docker/www-lubos-klokner-sk/site
state: directory
owner: root
group: root
mode: '0777'
- name: build the web page content
docker_compose:
project_src: /root/docker/www-lubos-klokner-sk
services: jekyll-build
register: output
- name: publish the web site
docker_compose:
project_src: /root/docker/www-lubos-klokner-sk
services: web-lubos
register: output
…and that’s all folks! Hope someone will read this and find it useful.