AZURE CLI: Azure Cloud Infrastructure Setup

 

Azure Cloud Infrastructure Setup Guide

This step-by-step guide will help you create a complete Azure infrastructure with VM, storage, and web application.

Prerequisites

  • Azure account (free tier available)

  • Azure CLI installed

  • Basic knowledge of Linux commands


Step 1: Install and Login to Azure CLI

Install Azure CLI (if not already installed)

cmd
# Download and install Azure CLI from:
# https://docs.microsoft.com/en-us/cli/azure/install-azure-cli

# Verify installation
az --version

Login to Azure

cmd
# Login to your Azure account
az login

# This will open a browser window for authentication
# After login, you'll see your subscription information

# List available subscriptions
az account list --output table

# Set your subscription (if you have multiple)
az account set --subscription "Your-Subscription-Name"

Step 2: Set Environment Variables

bash
# Set variables for consistent resource naming
RESOURCE_GROUP="myPythonAppRG"
LOCATION="eastus"  # You can change to your preferred region
VM_NAME="pythonvm"
VM_IMAGE="Ubuntu2204"
ADMIN_USERNAME="azureuser"
STORAGE_ACCOUNT="mypythonstorage$(date +%s)"  # Unique name with timestamp
BLOB_CONTAINER="fileuploads"
SQL_SERVER="python-sql-server"
SQL_DATABASE="FileUploadDB"
SQL_ADMIN_USER="sqladmin"
SQL_ADMIN_PASSWORD="MyStrongPassword123!"

# Verify the variables are set
echo "Resource Group: $RESOURCE_GROUP"
echo "Location: $LOCATION"
echo "VM Name: $VM_NAME"
echo "Storage Account: $STORAGE_ACCOUNT"

Step 3: Create Resource Group

bash
# Create a resource group to contain all our resources
az group create \
    --name $RESOURCE_GROUP \
    --location $LOCATION

# Verify resource group creation
az group show --name $RESOURCE_GROUP

Step 4: Create Linux VM

bash
# Create a Linux VM with Ubuntu
az vm create \
    --resource-group $RESOURCE_GROUP \
    --name $VM_NAME \
    --image $VM_IMAGE \
    --admin-username $ADMIN_USERNAME \
    --generate-ssh-keys \
    --public-ip-sku Standard \
    --size Standard_B1s  # Basic tier for cost savings

# Check if VM is running
az vm show \
    --resource-group $RESOURCE_GROUP \
    --name $VM_NAME \
    --show-details \
    --query "powerState"

# Get the public IP address of the VM
VM_IP=$(az vm show \
    --resource-group $RESOURCE_GROUP \
    --name $VM_NAME \
    --show-details \
    --query "publicIps" -o tsv)

echo "VM Public IP: $VM_IP"

Step 5: Configure Firewall Rules

bash
# Open port 80 for HTTP and 5000 for Flask app
az vm open-port \
    --resource-group $RESOURCE_GROUP \
    --name $VM_NAME \
    --port 80 \
    --priority 1001

az vm open-port \
    --resource-group $RESOURCE_GROUP \
    --name $VM_NAME \
    --port 5000 \
    --priority 1002

# Open port 22 for SSH (should already be open by default)
az vm open-port \
    --resource-group $RESOURCE_GROUP \
    --name $VM_NAME \
    --port 22 \
    --priority 1003

Step 6: Create Blob Storage Account

bash
# Create storage account
az storage account create \
    --resource-group $RESOURCE_GROUP \
    --name $STORAGE_ACCOUNT \
    --location $LOCATION \
    --sku Standard_LRS \
    --kind StorageV2

# Get storage account key
STORAGE_KEY=$(az storage account keys list \
    --resource-group $RESOURCE_GROUP \
    --account-name $STORAGE_ACCOUNT \
    --query "[0].value" -o tsv)

echo "Storage Key: $STORAGE_KEY"

# Create blob container
az storage container create \
    --name $BLOB_CONTAINER \
    --account-name $STORAGE_ACCOUNT \
    --account-key $STORAGE_KEY \
    --public-access off

