Batch 4: Cloud Ansible & OpenShift AWS & Azure Docker & Podman Apache & NGINX ← Full TOC

⚙ Chapter 38 — Ansible & OpenShift

Complete reference for Ansible automation and OpenShift/Kubernetes — playbooks, roles, inventory, modules, ad-hoc commands, and container orchestration.
Part of The Direct Path to Linux Ubuntu — free for all.

4Topics
109+Commands
5Examples
ansibleTool
Ansible 10Version
FreeNo login
📋 Ansible Playbooks 📂 Inventory & Roles ⚡ Ad-hoc & Modules ☸️ OpenShift & K8s ⚡ Examples

📋 Ansible Playbooks

📋 Ansible PlaybooksPlaybook structure, tasks, handlers, variables, loops

📂 Inventory & Roles

📂 Ansible Inventory & RolesInventory files, host groups, roles directory structure

⚡ Ad-hoc & Modules

⚡ Ansible Ad-hoc & ModulesAd-hoc commands, essential modules, Jinja2 templating

☸️ OpenShift & K8s

☸️ OpenShift & Kubernetesoc CLI, pods, deployments, services, routes, builds

⚡ Quick Examples

Install Ansible: pip install ansible. OpenShift CLI: download oc from Red Hat.

Ansible Quick Reference

Most-used Ansible commands and playbook patterns.

# ── Install Ansible ──────────────────────────────────────────
pip install ansible            # pip install
sudo apt install ansible       # Ubuntu/Debian
brew install ansible           # macOS

# ── Test connectivity ─────────────────────────────────────────
ansible all -i inventory -m ping
ansible webservers -i "web01,web02," -m ping  # inline inventory

# ── Ad-hoc commands ───────────────────────────────────────────
ansible all -i inventory -m command    -a "uptime"
ansible all -i inventory -m shell      -a "df -h | grep /dev/sd"
ansible all -i inventory -m apt        -a "name=curl state=present" -b
ansible all -i inventory -m service    -a "name=apache2 state=restarted" -b
ansible all -i inventory -m copy       -a "src=file.txt dest=/tmp/" -b
ansible all -i inventory -m setup      -a "filter=ansible_distribution*"

# ── Playbook commands ─────────────────────────────────────────
ansible-playbook site.yml -i inventory
ansible-playbook site.yml -i inventory --check       # dry run
ansible-playbook site.yml -i inventory --diff        # show changes
ansible-playbook site.yml -i inventory -v            # verbose
ansible-playbook site.yml -i inventory -vvv          # very verbose
ansible-playbook site.yml -i inventory --tags install
ansible-playbook site.yml -i inventory --limit web01
ansible-playbook site.yml -i inventory --ask-vault-pass

# ── Vault ────────────────────────────────────────────────────
ansible-vault create  vars/secrets.yml
ansible-vault encrypt vars/secrets.yml
ansible-vault decrypt vars/secrets.yml
ansible-vault edit    vars/secrets.yml
ansible-vault view    vars/secrets.yml

# ── Galaxy ───────────────────────────────────────────────────
ansible-galaxy install geerlingguy.apache
ansible-galaxy install -r requirements.yml
ansible-galaxy init    my_new_role
ansible-galaxy list

# ── Debug ────────────────────────────────────────────────────
ansible-inventory -i inventory --list
ansible-inventory -i inventory --graph
ansible all -i inventory -m setup 2>/dev/null | python3 -m json.tool

Full Deploy Playbook

Complete playbook deploying MyWebUniversity on Ubuntu.

