Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
f348ff8
initial
Sep 25, 2025
a263798
instructions for environment setup
Sep 25, 2025
6655612
added required parameter
Sep 25, 2025
cb491e0
added connection description
Sep 25, 2025
5de15cb
rework gitops
Oct 15, 2025
0675294
rework gitops
Oct 15, 2025
f7f041c
testing gitops
Oct 15, 2025
8ea201d
gitops testing deletion of ns
Oct 15, 2025
c045690
added files for hello-world app
Oct 29, 2025
44479a9
added namespace hello-world
Oct 29, 2025
f3f5bcd
added namespace to yaml files
Oct 29, 2025
384ee85
added image-automation
Oct 30, 2025
4a97e96
divers
Dec 2, 2025
4cc7af8
Merge remote-tracking branch 'upstream/main'
Dec 2, 2025
d439a06
ch1
Dec 2, 2025
aeb88e3
ch1
Dec 2, 2025
9ff5159
ch1
Dec 2, 2025
200cefe
ch1
Dec 2, 2025
71752a5
rework env setup
Dec 5, 2025
66a08c9
reworked env setup
Dec 5, 2025
827f91e
renamed so terraform ignores the template
Dec 5, 2025
07fccb7
env setup desc
Dec 5, 2025
6a1e658
ch1
Dec 12, 2025
95c9b85
removed sub-id
Dec 12, 2025
28155d3
ch2 - desc
Dec 15, 2025
eae3728
added network validation scripts
Dec 15, 2025
c1782d1
removed regional endpoint as it provides false positives
Dec 15, 2025
0697861
fix connectivity check synopsis
Dec 15, 2025
a6dcbb3
test
Dec 15, 2025
3d3def2
ch-2
Dec 15, 2025
34b6589
fix
Dec 15, 2025
24b975b
fix
Dec 15, 2025
6237131
ch-2
Dec 16, 2025
93b884f
ch-2
Dec 16, 2025
379ee97
refactored to use K3s instead of AKS
Dec 17, 2025
7461272
added obo endpoint
Dec 18, 2025
0d65a6e
debugging
Dec 18, 2025
abd2433
fixed resource access from the portal
Dec 29, 2025
d55271f
changed structure according to hackbox template
Dec 29, 2025
b200ccc
fixed links after restructuring
Dec 29, 2025
e5f7629
completed challenge 2
Dec 30, 2025
60c09ab
fixed formatting
Dec 30, 2025
1a69458
format fixes
Dec 30, 2025
9f8ee49
update challenge 3 title and details;
Dec 30, 2025
699283b
challenge-04
Jan 14, 2026
8014d49
cosmetic changes
Jan 14, 2026
4ee12b2
stripped down gitops to namespace expample
Feb 2, 2026
a146527
test gitops
Feb 2, 2026
90ba4f7
cleaning up after gitops test
Feb 2, 2026
284baf5
shutdown schedule for k3s nodes
Feb 27, 2026
b95c225
parameterized hardcoded admin name
Feb 27, 2026
6529e47
removed test namespace
Feb 27, 2026
da45885
changed folder name
Mar 3, 2026
48fdcff
removed invalid folder name
Mar 4, 2026
a99188a
added Lars to contributors
Mar 6, 2026
d220e7e
Change challenge 3
Mar 6, 2026
8eac281
spelling mistake
Mar 6, 2026
1d681d1
spelling changes
Mar 6, 2026
2038165
cleansed description
Mar 6, 2026
858da01
Merge branch 'main' of https://github.com/skiddder/MicroHack
Mar 6, 2026
b064617
fixed regex for user_number extraction to also match different userna…
Mar 6, 2026
b0b8ec2
Merge branch 'main' of https://github.com/skiddder/MicroHack
Mar 6, 2026
dd5a9f2
fixed regex to work with general user names.
Mar 6, 2026
c8165f1
added openwebui portal
Mar 6, 2026
4f59118
Merge branch 'main' of https://github.com/microsoft/MicroHack
Mar 6, 2026
71f5a4e
Remove unintended files from PR
Mar 6, 2026
a97ffbd
arc gateway is now GA and also supporting arc-enabled k8s
Mar 9, 2026
4cccfe4
Add team1 namespace configuration
skiddder Mar 10, 2026
89fbced
fixed naming convention
Mar 11, 2026
5eefc2e
Fix user_number extraction logic
Mar 27, 2026
77d6a2c
Add Windows 11 workstation VM, Azure Bastion, and Ansible provisioning
Apr 14, 2026
7ddb09a
validated
Apr 14, 2026
5c1ebb1
- added explicit rules for node communication
Apr 17, 2026
4a25190
fixes to work with private ip address and ssh tunnel
Apr 17, 2026
80f4a0d
- fixes deployment parameters
Apr 20, 2026
601628f
Merge remote-tracking branch 'upstream/main'
Apr 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* [**Objectives**](#objectives)
* [**General Prerequisites**](#general-prerequisites)
* [**MicroHack Challenges**](#microhack-challenges)
* [Challenge 01 - Onboarding your Kubernetes Cluster](challenges/challenge-01.md))
* [Challenge 01 - Onboarding your Kubernetes Cluster](challenges/challenge-01.md)
* [Challenge 02 - Enable Azure Monitor for Containers](challenges/challenge-02.md)
* [Challenge 03 - Deploy CPU based Large & Small Language Models (LLM/SLM) on Azure Arc-enabled Kubernetes](challenges/challenge-03.md)
* [Challenge 04 - Deploy SQL Managed Instance](challenges/challenge-04.md)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ In challenge 1 you will connect/onboard your existing K8s cluster to Azure Arc.
* Verify all prerequisites are in place
* Resource Providers
* Azure CLI extensions
* Resource group (Name: mh-arc-k8s-<xy>)
* Resource group (Name: <xy>-k8s-arc)
* Connectivity to required Azure endpoints
* Deploy the Azure Arc agent pods to your k8s cluster
* Assign permissions to view k8s resources in the Azure portal
Expand All @@ -18,13 +18,14 @@ In challenge 1 you will connect/onboard your existing K8s cluster to Azure Arc.

## Learning Resources
* (https://learn.microsoft.com/en-us/azure/azure-arc/kubernetes/overview)
* (https://learn.microsoft.com/en-us/azure/azure-arc/kubernetes/quickstart-connect-cluster?tabs=azure-cli)
* (https://learn.microsoft.com/en-us/azure/azure-arc/kubernetes/quickstart-connect-cluster)
* (https://learn.microsoft.com/en-us/azure/azure-arc/kubernetes/cluster-connect)
* (https://learn.microsoft.com/en-us/azure/azure-arc/kubernetes/arc-gateway-simplify-networking)
* (https://learn.microsoft.com/en-us/azure/azure-arc/kubernetes/azure-rbac)
* (https://learn.microsoft.com/en-us/azure/azure-arc/kubernetes/kubernetes-resource-view)
* (https://learn.microsoft.com/en-us/cli/azure/connectedk8s?view=azure-cli-latest)

## Solution - Spoilerwarning
[Solution Steps](../walkthroughs/challenge-01/solution.md)
[Solution Steps](../walkthrough/challenge-01/solution.md)

[Next challenge](challenge-02.md) | [Back](../Readme.md)
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ All telemetry, logs, and security signals generated by these services flow into
* https://learn.microsoft.com/en-us/azure/defender-for-cloud/defender-for-containers-arc-enable-programmatically

## Solution - Spoilerwarning
[Solution Steps](../walkthroughs/challenge-02/solution.md)
[Solution Steps](../walkthrough/challenge-02/solution.md)

[Next challenge](challenge-03.md) | [Back](../Readme.md)
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ In challenge 3 you will deploy a LLM/SLM to your Azure Arc-enabled Kubernetes Cl
* (https://github.com/kaito-project/kaito) GPU Based LLM/SLM

### Solution - Spoilerwarning
[Solution Steps](../walkthroughs/challenge-03/solution.md)
[Solution Steps](../walkthrough/challenge-03/solution.md)

[Next challenge](challenge-04.md) | [Back](../Readme.md)
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ In challenge 4 you will deploy Azure Arc-enabled Data Services to your cluster a
* [Azure Arc Data Controller deployment](https://learn.microsoft.com/en-us/azure/azure-arc/data/create-data-controller)

## Solution - Spoilerwarning
[Solution Steps](../walkthroughs/challenge-04/solution.md)
[Solution Steps](../walkthrough/challenge-04/solution.md)

[Next challenge](challenge-05.md) | [Back](../Readme.md)
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ Configure GitOps with Flux for cluster management. In this microhack we chose to
* [Flux documentation - Get started](https://fluxcd.io/docs/get-started/)

## Solution - Spoilerwarning
[Solution Steps](../walkthroughs/challenge-05/solution.md)
[Solution Steps](../walkthrough/challenge-05/solution.md)

[Next challenge](challenge-06.md) | [Back](../Readme.md)
[Back](../Readme.md)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Generated by Terraform — environment-specific
inventory.yml
open-bastion-tunnels.sh
.bastion-tunnel-pids
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Ansible connects via Bastion tunnels to localhost with per-VM ports.
# Run open-bastion-tunnels.sh BEFORE running the playbook.
all:
children:
workstations:
hosts:
%{ for i, idx in indices ~}
workstation-${format("%02d", idx)}:
ansible_host: 127.0.0.1
ansible_winrm_port: ${55986 + i}
bastion_vm_id: ${workstation_vm_ids[i]}
private_ip: ${workstation_ips[i]}
%{ endfor ~}
vars:
ansible_user: ${admin_user}
ansible_connection: winrm
ansible_winrm_scheme: https
ansible_winrm_transport: basic
ansible_winrm_server_cert_validation: ignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/bin/bash
# Auto-generated by Terraform — opens Bastion tunnels for all workstations.
# Each tunnel forwards remote WinRM port 5986 to a unique local port.
# Run this BEFORE running the Ansible playbook.
#
# Usage: ./open-bastion-tunnels.sh
# Stop: ./open-bastion-tunnels.sh --stop

set -e

BASTION_NAME="${bastion_name}"
BASTION_RG="${bastion_rg}"
PIDS_FILE="$(dirname "$0")/.bastion-tunnel-pids"

if [[ "$1" == "--stop" ]]; then
echo "Stopping Bastion tunnels..."
# Kill tracked PIDs
if [[ -f "$PIDS_FILE" ]]; then
while read -r pid; do
kill "$pid" 2>/dev/null && echo " Stopped tunnel (PID $pid)" || true
done < "$PIDS_FILE"
rm -f "$PIDS_FILE"
fi
# Also kill any orphaned bastion tunnel processes for this bastion
pkill -f "az network bastion tunnel --name $BASTION_NAME" 2>/dev/null && echo " Cleaned up orphaned tunnel processes" || true
exit 0
fi

# Kill any leftover tunnels before starting new ones
pkill -f "az network bastion tunnel --name $BASTION_NAME" 2>/dev/null || true
sleep 1

echo "Opening Bastion tunnels (Bastion: $BASTION_NAME in $BASTION_RG)..."
echo ""

> "$PIDS_FILE"

%{ for i, idx in indices ~}
echo " workstation-${format("%02d", idx)}: localhost:${55986 + i} -> ${workstation_ips[i]}:5986"
az network bastion tunnel \
--name "$BASTION_NAME" \
--resource-group "$BASTION_RG" \
--target-resource-id "${workstation_vm_ids[i]}" \
--resource-port 5986 \
--port ${55986 + i} &
echo $! >> "$PIDS_FILE"

%{ endfor ~}

echo ""
echo "All tunnels started. Waiting for tunnels to establish..."
sleep 10
echo "Ready. Run the Ansible playbook now:"
echo " ansible-playbook -i inventory.yml playbook-workstation.yml --extra-vars \"ansible_password=<password>\""
echo ""
echo "To stop tunnels later: $0 --stop"
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
---
# Ansible playbook to configure Windows 11 workstation VMs with all
# MicroHack prerequisites: VS Code, Git, WSL2 + Ubuntu, Azure CLI,
# kubectl, and Helm.
#
# Usage:
# ansible-galaxy collection install -r requirements.yml
# ./open-bastion-tunnels.sh
# ansible-playbook -i inventory.yml playbook-workstation.yml \
# --extra-vars "ansible_password=<admin_password>"
# ./open-bastion-tunnels.sh --stop

- name: Configure Windows 11 Workstation for MicroHack
hosts: workstations
gather_facts: true

tasks:
# ── Windows tools via Chocolatey ──────────────────────────────────

- name: Install Git
chocolatey.chocolatey.win_chocolatey:
name: git
state: present

- name: Install Visual Studio Code
chocolatey.chocolatey.win_chocolatey:
name: vscode
state: present
package_params: "/NoDesktopIcon"

- name: Install VS Code WSL extension
ansible.windows.win_command: cmd /c code --install-extension ms-vscode-remote.remote-wsl
register: vscode_wsl_ext
changed_when: "'was successfully installed' in vscode_wsl_ext.stdout"
failed_when: false

# ── Install WSL2 + Ubuntu ────────────────────────────────────────
# On Win11 24H2 the in-box wsl.exe is a stub that requires Store
# install (which fails in non-interactive WinRM sessions).
# Use Chocolatey to install the WSL2 kernel and Ubuntu distro.

- name: Create temp directory on Windows
ansible.windows.win_file:
path: C:\Temp
state: directory

- name: Enable VirtualMachinePlatform feature
ansible.windows.win_optional_feature:
name: VirtualMachinePlatform
state: present
register: vm_platform

- name: Enable Microsoft-Windows-Subsystem-Linux feature
ansible.windows.win_optional_feature:
name: Microsoft-Windows-Subsystem-Linux
state: present
register: wsl_feature

- name: Reboot after enabling WSL features
ansible.windows.win_reboot:
reboot_timeout: 600
when: wsl_feature.reboot_required | default(false) or
vm_platform.reboot_required | default(false)

- name: Install WSL2 via Chocolatey
chocolatey.chocolatey.win_chocolatey:
name: wsl2
state: present
register: wsl2_choco

- name: Reboot after WSL2 install
ansible.windows.win_reboot:
reboot_timeout: 600
when: wsl2_choco.changed | default(false)

- name: Set WSL default version to 2
ansible.windows.win_command: wsl --set-default-version 2
register: wsl_ver
retries: 3
delay: 10
until: wsl_ver.rc == 0

- name: Check if Ubuntu WSL distribution is already installed
ansible.windows.win_shell: (wsl -l -q) -contains 'Ubuntu'
register: wsl_list
changed_when: false
failed_when: false

- name: Install Ubuntu WSL distribution
ansible.windows.win_command: wsl --install -d Ubuntu --no-launch
register: wsl_install_ubuntu
failed_when: wsl_install_ubuntu.rc not in [0, 1]
when: "'True' not in wsl_list.stdout"

- name: Reboot after Ubuntu install
ansible.windows.win_reboot:
reboot_timeout: 600
when: wsl_install_ubuntu is changed

- name: Initialize Ubuntu distro (first launch)
ansible.windows.win_command: >-
wsl -d Ubuntu -- echo "WSL Ubuntu initialized"
register: wsl_init
retries: 5
delay: 15
until: wsl_init.rc == 0

# ── Install tools inside WSL Ubuntu ──────────────────────────────

- name: Copy WSL tools setup script
ansible.windows.win_copy:
src: ../scripts/setup-wsl-tools.sh
dest: C:\Temp\setup-wsl-tools.sh

- name: Install prerequisite tools in WSL Ubuntu
ansible.windows.win_command: wsl -d Ubuntu -u root -- bash /mnt/c/Temp/setup-wsl-tools.sh
register: wsl_tools
async: 1200
poll: 30

- name: Show WSL tools installation output
ansible.builtin.debug:
var: wsl_tools.stdout_lines
when: wsl_tools.stdout_lines is defined
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
collections:
- name: ansible.windows
version: ">=2.3.0"
- name: chocolatey.chocolatey
version: ">=1.5.0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# ==============================================================================
# Central Azure Bastion for secure RDP/SSH access to all participant VMs.
# Uses Standard SKU with IP-based connection to reach VMs across peered VNets.
# ==============================================================================

# --- Bastion Resource Group ---

resource "azurerm_resource_group" "bastion" {
name = "mh-bastion"
location = var.bastion_location
tags = { "SecurityControl" = "Ignore" }
}

# --- Bastion VNet ---

resource "azurerm_virtual_network" "bastion" {
name = "mh-bastion-vnet"
address_space = [var.bastion_vnet_address_space]
location = azurerm_resource_group.bastion.location
resource_group_name = azurerm_resource_group.bastion.name

tags = {
Project = "simulated onprem k8s cluster for microhack"
}
}

# Azure Bastion requires a subnet named exactly "AzureBastionSubnet"
resource "azurerm_subnet" "bastion" {
name = "AzureBastionSubnet"
resource_group_name = azurerm_resource_group.bastion.name
virtual_network_name = azurerm_virtual_network.bastion.name
address_prefixes = [cidrsubnet(var.bastion_vnet_address_space, 8, 0)] # /24
}

# --- Bastion Public IP ---

resource "azurerm_public_ip" "bastion" {
name = "mh-bastion-ip"
resource_group_name = azurerm_resource_group.bastion.name
location = azurerm_resource_group.bastion.location
allocation_method = "Static"
sku = "Standard"

tags = {
Project = "simulated onprem k8s cluster for microhack"
}
}

# --- Azure Bastion Host (Standard SKU) ---

resource "azurerm_bastion_host" "bastion" {
name = "mh-bastion"
resource_group_name = azurerm_resource_group.bastion.name
location = azurerm_resource_group.bastion.location
sku = "Standard"

# Required for cross-VNet connections via peering
ip_connect_enabled = true
tunneling_enabled = true
shareable_link_enabled = false

ip_configuration {
name = "bastion-ip-config"
subnet_id = azurerm_subnet.bastion.id
public_ip_address_id = azurerm_public_ip.bastion.id
}

tags = {
Project = "simulated onprem k8s cluster for microhack"
}
}

# --- Global VNet Peering: Bastion <-> each participant VNet ---

resource "azurerm_virtual_network_peering" "bastion_to_onprem" {
count = length(local.indices)
name = "bastion-to-${format("%02d", local.indices[count.index])}-onprem"
resource_group_name = azurerm_resource_group.bastion.name
virtual_network_name = azurerm_virtual_network.bastion.name
remote_virtual_network_id = azurerm_virtual_network.onprem[count.index].id
allow_virtual_network_access = true
allow_forwarded_traffic = true
allow_gateway_transit = false
use_remote_gateways = false
}

resource "azurerm_virtual_network_peering" "onprem_to_bastion" {
count = length(local.indices)
name = "${format("%02d", local.indices[count.index])}-onprem-to-bastion"
resource_group_name = azurerm_resource_group.mh_k8s_onprem[count.index].name
virtual_network_name = azurerm_virtual_network.onprem[count.index].name
remote_virtual_network_id = azurerm_virtual_network.bastion.id
allow_virtual_network_access = true
allow_forwarded_traffic = true
allow_gateway_transit = false
use_remote_gateways = false
}

# --- Outputs ---

output "bastion_name" {
value = azurerm_bastion_host.bastion.name
}

output "bastion_resource_group" {
value = azurerm_resource_group.bastion.name
}
Loading
Loading