The Complete NGINX on Ubuntu Series: Part 18 – Automation and DevOps Integration

The Complete NGINX on Ubuntu Series: Part 18 – Automation and DevOps Integration

Welcome to Part 18 of our comprehensive NGINX on Ubuntu series! We’ll implement automation and DevOps practices to streamline NGINX deployment, configuration management, and continuous integration workflows.

DevOps and Automation Fundamentals

NGINX automation involves Infrastructure as Code (IaC), configuration management, CI/CD pipelines, and automated testing to ensure consistent, reliable, and scalable deployments.

graph TD
    A[DevOps Automation] --> B[Infrastructure as Code]
    A --> C[Configuration Management]
    A --> D[CI/CD Pipelines]
    A --> E[Automated Testing]
    
    B --> F[TerraformCloudFormationAnsible Playbooks]
    C --> G[Version ControlTemplate ManagementEnvironment Sync]
    D --> H[Build AutomationDeployment PipelineRollback Strategy]
    E --> I[Config ValidationLoad TestingSecurity Scanning]
    
    J[Automation Benefits] --> K[Consistency]
    J --> L[Reliability]
    J --> M[Scalability]
    J --> N[Speed]
    
    style A fill:#e1f5fe
    style J fill:#e8f5e8
    style F fill:#fff3e0
    style G fill:#e3f2fd
    style H fill:#e8f5e8
    style I fill:#fff3e0

Ansible Playbook for NGINX

# Install Ansible and create project structure
sudo apt update
sudo apt install ansible -y

mkdir -p /opt/nginx-automation/{playbooks,roles,inventory,templates,vars}
cd /opt/nginx-automation
# Create NGINX deployment playbook
cat > playbooks/nginx-deployment.yml << 'EOF'
---
- name: Deploy and Configure NGINX
  hosts: nginx_servers
  become: yes
  vars_files:
    - ../vars/nginx-config.yml
  
  tasks:
    - name: Update package cache
      apt:
        update_cache: yes
        cache_valid_time: 3600
    
    - name: Install NGINX and dependencies
      apt:
        name:
          - nginx
          - certbot
          - python3-certbot-nginx
          - curl
        state: present
    
    - name: Start and enable NGINX
      systemd:
        name: nginx
        state: started
        enabled: yes
    
    - name: Backup existing configuration
      copy:
        src: /etc/nginx/nginx.conf
        dest: "/etc/nginx/nginx.conf.backup.{{ ansible_date_time.epoch }}"
        remote_src: yes
        backup: yes
    
    - name: Deploy main NGINX configuration
      template:
        src: ../templates/nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        owner: root
        group: root
        mode: '0644'
        backup: yes
      notify: 
        - validate nginx config
        - reload nginx
    
    - name: Create virtual host configurations
      template:
        src: "../templates/{{ item.template }}"
        dest: "/etc/nginx/sites-available/{{ item.name }}"
        owner: root
        group: root
        mode: '0644'
      loop: "{{ nginx_sites }}"
      notify:
        - validate nginx config
        - reload nginx
    
    - name: Enable sites
      file:
        src: "/etc/nginx/sites-available/{{ item.name }}"
        dest: "/etc/nginx/sites-enabled/{{ item.name }}"
        state: link
      loop: "{{ nginx_sites }}"
      when: item.enabled | default(true)
      notify:
        - validate nginx config
        - reload nginx
    
    - name: Remove default site
      file:
        path: /etc/nginx/sites-enabled/default
        state: absent
      notify:
        - validate nginx config
        - reload nginx
    
    - name: Configure firewall
      ufw:
        rule: allow
        port: "{{ item }}"
        proto: tcp
      loop:
        - '80'
        - '443'
      when: configure_firewall | default(true)
    
    - name: Deploy monitoring scripts
      template:
        src: "../templates/{{ item }}.j2"
        dest: "/usr/local/bin/{{ item }}"
        owner: root
        group: root
        mode: '0755'
      loop:
        - nginx-health-check
        - nginx-backup
        - nginx-deploy
  
  handlers:
    - name: validate nginx config
      command: nginx -t
      register: nginx_syntax
      failed_when: nginx_syntax.rc != 0
    
    - name: reload nginx
      systemd:
        name: nginx
        state: reloaded
      when: nginx_syntax is succeeded
EOF

Configuration Templates

# Create main NGINX configuration template
cat > templates/nginx.conf.j2 << 'EOF'
# {{ ansible_managed }}
user {{ nginx_user | default('www-data') }};
worker_processes {{ nginx_worker_processes | default('auto') }};
worker_rlimit_nofile {{ nginx_worker_rlimit | default('65535') }};