# deploy.yml — deploy MyWebUniversity to Ubuntu servers
---
- name: Deploy MyWebUniversity
  hosts: webservers
  become: true
  vars:
    app_dir:  /var/www/React/prod
    cgi_dir:  /usr/lib/cgi-bin
    app_user: www-data
    chapters:
      - {name: Python,      cgi: pydoc,      ch: pydoc}
      - {name: Go,          cgi: Go,         ch: Go}
      - {name: Java,        cgi: Java,       ch: Java}
      - {name: JavaScript,  cgi: JavaScript, ch: JavaScript}
      - {name: SQL,         cgi: SQL,        ch: SQL}
      - {name: LDAP,        cgi: LDAP,       ch: LDAP}
      - {name: Cloud,       cgi: Cloud,      ch: Cloud}

  tasks:
    - name: Ensure packages installed
      ansible.builtin.apt:
        name: [apache2, python3, libapache2-mod-cgid]
        state: present
        update_cache: true

    - name: Enable Apache modules
      community.general.apache2_module:
        name: "{{ item }}"
        state: present
      loop: [cgid, rewrite, ssl, headers, expires]
      notify: Restart Apache

    - name: Create app directory
      ansible.builtin.file:
        path:  "{{ app_dir }}"
        state: directory
        owner: "{{ app_user }}"
        mode:  '0755'

    - name: Create CGI chapter directories
      ansible.builtin.file:
        path:  "{{ cgi_dir }}/{{ item.cgi }}"
        state: directory
        owner: "{{ app_user }}"
        mode:  '0755'
      loop: "{{ chapters }}"

    - name: Deploy CGI scripts
      ansible.builtin.copy:
        src:   "files/{{ item.cgi }}-Reference.cgi"
        dest:  "{{ cgi_dir }}/{{ item.cgi }}/{{ item.cgi }}-Reference.cgi"
        owner: "{{ app_user }}"
        mode:  '0755'
      loop: "{{ chapters }}"
      when: lookup('fileglob', 'files/' + item.cgi + '-Reference.cgi') | length > 0

    - name: Deploy HTML index files
      ansible.builtin.copy:
        src:   "files/{{ item.cgi }}-index.html"
        dest:  "{{ app_dir }}/{{ item.cgi }}/index.html"
        owner: "{{ app_user }}"
        mode:  '0644'
      loop: "{{ chapters }}"
      when: lookup('fileglob', 'files/' + item.cgi + '-index.html') | length > 0

    - name: Set ownership on app directory
      ansible.builtin.file:
        path:    "{{ app_dir }}"
        owner:   "{{ app_user }}"
        group:   "{{ app_user }}"
        recurse: true

    - name: Verify Apache is running
      ansible.builtin.service:
        name:    apache2
        state:   started
        enabled: true

    - name: Health check
      ansible.builtin.uri:
        url:         "http://{{ ansible_default_ipv4.address }}/toc1.html"
        status_code: 200
      register: check
      ignore_errors: true

    - name: Deployment status
      ansible.builtin.debug:
        msg: "{{ 'SUCCESS' if check.status == 200 else 'FAILED - check Apache logs' }}"

  handlers:
    - name: Restart Apache
      ansible.builtin.service:
        name: apache2
        state: restarted

# Run: ansible-playbook deploy.yml -i "your-server," -u ubuntu --private-key ~/.ssh/key.pem

Kubernetes Deployment YAML

Complete k8s Deployment, Service, Ingress, and ConfigMap.

# k8s-mywebuniversity.yaml
# kubectl apply -f k8s-mywebuniversity.yaml

---
apiVersion: v1
kind: Namespace
metadata:
  name: mywebuniversity

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: mywebuniversity
data:
  ENVIRONMENT:  "production"
  LOG_LEVEL:    "info"
  SITE_URL:     "https://www.mywebuniversity.com"

---
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
  namespace: mywebuniversity
type: Opaque
stringData:
  DB_PASSWORD: "change-me-in-production"
  API_KEY:     "change-me-in-production"

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name:      mywebuniversity-web
  namespace: mywebuniversity
  labels:
    app:     mywebuniversity
    tier:    web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: mywebuniversity
  strategy:
    type: RollingUpdate
    rollingUpdate: {maxSurge: 1, maxUnavailable: 0}
  template:
    metadata:
      labels:
        app: mywebuniversity
    spec:
      containers:
        - name:  web
          image: myregistry.io/mywebuniversity/web:latest
          ports:
            - containerPort: 80
          envFrom:
            - configMapRef:  {name: app-config}
          env:
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef: {name: app-secrets, key: DB_PASSWORD}
          resources:
            requests: {cpu: "100m", memory: "128Mi"}
            limits:   {cpu: "500m", memory: "512Mi"}
          livenessProbe:
            httpGet: {path: /health, port: 80}
            initialDelaySeconds: 15
            periodSeconds: 20
          readinessProbe:
            httpGet: {path: /ready, port: 80}
            initialDelaySeconds: 5
            periodSeconds: 10

---
apiVersion: v1
kind: Service
metadata:
  name:      mywebuniversity-web
  namespace: mywebuniversity
spec:
  selector:
    app: mywebuniversity
  ports:
    - name: http
      port:       80
      targetPort: 80

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name:      mywebuniversity-ingress
  namespace: mywebuniversity
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  rules:
    - host: www.mywebuniversity.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: mywebuniversity-web
                port: {number: 80}
  tls:
    - hosts: [www.mywebuniversity.com]
      secretName: mywebuniversity-tls

# Commands:
# kubectl apply -f k8s-mywebuniversity.yaml
# kubectl get all -n mywebuniversity
# kubectl rollout status deployment/mywebuniversity-web -n mywebuniversity
# kubectl scale deployment mywebuniversity-web --replicas=5 -n mywebuniversity

Ansible Vault Secrets

