D3.1 Docker Images με Ansible 🖼️

Τι είναι Docker Image

Docker Image
├── Αμετάβλητο template για containers
├── Layers — κάθε εντολή Dockerfile = layer
├── Αποθηκεύεται σε registry (Docker Hub, private)
└── Tag format: registry/namespace/name:tag
    └── nginx:latest
        registry.example.com/myapp:2.1.4

Pull Images

tasks:

  # ── Βασικό pull ───────────────────────────
  - name: Pull nginx image
    community.docker.docker_image:
      name:   nginx
      tag:    latest
      source: pull

  # ── Συγκεκριμένη έκδοση (best practice!) ──
  - name: Pull specific versions
    community.docker.docker_image:
      name:   "{{ item.name }}"
      tag:    "{{ item.tag }}"
      source: pull
    loop:
      - { name: nginx,    tag: "1.25.3" }
      - { name: postgres, tag: "15.4"   }
      - { name: redis,    tag: "7.2"    }
      - { name: alpine,   tag: "3.19"   }
    loop_control:
      label: "{{ item.name }}:{{ item.tag }}"

  # ── Pull από private registry ──────────────
  - name: Pull από private registry
    community.docker.docker_image:
      name:   "registry.example.com/myapp"
      tag:    "{{ app_version }}"
      source: pull

  # ── Force pull (πάντα κατεβάζει) ──────────
  - name: Force pull latest
    community.docker.docker_image:
      name:   nginx
      tag:    latest
      source: pull
      force_source: true    # ← πάντα pull ακόμα κι αν υπάρχει

Build Images από Dockerfile

tasks:

  # ── Βασικό build ──────────────────────────
  - name: Build application image
    community.docker.docker_image:
      name:   myapp
      tag:    "{{ app_version }}"
      source: build
      build:
        path: /opt/myapp    # ← φάκελος με Dockerfile

  # ── Build με arguments ────────────────────
  - name: Build με build args
    community.docker.docker_image:
      name:   myapp
      tag:    "{{ app_version }}"
      source: build
      build:
        path:    /opt/myapp
        pull:    true        # ← pull base image πρώτα
        nocache: false       # ← χρησιμοποίησε cache
        args:
          APP_VERSION: "{{ app_version }}"
          NODE_ENV:    production
          BUILD_DATE:  "{{ ansible_facts['date_time']['date'] }}"

  # ── Build με custom Dockerfile ────────────
  - name: Build με custom Dockerfile
    community.docker.docker_image:
      name:   myapp-test
      tag:    latest
      source: build
      build:
        path:       /opt/myapp
        dockerfile: Dockerfile.test    # ← custom dockerfile

  # ── Build και push ────────────────────────
  - name: Build και push σε registry
    community.docker.docker_image:
      name:       "registry.example.com/myapp"
      tag:        "{{ app_version }}"
      source:     build
      push:       true              # ← push μετά το build
      build:
        path: /opt/myapp

Παράδειγμα Dockerfile

# Δημιουργία test Dockerfile στον server
cat > /tmp/test-dockerfile/Dockerfile << 'EOF'
FROM nginx:1.25.3-alpine

# Build arguments
ARG APP_VERSION=1.0.0
ARG BUILD_DATE

# Labels
LABEL version="${APP_VERSION}"
LABEL build-date="${BUILD_DATE}"
LABEL maintainer="ansible@example.com"

# Copy custom config
COPY nginx.conf /etc/nginx/nginx.conf

# Copy application files
COPY html/ /usr/share/nginx/html/

# Health check
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
    CMD wget -q -O /dev/null http://localhost/ || exit 1

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
EOF
# Ansible: deploy Dockerfile και build
tasks:

  - name: Δημιουργία build directory
    ansible.builtin.file:
      path:  /opt/myapp/docker
      state: directory
      mode:  '0755'

  - name: Deploy Dockerfile
    ansible.builtin.template:
      src:  templates/Dockerfile.j2
      dest: /opt/myapp/docker/Dockerfile

  - name: Deploy nginx.conf
    ansible.builtin.template:
      src:  templates/nginx.conf.j2
      dest: /opt/myapp/docker/nginx.conf

  - name: Build image
    community.docker.docker_image:
      name:   myapp
      tag:    "{{ app_version }}"
      source: build
      build:
        path: /opt/myapp/docker
        args:
          APP_VERSION: "{{ app_version }}"
          BUILD_DATE:  "{{ ansible_facts['date_time']['date'] }}"
    register: build_result

  - name: Build result
    ansible.builtin.debug:
      msg: "Built: {{ build_result.image.Id[:12] }}"

Tag & Push Images

tasks:

  # ── Tag image ─────────────────────────────
  - name: Tag image για registry
    community.docker.docker_image:
      name:       "myapp:{{ app_version }}"
      repository: "registry.example.com/myapp"
      tag:        "{{ app_version }}"
      source:     local              # ← από local images
      push:       false             # ← μόνο tag, όχι push

  # ── Tag ως latest ─────────────────────────
  - name: Tag ως latest
    community.docker.docker_image:
      name:       "myapp:{{ app_version }}"
      repository: "registry.example.com/myapp"
      tag:        latest
      source:     local
      push:       false

  # ── Push σε registry ──────────────────────
  - name: Push image στο registry
    community.docker.docker_image:
      name:   "registry.example.com/myapp"
      tag:    "{{ item }}"
      source: local
      push:   true
    loop:
      - "{{ app_version }}"
      - latest
    loop_control:
      label: "registry.example.com/myapp:{{ item }}"

Registry Authentication