# Get connection string for Python code
CONNECTION_STRING=$(az storage account show-connection-string \
    --resource-group $RESOURCE_GROUP \
    --name $STORAGE_ACCOUNT \
    --query "connectionString" -o tsv)

echo "Connection String: $CONNECTION_STRING"

Step 7: Create Azure SQL Database

bash
# Create SQL Server
az sql server create \
    --resource-group $RESOURCE_GROUP \
    --name $SQL_SERVER \
    --location $LOCATION \
    --admin-user $SQL_ADMIN_USER \
    --admin-password $SQL_ADMIN_PASSWORD

# Configure firewall to allow Azure services and our VM IP
az sql server firewall-rule create \
    --resource-group $RESOURCE_GROUP \
    --server $SQL_SERVER \
    --name "AllowAzureServices" \
    --start-ip-address 0.0.0.0 \
    --end-ip-address 0.0.0.0

az sql server firewall-rule create \
    --resource-group $RESOURCE_GROUP \
    --server $SQL_SERVER \
    --name "AllowVMIP" \
    --start-ip-address $VM_IP \
    --end-ip-address $VM_IP

# Create SQL Database
az sql db create \
    --resource-group $RESOURCE_GROUP \
    --server $SQL_SERVER \
    --name $SQL_DATABASE \
    --service-objective Basic \
    --edition Basic

# Get SQL connection string
SQL_CONNECTION_STRING=$(az sql db show-connection-string \
    --server $SQL_SERVER \
    --name $SQL_DATABASE \
    --client ado.net --output tsv)