pid /var/run/nginx.pid;

events {
    use {{ nginx_events_use | default('epoll') }};
    worker_connections {{ nginx_worker_connections | default('4096') }};
    multi_accept {{ nginx_multi_accept | default('on') }};
}

http {
    sendfile {{ nginx_sendfile | default('on') }};
    tcp_nopush {{ nginx_tcp_nopush | default('on') }};
    tcp_nodelay {{ nginx_tcp_nodelay | default('on') }};
    keepalive_timeout {{ nginx_keepalive_timeout | default('65') }};
    
    client_max_body_size {{ nginx_client_max_body_size | default('64m') }};
    
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" rt=$request_time';
    
    access_log {{ nginx_access_log | default('/var/log/nginx/access.log') }} main;
    error_log {{ nginx_error_log | default('/var/log/nginx/error.log') }} warn;
    
    gzip {{ nginx_gzip | default('on') }};
    gzip_vary on;
    gzip_comp_level {{ nginx_gzip_comp_level | default('6') }};
    gzip_types
        text/plain
        text/css
        text/xml
        text/javascript
        application/json
        application/javascript;
    
    ssl_protocols {{ nginx_ssl_protocols | default('TLSv1.2 TLSv1.3') }};
    ssl_session_cache {{ nginx_ssl_session_cache | default('shared:SSL:50m') }};
    
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}
EOF
# Create virtual host template
cat > templates/virtual-host.conf.j2 << 'EOF'
# {{ ansible_managed }}
# Virtual Host: {{ server_name }}

{% if upstream_servers is defined %}
upstream {{ upstream_name | default('backend') }} {
{% for server in upstream_servers %}
    server {{ server.address }}:{{ server.port | default('8080') }};
{% endfor %}
    keepalive 32;
}
{% endif %}

server {
{% if enable_ssl | default(false) %}
    listen 443 ssl http2;
    ssl_certificate /etc/ssl/certs/{{ server_name }}.crt;
    ssl_certificate_key /etc/ssl/private/{{ server_name }}.key;
{% else %}
    listen 80;
{% endif %}
    
    server_name {{ server_name }};
    
{% if document_root is defined %}
    root {{ document_root }};
    index index.html index.htm;
{% endif %}
    
    access_log /var/log/nginx/{{ server_name }}-access.log main;
    error_log /var/log/nginx/{{ server_name }}-error.log warn;
    
    location / {
{% if upstream_servers is defined %}
        proxy_pass http://{{ upstream_name | default('backend') }};
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
{% else %}
        try_files $uri $uri/ =404;
{% endif %}
    }
    
    location = /health {
        access_log off;
        return 200 "healthy\n";
        add_header Content-Type text/plain;
    }
}
EOF

Configuration Variables

# Create configuration variables
cat > vars/nginx-config.yml << 'EOF'
---
# NGINX Configuration Variables

nginx_user: www-data
nginx_worker_processes: auto
nginx_worker_connections: 4096
nginx_keepalive_timeout: 65
nginx_client_max_body_size: 64m
nginx_gzip: "on"
nginx_gzip_comp_level: 6

nginx_sites:
  - name: "api.example.com"
    template: "virtual-host.conf.j2"
    server_name: "api.example.com"
    enable_ssl: true
    upstream_name: "api_backend"
    upstream_servers:
      - address: "192.168.1.10"
        port: 8080
      - address: "192.168.1.11"
        port: 8080
    enabled: true

  - name: "www.example.com"
    template: "virtual-host.conf.j2"
    server_name: "www.example.com"
    enable_ssl: false
    document_root: "/var/www/html"
    enabled: true

configure_firewall: true
EOF

CI/CD Pipeline

graph TD
    A[Code Commit] --> B[CI Trigger]
    B --> C[Config Validation]
    C --> D[Security Scan]
    D --> E[Deploy Staging]
    E --> F[Automated Tests]
    F --> G{Tests Pass?}
    G -->|Yes| H[Deploy Production]
    G -->|No| I[Rollback]
    H --> J[Health Checks]
    
    K[Pipeline Stages] --> L[Validate]
    K --> M[Test]
    K --> N[Deploy]
    K --> O[Monitor]
    
    style A fill:#e1f5fe
    style K fill:#e8f5e8
    style G fill:#fff3e0
    style H fill:#e8f5e8
    style I fill:#ffebee