Encrypt, use, and manage secrets with Ansible Vault.

# ── Create encrypted vars file ───────────────────────────────
ansible-vault create vars/secrets.yml
# Opens editor — type your secrets:
# db_password: SuperSecret123!
# api_key: abc123xyz
# smtp_password: mailpass456

# ── Encrypt existing file ─────────────────────────────────────
ansible-vault encrypt vars/secrets.yml

# ── View encrypted file ───────────────────────────────────────
ansible-vault view vars/secrets.yml

# ── Edit encrypted file ───────────────────────────────────────
ansible-vault edit vars/secrets.yml

# ── Encrypt single value (embed in playbook) ──────────────────
ansible-vault encrypt_string 'SuperSecret123!' --name db_password
# Output:
# db_password: !vault |
#   $ANSIBLE_VAULT;1.1;AES256
#   663...

# ── Use vault in playbook ─────────────────────────────────────
# secrets_playbook.yml
---
- name: Use vault secrets
  hosts: databases
  become: true
  vars_files:
    - vars/secrets.yml    # encrypted!
  tasks:
    - name: Configure database
      ansible.builtin.template:
        src:  templates/pg_hba.conf.j2
        dest: /etc/postgresql/16/main/pg_hba.conf
      # Template uses: {{ db_password }}

    - name: Set DB password
      ansible.builtin.postgresql_user:
        name:     myapp
        password: "{{ db_password }}"
      become_user: postgres

# ── Run with password ─────────────────────────────────────────
ansible-playbook secrets_playbook.yml -i inventory --ask-vault-pass
# Or use password file (for CI/CD):
echo "myVaultPassword" > ~/.vault_pass
chmod 600 ~/.vault_pass
ansible-playbook secrets_playbook.yml -i inventory --vault-password-file ~/.vault_pass

# ── Multiple vault IDs ────────────────────────────────────────
ansible-vault create --vault-id dev@prompt  vars/dev_secrets.yml
ansible-vault create --vault-id prod@prompt vars/prod_secrets.yml
ansible-playbook site.yml \
  --vault-id dev@~/.dev_vault_pass \
  --vault-id prod@~/.prod_vault_pass

# ── Rekey (change password) ───────────────────────────────────
ansible-vault rekey vars/secrets.yml

OpenShift S2I Build

Source-to-Image build and deploy on OpenShift.

# OpenShift Source-to-Image (S2I) Workflow
# S2I builds container images directly from source code

# ── Login and setup ───────────────────────────────────────────
oc login https://api.myocp.example.com:6443 --token=mytoken
oc new-project mywebuniversity

# ── S2I from Git repo ─────────────────────────────────────────
# Python app
oc new-app python~https://github.com/myorg/mywebuniversity-api.git \
  --name=api \
  --env=FLASK_ENV=production \
  --env=DATABASE_URL=postgresql://db:5432/mywebuniversity

# Node.js app
oc new-app nodejs~https://github.com/myorg/mywebuniversity-frontend.git \
  --name=frontend

# ── S2I from local directory ──────────────────────────────────
# Create build config
oc new-build python --name=myapp --binary

# Build from current directory
oc start-build myapp --from-dir=. --follow

# ── Monitor build ─────────────────────────────────────────────
oc get builds
oc logs build/myapp-1 -f
oc describe build myapp-1

# ── Expose with Route ─────────────────────────────────────────
oc expose service api
oc get routes

# Edge TLS termination
oc create route edge api \
  --service=api \
  --hostname=api.mywebuniversity.com

# ── Environment and secrets ───────────────────────────────────
oc create secret generic db-creds \
  --from-literal=DATABASE_URL="postgresql://admin:pass@db:5432/mywebuniversity"

oc set env deployment/api --from=secret/db-creds
oc set env deployment/api LOG_LEVEL=info WORKERS=4

# ── Scaling and updates ───────────────────────────────────────
oc scale deployment api --replicas=5
oc rollout status deployment/api
oc set image deployment/api api=myregistry.io/mywebuniversity/api:v2.0
oc rollout undo deployment/api   # rollback if needed

# ── Resource quotas ───────────────────────────────────────────
oc create quota mywebuniversity-quota \
  --hard=pods=20,requests.cpu=4,requests.memory=8Gi,limits.cpu=8,limits.memory=16Gi

oc describe quota mywebuniversity-quota

# ── Useful oc commands ────────────────────────────────────────
oc get all                           # all resources in project
oc get pods -o wide                  # pods with node info
oc logs deployment/api -f            # stream logs
oc exec -it api-xxx-yyy -- bash      # shell into pod
oc port-forward api-xxx-yyy 8080:8080  # local port forward
oc get events --sort-by=.lastTimestamp  # recent events