SQL_CONNECTION_STRING=${SQL_CONNECTION_STRING//<username>/$SQL_ADMIN_USER}
SQL_CONNECTION_STRING=${SQL_CONNECTION_STRING//<password>/$SQL_ADMIN_PASSWORD}

echo "SQL Connection String: $SQL_CONNECTION_STRING"

Step 8: SSH into VM and Setup Environment

bash
# SSH into the VM using the generated keys
ssh -o StrictHostKeyChecking=no $ADMIN_USERNAME@$VM_IP

Now you're inside the VM. Run these commands:

bash
# Update package list and upgrade existing packages
sudo apt update && sudo apt upgrade -y

# Install Python and pip
sudo apt install python3 python3-pip -y

# Install Azure SDK and other dependencies
pip3 install azure-storage-blob azure-identity flask sqlalchemy pymysql

# Install additional system packages
sudo apt install git -y

Step 9: Create Database Table

Create a SQL script file on the VM:

bash
# Create a SQL script to setup the database table
cat > setup_database.sql << 'EOF'
CREATE TABLE file_uploads (
    id INT PRIMARY KEY IDENTITY(1,1),
    filename NVARCHAR(255) NOT NULL,
    file_size BIGINT,
    upload_date DATETIME2 DEFAULT GETDATE(),
    blob_url NVARCHAR(500),
    is_deleted BIT DEFAULT 0
);
EOF

# Install SQL command line tools
curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
curl https://packages.microsoft.com/config/ubuntu/22.04/prod.list | sudo tee /etc/apt/sources.list.d/mssql-release.list
sudo apt update
sudo ACCEPT_EULA=Y apt install -y msodbcsql18 mssql-tools18

echo 'export PATH="$PATH:/opt/mssql-tools18/bin"' >> ~/.bashrc
source ~/.bashrc

# Run the SQL script (replace with your actual SQL server details)
sqlcmd -S $SQL_SERVER.database.windows.net -U $SQL_ADMIN_USER -P $SQL_ADMIN_PASSWORD -d $SQL_DATABASE -i setup_database.sql

Step 10: Create Python Flask Web Application

Create the main application file:

bash
cat > app.py << 'EOF'
from flask import Flask, render_template, request, redirect, url_for, flash, send_file
from azure.storage.blob import BlobServiceClient
import pyodbc
import os
import uuid
from io import BytesIO

app = Flask(__name__)
app.secret_key = 'your-secret-key-here'

# Azure Storage Configuration
connection_string = "YOUR_CONNECTION_STRING_HERE"
container_name = "fileuploads"
blob_service_client = BlobServiceClient.from_connection_string(connection_string)

# SQL Database Configuration
server = 'python-sql-server.database.windows.net'
database = 'FileUploadDB'
username = 'sqladmin'
password = 'MyStrongPassword123!'
driver = '{ODBC Driver 18 for SQL Server}'

def get_db_connection():
    return pyodbc.connect(
        f'DRIVER={driver};SERVER={server};DATABASE={database};UID={username};PWD={password}'
    )

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/upload', methods=['POST'])
def upload_file():
    if 'file' not in request.files:
        flash('No file selected')
        return redirect(request.url)
    
    file = request.files['file']
    if file.filename == '':
        flash('No file selected')
        return redirect(request.url)
    
    if file:
        # Generate unique filename
        unique_filename = str(uuid.uuid4()) + '_' + file.filename
        
        # Upload to Azure Blob Storage
        blob_client = blob_service_client.get_blob_client(
            container=container_name, 
            blob=unique_filename
        )
        blob_client.upload_blob(file)
        
        # Store metadata in SQL Database
        conn = get_db_connection()
        cursor = conn.cursor()
        cursor.execute(
            "INSERT INTO file_uploads (filename, file_size, blob_url) VALUES (?, ?, ?)",
            file.filename, 
            len(file.read()),
            blob_client.url
        )
        conn.commit()
        conn.close()
        
        flash('File uploaded successfully!')
        return redirect(url_for('list_files'))

@app.route('/files')
def list_files():
    conn = get_db_connection()
    cursor = conn.cursor()
    cursor.execute("SELECT id, filename, file_size, upload_date FROM file_uploads WHERE is_deleted = 0")
    files = cursor.fetchall()
    conn.close()
    
    return render_template('files.html', files=files)

@app.route('/download/<int:file_id>')
def download_file(file_id):
    conn = get_db_connection()
    cursor = conn.cursor()
    cursor.execute("SELECT filename FROM file_uploads WHERE id = ?", file_id)
    result = cursor.fetchone()
    conn.close()
    
    if result:
        original_filename = result[0]
        # In a real scenario, you'd get the blob name from database
        # This is simplified for demo
        
        blob_client = blob_service_client.get_blob_client(
            container=container_name, 
            blob=original_filename
        )
        
        download_stream = blob_client.download_blob()
        file_content = download_stream.readall()
        
        return send_file(
            BytesIO(file_content),
            as_attachment=True,
            download_name=original_filename
        )
    
    flash('File not found')
    return redirect(url_for('list_files'))

@app.route('/delete/<int:file_id>')
def delete_file(file_id):
    conn = get_db_connection()
    cursor = conn.cursor()
    
    # Get filename before deleting record
    cursor.execute("SELECT filename FROM file_uploads WHERE id = ?", file_id)
    result = cursor.fetchone()
    
    if result:
        # Soft delete from database
        cursor.execute("UPDATE file_uploads SET is_deleted = 1 WHERE id = ?", file_id)
        conn.commit()
        
        # Delete from blob storage
        blob_client = blob_service_client.get_blob_client(
            container=container_name, 
            blob=result[0]
        )
        blob_client.delete_blob()
    
    conn.close()
    flash('File deleted successfully!')
    return redirect(url_for('list_files'))

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)
EOF

Step 11: Create HTML Templates

Create templates directory and HTML files:

bash
mkdir templates

cat > templates/index.html << 'EOF'
<!DOCTYPE html>
<html>
<head>
    <title>Azure File Upload Service</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="container mt-5">
        <h1>Azure File Upload Service</h1>
        
        {% with messages = get_flashed_messages() %}
            {% if messages %}
                {% for message in messages %}
                    <div class="alert alert-info">{{ message }}</div>
                {% endfor %}
            {% endif %}
        {% endwith %}
        
        <div class="card mt-4">
            <div class="card-header">
                <h5>Upload File to Azure Blob Storage</h5>
            </div>
            <div class="card-body">
                <form action="/upload" method="post" enctype="multipart/form-data">
                    <div class="mb-3">
                        <input type="file" class="form-control" name="file" required>
                    </div>
                    <button type="submit" class="btn btn-primary">Upload File</button>
                </form>
            </div>
        </div>
        
        <div class="mt-3">
            <a href="/files" class="btn btn-secondary">View All Files</a>
        </div>
    </div>
</body>
</html>
EOF

cat > templates/files.html << 'EOF'
<!DOCTYPE html>
<html>
<head>
    <title>Uploaded Files</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="container mt-5">
        <h1>Uploaded Files</h1>
        
        {% with messages = get_flashed_messages() %}
            {% if messages %}
                {% for message in messages %}
                    <div class="alert alert-info">{{ message }}</div>
                {% endfor %}
            {% endif %}
        {% endwith %}
        
        <a href="/" class="btn btn-secondary mb-3">Back to Upload</a>
        
        <table class="table table-striped">
            <thead>
                <tr>
                    <th>Filename</th>
                    <th>Size</th>
                    <th>Upload Date</th>
                    <th>Actions</th>
                </tr>
            </thead>
            <tbody>
                {% for file in files %}
                <tr>
                    <td>{{ file[1] }}</td>
                    <td>{{ file[2] }} bytes</td>
                    <td>{{ file[3] }}</td>
                    <td>
                        <a href="/download/{{ file[0] }}" class="btn btn-sm btn-success">Download</a>
                        <a href="/delete/{{ file[0] }}" class="btn btn-sm btn-danger" 
                           onclick="return confirm('Are you sure you want to delete this file?')">Delete</a>
                    </td>
                </tr>
                {% endfor %}
            </tbody>
        </table>
        
        {% if not files %}
        <div class="alert alert-warning">No files uploaded yet.</div>
        {% endif %}
    </div>
</body>
</html>
EOF

Step 12: Update Configuration and Run Application

Back on the VM, update the connection string in app.py:

bash
# Replace the connection string in app.py with your actual connection string
sed -i "s|YOUR_CONNECTION_STRING_HERE|$CONNECTION_STRING|g" app.py

# Make the app accessible on port 80 by running with sudo
sudo pip3 install flask

# Run the application (for testing)
python3 app.py

The app will be accessible at: http://YOUR_VM_IP:5000


Step 13: Create Systemd Service for Auto-start

bash
# Create a systemd service file
sudo cat > /etc/systemd/system/python-app.service << EOF
[Unit]
Description=Python Flask File Upload App
After=network.target

[Service]
Type=simple
User=azureuser
WorkingDirectory=/home/azureuser
ExecStart=/usr/bin/python3 /home/azureuser/app.py
Restart=always

[Install]
WantedBy=multi-user.target
EOF

# Reload systemd and enable the service
sudo systemctl daemon-reload
sudo systemctl enable python-app.service
sudo systemctl start python-app.service

# Check service status
sudo systemctl status python-app.service

Step 14: Test Your Application

  1. Access your application: Open browser and go to http://YOUR_VM_IP:5000

  2. Upload a file: Use the upload form to test file upload

  3. View files: Click "View All Files" to see uploaded files

  4. Download files: Test download functionality

  5. Delete files: Test delete functionality


Step 15: Cleanup (When Done)

bash
# Delete the entire resource group (this will delete all resources)
az group delete --name $RESOURCE_GROUP --yes --no-wait

# Or delete individual resources
az vm delete --resource-group $RESOURCE_GROUP --name $VM_NAME --yes
az storage account delete --resource-group $RESOURCE_GROUP --name $STORAGE_ACCOUNT --yes
az sql server delete --resource-group $RESOURCE_GROUP --name $SQL_SERVER --yes

Important Security Notes

  1. Never hardcode credentials in production - use Azure Key Vault

  2. Use environment variables for sensitive information

  3. Implement proper authentication for production apps

  4. Configure SSL/TLS for secure communication

  5. Regularly update and patch your VM

Cost Optimization

  • Use Basic/Standard tiers for development

  • Shut down VMs when not in use

  • Use Azure Spot Instances for testing

  • Set up billing alerts

This complete setup demonstrates a real-world Azure cloud application using multiple Azure services working together!

Comments

Popular posts from this blog

Interview Tips: Dot NET Framework vs Net CORE

FREE Webinar: Run Your Own Independent DeepSeek LLM

Delegates and Events