Kubernetes- und Docker-Workshops sind sehr schwer vorzubereiten, Play-with-Docker und Play-with-Kubernetes können dabei aber eine große Hilfe sein. Die Dokumentation dazu ist leider nicht sehr umfangreich, wie man es schnell und einfach installieren kann erfahrt ihr hier.
Cloud statt localhost
Ende 2018 habe ich mit einem Kollegen meinen ersten Workshop als Consultant gehalten. Das Thema war “Eine Einführung in Docker und Container”. Die Vorbereitung lief gut, wir hatten viel Material. Leider bedachten wir zu spät, die Systemanforderungen an den Kunden mitzuteilen. Somit hatte er zwar eine erstellte Ubuntu VM, allerdings nur auf einem USB-Stick. Während der Stick nun umher ging und repliziert wurde, fiel einigen Teilnehmern auf, dass sie gar keine Virtualisierungssoftware auf dem Windows-System haben. Sicherheitseinstellungen des Betriebs verhinderten zudem bei einigen die Installation der Software. Das ganze erinnerte etwas an LAN-Partys in den 90er Jahren. Damals verbrachte man die ersten Stunden auch immer damit, die Rechner zu konfigurieren. Meinem Kollegen fiel dann ein, dass die Teilnehmer mit Virtualisierungsproblemen mal versuchen sollten das Ganze auf Play-with-Docker zu probieren. Abgesehen davon, dass einige Teilnehmer nicht sofort einen Slot bekamen, ging es von da an problemlos weiter.
Nach dieser Erfahrung haben wir uns viele Gedanken gemacht, wie man den Prozess in Zukunft verbessern kann. Wir dachten z.B. darüber nach, eigene Hardware samt WIFI Hotspot mitzubringen, aber im Grunde waren die meisten Ideen zu aufwendig. Die beste Lösung war meines Erachtens eine eigene Play-with-Docker Installation in der Cloud. Da Play-with-Docker Open Source ist, musste nur noch eine geeignete Installationsroutine her.
Ansible als Automatisierungs-Werkzeug
Ansible ist ein Configuration Management Tool das sich besonders in DevOps-Projekten eignet. Da (fast) alles in einfachen YAML auszudrücken ist, braucht es kaum Programmierkenntnisse. Dennoch ist es sehr umfangreich und bietet für vieles eigene Plug-ins.
Wir benutzen es für dieses Projekt da wir im Gegensatz zu Chef und Puppet keinen Client auf dem Zielhost benötigen und die Konfiguration sehr übersichtlich ist. Obendrein gibt es bereits fertige Plug-ins für AWS, die das Leben vereinfachen.
Vorbereitung der Umgebung
Zum Ausführen benötigen wir wie bereits erwähnt Ansible (aktuell in Version 2.8.5) und boto3 (aktuell in Version 1.9.244). Beides ist am schnellsten mit python-pip zu installieren. In den meisten Package Managern ist es sowohl für Python2 als auch Python3 vertreten. Alternativ kann es aber auch manuell installiert werden.
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python get-pip.py
Um das Ansible Playbook auszuführen, muss als Erstes das Git Repository ausgecheckt werden.
git clone https://github.com/skornehl/ansible-play-with-docker-aws.git
Als nächstes müssen die erforderlichen Abhängigkeiten installiert werden.
pip install -r requirements.txt
Im Anschluss wird der AWS Account vorbereitet. Der Einfachheit halber können wir einen neuen User erstellen und ihm die Rechte “AmazonEC2FullAccess” und “AmazonRoute53FullAccess” geben. Für mehr Sicherheit kann das aber noch weiter eingeschränkt werden. Credentials herunterladen und entweder als Umgebungsvariablen setzen
export AWS_ACCESS_KEY_ID=AKAA11111111AAAAAA
export AWS_SECRET_ACCESS_KEY=AAAAAAAAAAA1111111111111111AAAAAAA
oder als an die Credentials-Datei im Home einfügen ~/.aws/credentials
[play-with-docker]
aws_access_key_id = AKAA11111111AAAAAA
aws_secret_access_key =AAAAAAAAAAA1111111111111111AAAAAAA
Zur späteren Ausführen wird noch eine Konfigurierte DNS im Route53 benötigt. Hierzu kann man sich z.B. direkt bei Amazon oder bei freien Providern wie freenom.com eine Domain registrieren und diese als Hosted Zone im Route53 hinterlegen.
Konfiguration des Playbooks
Es gibt nicht viele Konfigurationsparameter, ihr findet alle unter group_vars/all.
- public_domain: Die Route53 Hosted Zone (z.B. codecentric-workshop.de)
- dns_prefix: DIe Subdomain
- ec2_tag_Name: Name Tags vom EC2 um die Maschinen zu gruppieren. Wichtig für den Cleanup task
- aws_vpc: Die VPC ID aus AWS
- aws_profile: Falls es in der ~/.aws/credentials Datei mehrer Profile gibt kann hier das richtige angegeben werden
- aws_region: In welcher Region soll Play-with-Docker gestartet werden?
- aws_sec_grp_src: CIDR für die Security Group. 0.0.0.0/0 für alle aus dem Internet oder eure office IP (1.2.3.4/32)
- ec2_instance_count: Wieviele Instanzen benötigt ihr?
- ec2_instance_type: Welche Instanzgrösse brauchen wir? Zusammen mit den ec2_instance_count ist man so sehr flexibel
- ansible_user: Mit welchen User soll Ansible sich verbinden
- cleanup: Um ein Cleanup zu machen auf true setzen
- kubernetes: Soll auch Play-with-Kubernetes ausführbar sein
- default_image: dind für Docker, k8s für Kubernetes
Das Playbook
In diesem Kapitel werden nur teile der Playbooks besprochen, die auch im Detail nicht vollständig sind. Den vollständigen Code gibt es im Github Repository .
In der Cloud
Im ersten Ausführungsteil wird der AWS Account vorbereitet. Hierzu kann man unter roles/ec2/vars/main.yaml noch zwei Einstellungen vornehmen.
- ec2_volume_size: Festplattengrösse
- ec2_keypair: Der Name des SSH Keypair das angelegt wird. Bitte kein bestehendes eintragen, da dies im ersten Schritt gelöscht wird
- name: Remove old EC2 key
ec2_key:
name: "{{ ec2_keypair }}"
state: absent
- name: Create a new EC2 key
ec2_key:
name: "{{ ec2_keypair }}"
force: yes
register: ec2_key_result
- name: Save private key
copy:
content: "{{ ec2_key_result.key.private_key }}"
dest: "./{{ ec2_keypair }}.pem"
mode: 0600
when: ec2_key_result.changed
Der SSH-Schlüssel wird nach dem Erzeugen in der Cloud heruntergeladen um ihn für die Ansible-Ausführung nutzen zu können. Der Schlüssel besitzt aber keinen eigentlichen Wert und wird daher bei jeder Ausführung neu generiert. Im nächsten Schritt wird die Security Group erstellt und die Instanz hochgefahren.
- name: Add PWD security group
ec2_group:
name: "{{ ec2_tag_Name }}"
vpc_id: "{{ aws_vpc }}"
purge_rules: yes
rules:
- proto: tcp
ports:
- 80
- 22
cidr_ip: "{{ aws_sec_grp_src }}"
rule_desc: allow all on port 80
- proto: all
group_name: "{{ ec2_tag_Name }}"
register: var_awc_sec_grp
- name: Launch the new EC2 Instance
ec2:
group_id: "{{ var_awc_sec_grp.group_id }}"
vpc_subnet_id: "{{ subnet_facts.subnets|map(attribute='id')|list|random }}"
instance_type: "{{ ec2_instance_type }}"
image: "ubuntu"
wait: yes
key_name: "{{ ec2_keypair }}"
assign_public_ip: yes
count: "{{ ec2_instance_count }}"
register: ec2
In der Security Group werden sowohl Port 22 für die Provisionierung mit Ansible als auch Port 80 für die spätere Nutzung freigegeben. Die EC2 Instanz wird in einem zufälligen Subnetz in der VPC erstellt und hochgefahren. Anschliessend wird die Instanz in die Ansible Hosts eingetragen. Zuletzt wird gewartet bis sie per SSH erreichbar ist.
- name: Add to hosts
add_host:
name: "{{ item.public_ip }}"
groups: play-with-docker
url: "{{ dns_prefix }}{{ my_idx }}.{{ ec2_tag_Name }}.{{ public_domain }}"
public_dns_name: "{{ item.public_dns_name }}"
ansible_ssh_private_key_file: "./{{ ec2_keypair }}.pem"
loop: "{{ ec2.instances }}"
loop_control:
index_var: my_idx
- name: Wait for the instances to boot by checking the ssh port
wait_for:
host: "{{ item.public_ip }}"
port: 22
delay: 60
timeout: 320
state: started
with_items: "{{ ec2.instances }}"
Zum Host wird die URL als temporäre Variable gespeichert damit diese später als DNS gesetzt werden kann.
Play-with-Docker
Für die Nutzung von Play-with-Docker müssen als erstes zwei DNS-Eintrage erstellt werden. Dieser Task wird an den lokalen Host delegiert. Die Variable public_dns_name wurde im Task Add to hosts gesetzt.
- name: Create R53 records
route53:
state: present
zone: "{{ public_domain }}"
record: "{{ item }}"
type: CNAME
value: "{{ public_dns_name }}"
ttl: 60
overwrite: yes
loop:
- "{{ url }}"
- "*.{{ url }}"
delegate_to: 127.0.0.1
Im Play install-packages werden alle notwendigen Pakete für Docker installiert und anschließend Play-with-Docker aus Github gecloned.
- name: Clone PWD
git:
force: yes
repo: 'https://github.com/play-with-docker/play-with-docker.git'
Das Play pwd-config konfiguriert notwendige Details am Code. Als erstes wird der String localhost mit der URL des Hosts ersetzt.
- name: Replace localhost to actual DNS
replace:
path: "{{ PWD_HOME }}/config/config.go"
regexp: 'localhost'
replace: "{{ url }}"
backup: no
Im Folgenden wird das Session Timeout von 4 auf 10 Stunden erhöht, sodass sich die Teilnehmer an einem Workshop-Tag nicht diverse Male neu einloggen müssen.
- name: Increase timeout to life for a workshop day
replace:
path: "{{ PWD_HOME }}/api.go"
regexp: '4h'
replace: '10h'
backup: no
Darauf wird ein Swarm Cluster initialisiert, der für die Ausührung von Play-with-Docker zwingend benötigt wird.
- name: Init a new swarm with default parameters
docker_swarm:
state: present
Schließlich müssen noch die GO Abhängigkeiten installiert und das Docker Image heruntergeladen werden.
- name: Run dep ensure
shell: dep ensure
args:
chdir: "{{ PWD_HOME }}"
environment:
GOPATH: "{{ GOPATH }}"
- name: Docker pull Images
docker_image:
name: "{{ item }}"
source: pull
become: yes
loop:
- "franela/dind"
Das verwendete Image ist ein Docker in Docker (dind) Container. Demnach wird also ein Docker Conatiner gestartet, der wiederum andere Container starten kann. Das ist das Kernprinzip von Play-with-Docker. Am Ende wird nun noch Docker Compose ausgeführt. Die Startzeit beträgt etwa 3 bis 5 Minuten, danach ist das System über den Browser erreichbar.
- name: Run docker compose
shell: docker-compose up -d
args:
chdir: "{{ PWD_HOME }}"
environment:
GOPATH: "{{ GOPATH }}"
Play-with-Kubernetes
Der Unterschied zwischen Play-with-Docker und Play-with-Kubernetes ist nur das verwendete Docker Image. Statt franela/dind wird franela/k8s ausgeführt.
- name: Docker pull Images
docker_image:
name: "{{ item }}"
source: pull
loop:
- "franela/dind"
- "franela/k8s"
In dieser Konfiguration kann sowohl Play-with-Docker als auch Play-with-Kubernetes vom Nutzer gestartet werden. Mit der Option default_image kann nun noch das Standard Image beim Start eingestellt werden.
- name: Add K8S Image to PWD
replace:
path: "{{ PWD_HOME }}/api.go"
regexp: '{"franela/dind"}'
replace: '{"franela/dind", "franela/k8s"}'
Um Play-with-Kubernetes als DefaultDinDInstanceImage zu starten muss zusaetzlich in der group_vars/all das entsprechende Property gesetzt werden
default_image: "k8s"
Nach dem Workshop
Wie bereits bei der Konfiguration angedeutet, gibt es die Möglichkeit, ein automatisiertes Cleanup des AWS Account auszuführen. Dazu muss die Variable cleanup beim Aufruf auf true gesetzt werden.
ansible-playbook site.yaml -e cleanup=true
Im Anschluss werden alle EC2 Instanzen mit dem konfigurierten Play-with-Docker Tag terminiert.
- name: Terminate instances
ec2_instance:
state: absent
wait: yes
filters:
"tag:Name": "{{ ec2_tag_Name }}"
Nachdem alle Instanzen heruntergefahren wurden, können die Security Group und der SSH Schlüssel gelöscht werden.
- name: Remove Security group
ec2_group:
name: "{{ ec2_tag_Name }}"
vpc_id: "{{ aws_vpc }}"
state: absent
- name: Remove EC2 key
ec2_key:
name: "{{ ec2_tag_Name }}-{{ aws_region }}"
state: absent
Am Ende werden nun noch die DNS-Einträge gelöscht und der AWS Account ist wieder sauber.
- name: Get all hosted zones
route53_facts:
profile: "{{ aws_profile }}"
query: hosted_zone
register: hosted_zone
- name: Get ZoneID of PWD Zone
set_fact:
zone_id: "{{ item['Id'] }}"
loop: "{{ hosted_zone['HostedZones'] }}"
when: item['Name'][0:-1] == public_domain
Dazu werden als erstes alle gehosteten Zonen aus Route53 aufgelistet um die ZoneID bei AWS herauszufinden. Im Anschluss werden die vorhandenen DNS-Einträge in einer Variablen gespeichert sowie eine Teil-URL zusammengestellt.
- name: List record sets in Zone
route53_facts:
profile: "{{ aws_profile }}"
query: record_sets
hosted_zone_id: "{{ zone_id }}"
register: record_sets
- name: Build URL Part
set_fact:
url: "{{ ec2_tag_Name }}.{{ public_domain }}"
Am Ende werden dann alle DNS-Einträge entfernt, die den Teilstring der URL enthalten.
- name: Remove all record sets from PWD
route53:
state: absent
zone: "{{ public_domain }}"
record: "{{ item['Name']| replace('\\052', '*') }}"
type: "{{ item['Type'] }}"
ttl: "{{ item['TTL'] }}"
value:
- "{{ item['ResourceRecords'][0]['Value'] }}"
loop: "{{ record_sets['ResourceRecordSets'] }}"
when: dns_prefix in item['Name'] and url in item['Name']
Fazit
Durch Play-with-Docker, bzw. Play-with-Kubernetes können viele technische Probleme in einem Workshop gelöst werden. Alle Teilnehmer können Aufgaben im Browser ohne große Einschränkungen bearbeiten. Mittels Ansible und AWS ist der Aufwand dazu sehr gering und die Kosten durchaus überschaubar. Für viele Workshops reichen durchaus kleine Instanzen. Schon eine m5a.large kann 2 – 3 Kursteilnehmer ohne Probleme verkraften.
Wenn ihr mehr über Docker , Ansible oder AWS lernen wollt schaut euch weiter im Blog um.
Dein Job bei codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
Weitere Artikel in diesem Themenbereich
Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog-Autor*in
Sebastian Kornehl
IT Consultant
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.