tasks:

  # ── Login πριν pull/push ───────────────────
  - name: Login στο Docker Hub
    community.docker.docker_login:
      username: "{{ docker_hub_user }}"
      password: "{{ vault_docker_hub_token }}"
    no_log: true
    register: login_result

  - name: Login σε private registry
    community.docker.docker_login:
      registry_url: "https://registry.example.com"
      username:     "{{ registry_user }}"
      password:     "{{ vault_registry_pass }}"
    no_log: true

  # ── Pull protected image ───────────────────
  - name: Pull private image
    community.docker.docker_image:
      name:   "registry.example.com/myapp"
      tag:    "{{ app_version }}"
      source: pull
    # ← χρησιμοποιεί το login από πάνω

  # ── Logout μετά ───────────────────────────
  - name: Logout από registry
    community.docker.docker_login:
      username: "{{ docker_hub_user }}"
      state:    absent
    no_log: true

Image Information & Cleanup

tasks:

  # ── Image info ────────────────────────────
  - name: Get image info
    community.docker.docker_image_info:
      name:
        - "nginx:latest"
        - "myapp:{{ app_version }}"
    register: image_info

  - name: Show image info
    ansible.builtin.debug:
      msg:
        - "Image: {{ item.RepoTags | first | default('untagged') }}"
        - "Size : {{ (item.Size / 1048576) | round(1) }}MB"
        - "ID   : {{ item.Id[:12] }}"
        - "Created: {{ item.Created[:10] }}"
    loop: "{{ image_info.images }}"
    loop_control:
      label: "{{ item.RepoTags | first | default('untagged') }}"

  # ── List all images ────────────────────────
  - name: List all Docker images
    ansible.builtin.command:
      cmd: docker images --format json
    register:     images_list
    changed_when: false

  # ── Remove specific image ──────────────────
  - name: Remove old image
    community.docker.docker_image:
      name:         "myapp"
      tag:          "{{ old_version }}"
      state:        absent
      force_absent: true
    when: old_version is defined

  # ── Prune dangling images ─────────────────
  - name: Remove dangling images
    community.docker.docker_prune:
      images:         true
      images_filters:
        dangling: true

  # ── Full system prune (ΠΡΟΣΟΧΗ!) ───────────
  - name: Docker system prune
    community.docker.docker_prune:
      containers:      true
      images:          true
      networks:        true
      volumes:         false    # ← ΠΟΤΕ volumes χωρίς λόγο!
      builder_cache:   true
    when: docker_full_prune | default(false)

Πρακτικό Playbook — Image Management

cat > ~/ansible/playbooks/docker-images.yml << 'EOF'
---
# ============================================================
# Docker Image Management Playbook
# ============================================================

- name: Docker Image Management
  hosts: "{{ target | default('all_managed') }}"
  become: true
  gather_facts: true

  vars:
    app_name:    mywebapp
    app_version: "1.0.0"

    # Images που χρειαζόμαστε
    required_images:
      - name:   nginx
        tag:    "1.25.3-alpine"
      - name:   postgres
        tag:    "15.4-alpine"
      - name:   redis
        tag:    "7.2-alpine"

  tasks:

    # ── Pull required images ───────────────────
    - name: Pull required images
      community.docker.docker_image:
        name:   "{{ item.name }}"
        tag:    "{{ item.tag }}"
        source: pull
      loop: "{{ required_images }}"
      loop_control:
        label: "{{ item.name }}:{{ item.tag }}"
      tags: [pull, images]

    # ── Verify images ──────────────────────────
    - name: Verify images are available
      community.docker.docker_image_info:
        name: "{{ required_images | map(attribute='name') | list }}"
      register: image_check
      tags: [verify, images]

    - name: Image verification report
      ansible.builtin.debug:
        msg:
          - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
          - "Available Images"
          - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
          - "{% for img in image_check.images %}\
             {{ img.RepoTags | first | default('untagged') }}: \
             {{ (img.Size / 1048576) | round(1) }}MB\
             {% endfor %}"
          - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
      tags: [verify, images]

    # ── Prune old images ───────────────────────
    - name: Prune dangling images
      community.docker.docker_prune:
        images:         true
        images_filters:
          dangling: true
      tags: [cleanup, images]

    # ── Docker disk usage ─────────────────────
    - name: Show Docker disk usage
      ansible.builtin.command:
        cmd: docker system df
      register:     disk_usage
      changed_when: false
      tags: [verify, disk]

    - name: Disk usage report
      ansible.builtin.debug:
        msg: "{{ disk_usage.stdout_lines }}"
      tags: [verify, disk]
EOF
# Εκτέλεση
ansible-playbook playbooks/docker-images.yml \
    --limit nextcloud \
    -v

# Μόνο pull
ansible-playbook playbooks/docker-images.yml \
    --limit nextcloud \
    --tags pull

# Μόνο cleanup
ansible-playbook playbooks/docker-images.yml \
    --limit nextcloud \
    --tags cleanup

Σύνοψη D3.1

Docker Images με Ansible
├── Pull:
│   ├── docker_image: source=pull
│   ├── Συγκεκριμένα tags (όχι :latest σε production)
│   └── force_source=true για πάντα fresh pull
│
├── Build:
│   ├── docker_image: source=build, build.path=...
│   ├── Build args, custom Dockerfile
│   └── push=true για αυτόματο push
│
├── Tag & Push:
│   ├── docker_image: source=local, push=true
│   └── Multiple tags (version + latest)
│
├── Registry Auth:
│   ├── docker_login (no_log: true!)
│   └── Logout μετά
│
└── Cleanup:
    ├── docker_image: state=absent
    ├── docker_prune: images=true, dangling=true
    └── ΠΟΤΕ volume prune χωρίς προσοχή!