# Create GitHub Actions workflow
mkdir -p .github/workflows
cat > .github/workflows/nginx-deploy.yml << 'EOF'
name: NGINX Deployment Pipeline

on:
  push:
    branches: [ main, staging ]
    paths: ['nginx/**', 'ansible/**']

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    
    - name: Install Ansible
      run: sudo apt install -y ansible
    
    - name: Validate playbook syntax
      run: |
        cd ansible
        ansible-playbook --syntax-check playbooks/nginx-deployment.yml
    
    - name: Lint configuration
      run: |
        # Install nginx for syntax checking
        sudo apt install -y nginx
        
        # Render templates and validate
        cd ansible
        ansible-playbook --check --diff playbooks/nginx-deployment.yml

  deploy-staging:
    runs-on: ubuntu-latest
    needs: validate
    if: github.ref == 'refs/heads/staging'
    steps:
    - uses: actions/checkout@v3
    
    - name: Deploy to staging
      run: |
        cd ansible
        ansible-playbook -i inventory/staging \
          playbooks/nginx-deployment.yml
    
    - name: Health check
      run: |
        sleep 30
        curl -f http://staging.example.com/health

  deploy-production:
    runs-on: ubuntu-latest
    needs: validate
    if: github.ref == 'refs/heads/main'
    steps:
    - uses: actions/checkout@v3
    
    - name: Deploy to production
      run: |
        cd ansible
        ansible-playbook -i inventory/production \
          playbooks/nginx-deployment.yml
    
    - name: Health check
      run: |
        sleep 60
        curl -f https://api.example.com/health
        curl -f https://www.example.com/health
EOF

Infrastructure as Code

# Create Terraform configuration
mkdir -p terraform/nginx-infrastructure
cat > terraform/nginx-infrastructure/main.tf << 'EOF'
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = var.aws_region
}

# VPC
resource "aws_vpc" "nginx_vpc" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true

  tags = {
    Name = "nginx-vpc"
  }
}

# Subnet
resource "aws_subnet" "nginx_subnet" {
  vpc_id                  = aws_vpc.nginx_vpc.id
  cidr_block              = "10.0.1.0/24"
  availability_zone       = data.aws_availability_zones.available.names[0]
  map_public_ip_on_launch = true

  tags = {
    Name = "nginx-subnet"
  }
}

# Internet Gateway
resource "aws_internet_gateway" "nginx_igw" {
  vpc_id = aws_vpc.nginx_vpc.id

  tags = {
    Name = "nginx-igw"
  }
}

# Route Table
resource "aws_route_table" "nginx_rt" {
  vpc_id = aws_vpc.nginx_vpc.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.nginx_igw.id
  }

  tags = {
    Name = "nginx-rt"
  }
}

resource "aws_route_table_association" "nginx_rta" {
  subnet_id      = aws_subnet.nginx_subnet.id
  route_table_id = aws_route_table.nginx_rt.id
}

# Security Group
resource "aws_security_group" "nginx_sg" {
  name_prefix = "nginx-sg"
  vpc_id      = aws_vpc.nginx_vpc.id

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["10.0.0.0/8"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# EC2 Instances
resource "aws_instance" "nginx_servers" {
  count                  = var.nginx_instance_count
  ami                    = data.aws_ami.ubuntu.id
  instance_type          = var.instance_type
  key_name              = var.key_pair_name
  vpc_security_group_ids = [aws_security_group.nginx_sg.id]
  subnet_id             = aws_subnet.nginx_subnet.id

  user_data = <<-EOF
    #!/bin/bash
    apt update
    apt install -y python3 python3-pip
    pip3 install ansible
  EOF

  tags = {
    Name = "nginx-server-${count.index + 1}"
    Role = "nginx"
  }
}

# Data sources
data "aws_availability_zones" "available" {
  state = "available"
}

data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]

  filter {
    name   = "name"
    values = ["ubuntu/images/hv-ssd/ubuntu-22.04-amd64-server-*"]
  }
}
EOF
# Create Terraform variables
cat > terraform/nginx-infrastructure/variables.tf << 'EOF'
variable "aws_region" {
  description = "AWS region"
  type        = string
  default     = "us-west-2"
}

variable "nginx_instance_count" {
  description = "Number of NGINX instances"
  type        = number
  default     = 2
}

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t3.medium"
}

variable "key_pair_name" {
  description = "AWS key pair name"
  type        = string
}
EOF

Deployment Scripts

# Create deployment automation script
cat > /usr/local/bin/nginx-deploy-automation.sh << 'EOF'
#!/bin/bash

