D3.2 Docker Containers με Ansible 🐳
Container Lifecycle
┌─────────────────────────────────────────────────────────┐
│ Container States │
│ │
│ absent ──► created ──► started ──► stopped │
│ ▲ │ │ │
│ └──────────────────────┴────────────┘ │
│ │
│ Ansible states: │
│ ├── started → τρέχει (δημιουργεί αν δεν υπάρχει) │
│ ├── stopped → σταματημένο (δεν διαγράφει) │
│ ├── absent → διαγραμμένο │
│ └── present → υπάρχει (δεν ξεκινά αναγκαστικά) │
└─────────────────────────────────────────────────────────┘
Βασικές λειτουργίες
tasks:
# ── Start container ───────────────────────
- name: Start nginx
community.docker.docker_container:
name: nginx_web
image: nginx:1.25.3-alpine
state: started
# ── Stop container ────────────────────────
- name: Stop nginx
community.docker.docker_container:
name: nginx_web
state: stopped
# ── Restart container ─────────────────────
- name: Restart nginx
community.docker.docker_container:
name: nginx_web
state: started
restart: true # ← force restart
# ── Remove container ──────────────────────
- name: Remove nginx
community.docker.docker_container:
name: nginx_web
state: absent
force_kill: true # ← kill αν τρέχει
# ── Recreate container ────────────────────
- name: Recreate nginx
community.docker.docker_container:
name: nginx_web
image: nginx:1.25.3-alpine
state: started
recreate: true # ← πάντα recreate
Port Mapping
tasks:
# ── Single port ───────────────────────────
- name: Single port mapping
community.docker.docker_container:
name: web
image: nginx:alpine
state: started
ports:
- "80:80" # host:container
# ── Multiple ports ────────────────────────
- name: Multiple port mappings
community.docker.docker_container:
name: web
image: nginx:alpine
state: started
ports:
- "80:80"
- "443:443"
# ── Custom host port ──────────────────────
- name: Custom host port
community.docker.docker_container:
name: web
image: nginx:alpine
state: started
ports:
- "8080:80" # host 8080 → container 80
# ── Bind to specific interface ────────────
- name: Bind to localhost only
community.docker.docker_container:
name: api
image: myapi:latest
state: started
ports:
- "127.0.0.1:3000:3000" # μόνο localhost
# ── Variables για ports ───────────────────
- name: Port από variables
community.docker.docker_container:
name: "{{ app_name }}"
image: "{{ app_image }}"
state: started
ports:
- "{{ app_host_port }}:{{ app_container_port }}"
Environment Variables
tasks:
# ── Simple env vars ───────────────────────
- name: Container με env vars
community.docker.docker_container:
name: myapp
image: myapp:1.0
state: started
env:
NODE_ENV: production
PORT: "3000"
LOG_LEVEL: info
no_log: false # ← OK αν δεν υπάρχουν secrets
# ── Με secrets (πάντα no_log!) ────────────
- name: Container με secrets
community.docker.docker_container:
name: myapp
image: myapp:1.0
state: started
env:
NODE_ENV: production
DB_HOST: postgres
DB_NAME: "{{ db_name }}"
DB_USER: "{{ db_user }}"
DB_PASSWORD: "{{ vault_db_password }}"
SECRET_KEY: "{{ vault_app_secret }}"
API_TOKEN: "{{ vault_api_token }}"
no_log: true # ← ΠΑΝΤΑ για secrets!
# ── Env vars από αρχείο ───────────────────
- name: Container με env_file
community.docker.docker_container:
name: myapp
image: myapp:1.0
state: started
env_file:
- /opt/myapp/.env # ← αρχείο με env vars
# ── Δυναμικά env vars ─────────────────────
- name: Dynamic env vars
community.docker.docker_container:
name: myapp
image: myapp:1.0
state: started
env:
APP_VERSION: "{{ app_version }}"
BUILD_DATE: "{{ ansible_facts['date_time']['date'] }}"
HOSTNAME: "{{ inventory_hostname }}"
ENVIRONMENT: "{{ app_env | default('production') }}"
Volumes
tasks:
# ── Named volume ──────────────────────────
- name: Container με named volume
community.docker.docker_container:
name: postgres
image: postgres:15-alpine
state: started
volumes:
- postgres_data:/var/lib/postgresql/data
# ── Bind mount ────────────────────────────
- name: Container με bind mount
community.docker.docker_container:
name: nginx
image: nginx:alpine
state: started
volumes:
- /var/www/html:/usr/share/nginx/html:ro # ro = read-only!
- /etc/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
# ── Mixed volumes ─────────────────────────
- name: Container με mixed volumes
community.docker.docker_container:
name: myapp
image: myapp:1.0
state: started
volumes:
- myapp_data:/app/data # named volume
- /opt/myapp/config:/app/config:ro # bind mount (ro)
- /etc/localtime:/etc/localtime:ro # timezone
# ── Tmpfs mount ───────────────────────────
- name: Container με tmpfs
community.docker.docker_container:
name: myapp
image: myapp:1.0
state: started
mounts:
- type: tmpfs
target: /tmp
tmpfs_size: "100m"
Networks
tasks:
# ── Default bridge network ────────────────
- name: Container στο default network
community.docker.docker_container:
name: myapp
image: myapp:1.0
state: started
# ← χωρίς network → default bridge
# ── Custom network ────────────────────────
- name: Container σε custom network
community.docker.docker_container:
name: myapp
image: myapp:1.0
state: started
networks:
- name: app_network
# ── Πολλαπλά networks ─────────────────────
- name: Container σε πολλά networks
community.docker.docker_container:
name: nginx
image: nginx:alpine
state: started
networks:
- name: frontend # ← public traffic
- name: backend # ← internal traffic
# ── Network με aliases ────────────────────
- name: Container με network alias
community.docker.docker_container:
name: postgres
image: postgres:15-alpine
state: started
networks:
- name: backend
aliases:
- db # ← άλλα containers μπορούν να χρησιμοποιούν "db"
- database
# ── Host network ──────────────────────────
- name: Container με host network
community.docker.docker_container:
name: myapp
image: myapp:1.0
state: started
network_mode: host # ← χρησιμοποιεί host networking
Resource Limits
tasks:
# ── Memory limits ─────────────────────────
- name: Container με memory limit
community.docker.docker_container:
name: myapp
image: myapp:1.0
state: started
memory: "512m" # ← max 512MB RAM
memory_swap: "1g" # ← max 1GB swap
# ── CPU limits ────────────────────────────
- name: Container με CPU limit
community.docker.docker_container:
name: myapp
image: myapp:1.0
state: started
cpus: "0.5" # ← 0.5 CPU cores
# ── CPU shares ────────────────────────────
- name: Container με CPU shares
community.docker.docker_container:
name: myapp
image: myapp:1.0
state: started
cpu_shares: 512 # ← relative weight (default: 1024)
# ── Full resource config ──────────────────
- name: Production container με limits
community.docker.docker_container:
name: myapp
image: "myapp:{{ app_version }}"
state: started
memory: "{{ container_memory | default('512m') }}"
memory_swap: "{{ container_memory_swap | default('1g') }}"
cpus: "{{ container_cpus | default('1.0') }}"
cpu_shares: 1024
ulimits:
- nofile:65536:65536 # ← max open files
Restart Policies
tasks:
# ── no (default) ──────────────────────────
- name: Container χωρίς restart
community.docker.docker_container:
name: myapp
image: myapp:1.0
state: started
restart_policy: "no" # ← δεν ξεκινά αυτόματα
# ── always ────────────────────────────────
- name: Container με always restart
community.docker.docker_container:
name: myapp
image: myapp:1.0
state: started
restart_policy: always # ← πάντα restart (ακόμα κι αν manual stop)
# ── unless-stopped ────────────────────────
- name: Container με unless-stopped
community.docker.docker_container:
name: myapp
image: myapp:1.0
state: started
restart_policy: unless-stopped # ← restart εκτός αν manual stop ✅
# ── on-failure ────────────────────────────
- name: Container με on-failure
community.docker.docker_container:
name: myapp
image: myapp:1.0
state: started
restart_policy: on-failure
restart_retries: 3 # ← max 3 προσπάθειες
Health Checks
tasks:
# ── HTTP health check ─────────────────────
- name: Container με HTTP health check
community.docker.docker_container:
name: myapp
image: myapp:1.0
state: started
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s # ← περίμενε 40s πριν ξεκινήσεις checks
# ── Shell health check ────────────────────
- name: Container με shell health check
community.docker.docker_container:
name: postgres
image: postgres:15-alpine
state: started
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
# ── Wait for healthy ──────────────────────
- name: Περίμενε container να γίνει healthy
community.docker.docker_container_info:
name: myapp
register: health_check
until: >
health_check.container.State.Health.Status == 'healthy'
retries: 12
delay: 5
- name: Health status
ansible.builtin.debug:
msg: >
myapp health:
{{ health_check.container.State.Health.Status }}
Logging Configuration
tasks:
# ── json-file (default) ───────────────────
- name: Container με json-file logging
community.docker.docker_container:
name: myapp
image: myapp:1.0
state: started
log_driver: json-file
log_options:
max-size: "10m"
max-file: "3"
# ── syslog ────────────────────────────────
- name: Container με syslog
community.docker.docker_container:
name: myapp
image: myapp:1.0
state: started
log_driver: syslog
log_options:
syslog-address: "tcp://localhost:514"
tag: "myapp"
# ── journald ──────────────────────────────
- name: Container με journald
community.docker.docker_container:
name: myapp
image: myapp:1.0
state: started
log_driver: journald
log_options:
tag: "myapp-{{ app_version }}"
Labels
tasks:
- name: Container με labels
community.docker.docker_container:
name: myapp
image: "myapp:{{ app_version }}"
state: started
labels:
# ── Application metadata ──────────────
app: myapp
version: "{{ app_version }}"
environment: "{{ app_env }}"
# ── Deployment metadata ───────────────
deployed-by: ansible
deployed-on: "{{ ansible_facts['date_time']['date'] }}"
deployed-from: "{{ inventory_hostname }}"
# ── Traefik labels (reverse proxy) ────
traefik.enable: "true"
traefik.http.routers.myapp.rule: "Host(`myapp.example.com`)"
traefik.http.services.myapp.loadbalancer.server.port: "3000"
Container Info & Monitoring
tasks:
# ── Get container info ────────────────────
- name: Get container information
community.docker.docker_container_info:
name: myapp
register: app_info
- name: Show container details
ansible.builtin.debug:
msg:
- "Name : {{ app_info.container.Name }}"
- "Status : {{ app_info.container.State.Status }}"
- "Running: {{ app_info.container.State.Running }}"
- "IP : {{ app_info.container.NetworkSettings.Networks | dict2items | map(attribute='value') | map(attribute='IPAddress') | first | default('N/A') }}"
- "Image : {{ app_info.container.Config.Image }}"
- "Created: {{ app_info.container.Created[:10] }}"
when: app_info.exists
# ── Conditional restart ───────────────────
- name: Restart αν unhealthy
community.docker.docker_container:
name: myapp
state: started
restart: true
when:
- app_info.exists
- app_info.container.State.Health is defined
- app_info.container.State.Health.Status == 'unhealthy'
# ── Docker stats ──────────────────────────
- name: Get container stats
ansible.builtin.command:
cmd: >
docker stats myapp
--no-stream
--format "{{ '{{' }}.CPUPerc{{ '}}' }}\t{{ '{{' }}.MemUsage{{ '}}' }}"
register: container_stats
changed_when: false
ignore_errors: true
- name: Show stats
ansible.builtin.debug:
msg: "myapp stats: {{ container_stats.stdout }}"
when: not container_stats.failed
Πλήρες παράδειγμα — Nginx Container
---
# nginx-container.yml
- name: Deploy Nginx Container
hosts: "{{ target | default('all_managed') }}"
become: true
gather_facts: true
vars:
nginx_container_name: nginx_web
nginx_image: nginx:1.25.3-alpine
nginx_host_port: 80
nginx_web_root: /var/www/html
tasks:
# ── Προετοιμασία ──────────────────────────
- name: Create web directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
mode: '0755'
loop:
- "{{ nginx_web_root }}"
- /etc/nginx/conf.d
- name: Deploy index.html
ansible.builtin.copy:
dest: "{{ nginx_web_root }}/index.html"
mode: '0644'
content: |
<!DOCTYPE html>
<html>
<head><title>{{ inventory_hostname }}</title></head>
<body>
<h1>✅ {{ inventory_hostname }}</h1>
<p>Running in Docker container</p>
<p>Image: {{ nginx_image }}</p>
</body>
</html>
# ── Network & Volume ──────────────────────
- name: Create nginx network
community.docker.docker_network:
name: nginx_net
state: present
# ── Pull image ────────────────────────────
- name: Pull nginx image
community.docker.docker_image:
name: nginx
tag: 1.25.3-alpine
source: pull
# ── Deploy container ──────────────────────
- name: Deploy nginx container
community.docker.docker_container:
name: "{{ nginx_container_name }}"
image: "{{ nginx_image }}"
state: started
restart_policy: unless-stopped
ports:
- "{{ nginx_host_port }}:80"
volumes:
- "{{ nginx_web_root }}:/usr/share/nginx/html:ro"
- /etc/localtime:/etc/localtime:ro
networks:
- name: nginx_net
labels:
app: nginx
managed: ansible
memory: "128m"
cpus: "0.25"
log_driver: json-file
log_options:
max-size: "10m"
max-file: "3"
healthcheck:
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://localhost/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
register: nginx_result
# ── Wait for healthy ──────────────────────
- name: Wait for nginx healthy
community.docker.docker_container_info:
name: "{{ nginx_container_name }}"
register: nginx_info
until: >
nginx_info.container.State.Health is not defined or
nginx_info.container.State.Health.Status in ['healthy', 'starting']
retries: 6
delay: 5
# ── Verify ────────────────────────────────
- name: Verify nginx responds
ansible.builtin.uri:
url: "http://localhost:{{ nginx_host_port }}"
status_code: 200
register: http_check
retries: 3
delay: 5
ignore_errors: true
# ── Report ────────────────────────────────
- name: Deployment Report
ansible.builtin.debug:
msg:
- "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
- "✅ Nginx Container Deployed"
- "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
- "Container: {{ nginx_container_name }}"
- "Image : {{ nginx_image }}"
- "Port : {{ nginx_host_port }}"
- "Status : {{ nginx_info.container.State.Status }}"
- "HTTP : {{ '✅ OK' if not http_check.failed else '❌ Failed' }}"
- "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
tags: always
# Εκτέλεση
ansible-playbook playbooks/nginx-container.yml \
--limit nextcloud \
-v
# Verify
ansible nextcloud -m ansible.builtin.uri \
-a "url=http://localhost status_code=200"
Σύνοψη D3.2
Docker Containers με Ansible
├── States:
│ ├── started → τρέχει ✅
│ ├── stopped → σταματημένο
│ ├── absent → διαγραμμένο
│ └── present → υπάρχει
│
├── Configuration:
│ ├── ports → host:container mapping
│ ├── env → environment variables (no_log!)
│ ├── volumes → named volumes + bind mounts
│ ├── networks → custom Docker networks
│ └── labels → metadata
│
├── Resources:
│ ├── memory, memory_swap
│ ├── cpus, cpu_shares
│ └── ulimits
│
├── Reliability:
│ ├── restart_policy: unless-stopped
│ ├── healthcheck: test, interval, retries
│ └── Wait for healthy με until/retries
│
└── Monitoring:
├── docker_container_info
├── docker stats
└── Conditional restart αν unhealthy