# NGINX Deployment Automation Script
PLAYBOOK_DIR="/opt/nginx-automation"
LOG_FILE="/var/log/nginx-deploy.log"

log_message() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

deploy_nginx() {
    local environment="${1:-staging}"
    local inventory_file="$PLAYBOOK_DIR/inventory/$environment"
    
    log_message "Starting NGINX deployment to $environment"
    
    # Validate inventory
    if [ ! -f "$inventory_file" ]; then
        log_message "ERROR: Inventory file not found: $inventory_file"
        return 1
    fi
    
    # Run pre-deployment checks
    log_message "Running pre-deployment validation..."
    ansible-playbook --check \
        -i "$inventory_file" \
        "$PLAYBOOK_DIR/playbooks/nginx-deployment.yml"
    
    if [ $? -ne 0 ]; then
        log_message "ERROR: Pre-deployment validation failed"
        return 1
    fi
    
    # Execute deployment
    log_message "Executing deployment..."
    ansible-playbook \
        -i "$inventory_file" \
        "$PLAYBOOK_DIR/playbooks/nginx-deployment.yml" \
        --extra-vars "environment=$environment"
    
    if [ $? -eq 0 ]; then
        log_message "Deployment successful"
        
        # Run post-deployment tests
        run_health_checks "$environment"
    else
        log_message "ERROR: Deployment failed"
        return 1
    fi
}

run_health_checks() {
    local environment="$1"
    
    log_message "Running health checks for $environment"
    
    case "$environment" in
        staging)
            curl -f -s --max-time 10 http://staging.example.com/health
            ;;
        production)
            curl -f -s --max-time 10 https://api.example.com/health
            curl -f -s --max-time 10 https://www.example.com/health
            ;;
    esac
    
    if [ $? -eq 0 ]; then
        log_message "Health checks passed"
    else
        log_message "WARNING: Health checks failed"
    fi
}

rollback_deployment() {
    local environment="${1:-staging}"
    
    log_message "Initiating rollback for $environment"
    
    # Restore previous configuration backup
    ansible-playbook \
        -i "$PLAYBOOK_DIR/inventory/$environment" \
        "$PLAYBOOK_DIR/playbooks/nginx-rollback.yml"
    
    log_message "Rollback completed"
}

case "${1:-deploy}" in
    deploy)
        deploy_nginx "$2"
        ;;
    rollback)
        rollback_deployment "$2"
        ;;
    health-check)
        run_health_checks "$2"
        ;;
    *)
        echo "Usage: $0 {deploy|rollback|health-check} [environment]"
        echo "Environments: staging, production"
        ;;
esac

# Make executable: sudo chmod +x /usr/local/bin/nginx-deploy-automation.sh
EOF

Testing and Usage

# Setup and test automation

# 1. Initialize Ansible project
cd /opt/nginx-automation
ansible-galaxy init roles/nginx

# 2. Create inventory files
mkdir -p inventory
cat > inventory/staging << 'EOF'
[nginx_servers]
staging-nginx-1 ansible_host=192.168.1.10
staging-nginx-2 ansible_host=192.168.1.11
EOF

cat > inventory/production << 'EOF'
[nginx_servers]
prod-nginx-1 ansible_host=10.0.1.10
prod-nginx-2 ansible_host=10.0.1.11
EOF

# 3. Test playbook syntax
ansible-playbook --syntax-check playbooks/nginx-deployment.yml

# 4. Run dry-run deployment
ansible-playbook --check -i inventory/staging playbooks/nginx-deployment.yml

# 5. Deploy to staging
/usr/local/bin/nginx-deploy-automation.sh deploy staging

# 6. Run health checks
/usr/local/bin/nginx-deploy-automation.sh health-check staging

# 7. Deploy infrastructure with Terraform
cd terraform/nginx-infrastructure
terraform init
terraform plan
terraform apply

# 8. Trigger CI/CD pipeline
git add .
git commit -m "Update NGINX configuration"
git push origin staging  # Triggers staging deployment

What’s Next?

Excellent! You’ve implemented comprehensive DevOps automation for NGINX with Infrastructure as Code, configuration management, and CI/CD pipelines. Your deployment process is now automated, consistent, and scalable.

Coming up in Part 19: NGINX Container Deployment with Docker and Kubernetes

References


This is Part 18 of our 22-part NGINX series. Your infrastructure is now fully automated! Next, we’ll containerize NGINX with Docker and Kubernetes. Questions? Share them in the comments!

Written by:

377 Posts

View All Posts
Follow Me :