# Technical Documentation: marcel-dempers/docker-development-youtube-series

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/marcel-dempers/docker-development-youtube-series)

### Overview (`README.md`)

# The Ultimate Engineer Toolbox <img src="https://www.shareicon.net/data/128x128/2017/04/11/883708_media_512x512.png" alt="YouTube" width="5%" height="5%"> :hammer::wrench:

A Collection of tools, hands-on walkthroughs with source code. <br/>
The Ultimate Swiss Army knife for DevOps, Developers and Platform Engineers

<br/>


| Steps  | Playlist :tv: | Source | 
|---|---|---|
| [Kubernetes Lightning Course](https://www.youtube.com/playlist?list=PLHq1uqvAteVuOCnB0QUrf2BJ_jbMeYZ4l) | <a href="https://www.youtube.com/playlist?list=PLHq1uqvAteVuOCnB0QUrf2BJ_jbMeYZ4l" title="kubernetes lightning series"/><img src="https://i.ytimg.com/vi/2_eLxdad4W4/hqdefault.jpg" width="50%" alt="Kubernetes Guide" /></a>| In Video descriptions |
| [Learn Kubernetes (my old series)](./kubernetes/README.md) | <a href="https://www.youtube.com/playlist?list=PLHq1uqvAteVvUEdqaBeMK2awVThNujwMd" title="Kubernetes"><img src="https://i.ytimg.com/vi/8h4FoWK7tIA/hqdefault.jpg" width="50%" alt="Kubernetes Guide" /></a>  | [source](./kubernetes/readme.md) | 
| Learn about CI/CD tools :whale: | <a href="https://www.youtube.com/playlist?list=PLHq1uqvAteVsSsrnZimHEf7NJ1MlRhQUj" title="CI/CD"><img src="https://i.ytimg.com/vi/myCcJJ_Fk10/hqdefault.jpg" width="50%" alt="CI/CD Guide" /></a>  |   |   |   |
| Deploy Kubernetes to the cloud :partly_sunny: | <a href="https://www.youtube.com/playlist?list=PLHq1uqvAteVsUhzNBkn-rPzXtPNpJu1-k" title="Cloud K8s"><img src="https://i.ytimg.com/vi/3jA9EfkSAUU/hqdefault.jpg" width="50%" alt="Cloud Guide" /></a>  | [source](./kubernetes/cloud/readme.md)  | 
| Monitoring Kubernetes :mag: | <a href="https://www.youtube.com/playlist?list=PLHq1uqvAteVuEXCrRkPFWLXRKWNLOVUHn" title="Cloud K8s"><img src="https://i.ytimg.com/vi/5o37CGlNLr8/hqdefault.jpg" width="50%" alt="Cloud Guide" /></a>   | [source](./monitoring/prometheus/kubernetes/readme.md)  | 
| Guide to Logging :page_with_curl: | <a href="https://www.youtube.com/playlist?list=PLHq1uqvAteVvfDxFW50Mdezk0xum-tyHT" title="Cloud K8s"><img src="https://i.ytimg.com/vi/MMVdkzeQ848/hqdefault.jpg" width="50%" alt="Cloud Guide" /></a>   | [source](./monitoring/logging/readme.md)  |  
| Guide to ServiceMesh :globe_with_meridians: | <a href="https://www.youtube.com/playlist?list=PLHq1uqvAteVsmxHpGsMjTOROn3i99lzTA" title="Cloud K8s"><img src="https://i.ytimg.com/vi/rVNPnHeGYBE/hqdefault.jpg" width="50%" alt="Cloud Guide" /></a>   | [source](./kubernetes/servicemesh/readme.md)  |


## Docker Development Basics


| Step :heavy_check_mark:  | Video :movie_camera: | Source Code :octocat: |
|---|---|---|
| Working with `Dockerfiles` <br/>(.NET, Golang, Python, NodeJS) | <a href="https://youtu.be/wyjNpxLRmLg" title="Docker 1"><img src="https://i.ytimg.com/vi/wyjNpxLRmLg/hqdefault.jpg" width="50%" alt="Docker 1" /></a>  | [source](https://github.com/marcel-dempers/docker-development-youtube-series/tree/part1)  |
| Working with code <br/>(.NET, Golang, Python, NodeJS) | <a href="https://youtu.be/EdmKENqnQUw" title="Docker 1"><img src="https://i.ytimg.com/vi/EdmKENqnQUw/hqdefault.jpg" width="50%" alt="Docker 1" /></a>  | [source](https://github.com/marcel-dempers/docker-development-youtube-series/tree/part2)  |
| Docker Multistage explained  | <a href="https://youtu.be/2lQ7WrwpZfI" title="Docker 1"><img src="https://i.ytimg.com/vi/2lQ7WrwpZfI/hqdefault.jpg" width="50%" alt="Docker 1" /></a>  | [source](https://github.com/marcel-dempers/docker-development-youtube-series/tree/part3)  |
| Debugging Go in Docker  | <a href="https://youtu.be/kToyI16IFxs" title="Docker 1"><img src="https://i.ytimg.com/vi/kToyI16IFxs/hqdefault.jpg" width="50%" alt="Docker 1" /></a> | [source](https://github.com/marcel-dempers/docker-development-youtube-series/tree/master/golang)  |
| Debugging .NET in Docker  | <a href="https://youtu.be/ds2bud0ZYTY" title="Docker 1"><img src="https://i.ytimg.com/vi/ds2bud0ZYTY/hqdefault.jpg" width="50%" alt="Docker 1" /></a>  | [source](https://github.com/marcel-dempers/docker-development-youtube-series/tree/part5)  |
| Debugging Python in Docker  | <a href="https://youtu.be/b78Tg-YmJZI" title="Docker 1"><img src="https://i.ytimg.com/vi/b78Tg-YmJZI/hqdefault.jpg" width="50%" alt="Docker 1" /></a>  | [source](https://github.com/marcel-dempers/docker-development-youtube-series/tree/debugging-python)  |
| Debugging NodeJS in Docker  | <a href="https://youtu.be/ktvgr9VZ4dc" title="Docker 1"><img src="https://i.ytimg.com/vi/ktvgr9VZ4dc/hqdefault.jpg" width="50%" alt="Docker 1" /></a>  | [source](https://github.com/marcel-dempers/docker-development-youtube-series/tree/master/nodejs)  |

## Engineering Toolbox :hammer::wrench:


Checkout the toolbox [website](https://marceldempers.dev/toolbox)

<a href="https://marceldempers.dev/toolbox" title="toolbox 1"><img src="./toolbox.png" alt="toolbox 1" /></a>

---

### Overview (`course/README.md`)

# 🎓 The Ultimate DevOps Course (early access 🚧)

This course is my ultimate guide to DevOps,SRE & platform engineering for beginners </br>

<i>🚧 This course is currently under early access and undergoing constant review and updates</i>

<h3>🚀 Setting the stage</h3>

<p>In my many years working in the field, I have noticed a range of challenges and problems that people face when trying to start a DevOps, SRE, platform or cloud engineering career.</p>

<h3>🔴The current challenges of careers in Tech - <i>where to start ?</i></h3>

 <p>DevOps has its challenges:</p>
<ul>
  <li>There is no simple starting point to start learning about DevOps</li>
  <li>DevOps requires a very broad range of skills that can be very overwhelming for new people to learn</li>
  <li>DevOps is often treated as "Dev" and "Ops" which means you need understanding of two very broad fields</li>
  <li>DevOps technologies are littered with buzz words, such as cloud, AI, machine learning, CI/CD, scalability, immutable infrastructure, automation, security and it never ends</li>
</ul>

<p>Qualifications have its challenges:</p>
<ul>
  <li>Universities or Colleges offer IT degrees or courses, which often only cover traditional & "legacy" IT knowledge.</li>
  <li>They do not cover real world challenges faced when working with the cloud, or working with popular open source technologies or modern challenges that businesses face these days</li>
  <li>These technologies are often evolving at a rapid pace and traditional Universities cannot keep up with these.</li>
</ul>

<p>Cloud certifications have its challenges:</p>
<ul>
  <li>Cloud certifications often only focus on a specific cloud provider which can build bias</li>
  <li>Often only solve really specific problems, and miss real world problems</li>
  <li>Develop very narrow focussed problem solving skill</li>
  <li>Miss out on powerful open source skills</li>
  <li>Vendor locking: Often stuck only specialising in one vendor's product</li>
</ul>

<h3>🎯 Course Goals & Expectations</h3>

<p>This course provides a practical hands-on roadmap into the world of DevOps, SRE and platform engineering.</p>
<p>It will set you up with all the fundamentals required for successful future learning. </br>
During this course you will apply a wide range of DevOps principles and use modern technologies in a real world hands-on manner to create yourself a personal website. </br> 
While building your website, we will cover topics such as:</p>

<ul> 
<li>Source Control</li>
<li>Developer Tools</li>
<li>Operating Systems</li>
<li>Servers & Virtualization</li>
<li>Web technology</li>
<li>Monitoring, Logging, Metrics</li>
<li>Observability</li>
<li>Command line and automation scripting</li>
<li>CI / CD Pipelines</li>
<li>Infrastructure as Code</li>
<li>Basics of Cloud </li>
<li>Cloud deployments </li>
</ul>

<p>The personal website created throughout this course will become your marketing tool for your career to demonstrate all your newly obtained above-mentioned skills and abilities.</br>
You will build the ultimate foundation and be equipped to make more informed decisions on what you want to focus on next in your career. </br></p>

<h3>🛠 System & Other Requirements</h3>
<ul> 
<li><b>a Github account:</b> To follow along, we will be creating GitHub accounts</li>
<li><b>Operating System:</b> Windows (Pro and Enterprise ), Linux or MacOS</li>
  * <i>note: You'll need Pro or Enterprise Windows to support Virtualization</i>

<li>You will need at least 2GB EXTRA RAM and 25GB of hard disk space to complete modules related to virtualization.</li>
  * <i>These numbers are early access guide only and may be reviewed and adjusted based on course feedback</i>

</ul>

## 📖 Table of Contents 

* [Chapter 1: Source Control & GIT](./chapters/chapter-1-source-control-git/README.md)
* [Chapter 2: Operating Systems](./chapters/chapter-2-operating-systems/README.md)
* [Chapter 3: Linux Monitoring](./chapters/chapter-3-linux-monitoring/README.md)
* [Chapter 4: The Web & HTTP](./chapters/chapter-4-web-and-http/README.md)
* [Chapter 5: CI/CD & Automation](./chapters/chapter-5-ci-cd/README.md)
* 🚧[Chapter 6: Containers & Docker](./chapters/chapter-6-docker/README.md)
* 🚧[Chapter 7: Docker Compose ](./chapters/chapter-7-docker-compose/README.md)
* 🚧[Chapter 8: Infrastructure as Code ](./chapters/chapter-8-infra-as-code/README.md)
* 🚧[Chapter 9: Introduction to Cloud ](./chapters/chapter-9-intro-to-cloud/README.md)

## 📖 Where to find and explore the full course

You can explore the course on my [website](https://marceldempers.dev/)

---

### Overview (`deno/README.md`)

# Introduction to Deno with Docker

<a href="https://youtu.be/4EfnECkCx8E" title="Kubernetes"><img src="https://i.ytimg.com/vi/4EfnECkCx8E/hqdefault.jpg" width="20%" alt="introduction to deno" /></a>

---

### Overview (`drone-ci/README.md`)

# Introduction to Drone CI

<a href="https://youtu.be/myCcJJ_Fk10" title="drone ci"><img src="https://i.ytimg.com/vi/myCcJJ_Fk10/hqdefault.jpg" width="20%" alt="introduction to drone ci" /></a>

---

### Overview (`jenkins/readme.md`)

# Jenkins on Amazon Kubernetes

For running Jenkins on AMAZON, start [here](./amazon-eks/readme.md)

# Jenkins on Local (Docker Windows \ Minikube \ etc)

<a href="https://youtu.be/eRWIJGF3Y2g" title="jenkins"><img src="https://i.ytimg.com/vi/eRWIJGF3Y2g/hqdefault.jpg" width="20%" alt="jenkins" /></a> 

For running Jenkins on Local Docker for Windows or Minikube <br/>
Watch the [video](https://youtu.be/eRWIJGF3Y2g)

# Setting up Jenkins Agent

After installing `kubernetes-plugin` for Jenkins
* Go to Manage Jenkins | Bottom of Page | Cloud | Kubernetes (Add kubenretes cloud)
* Fill out plugin values
    * Name: kubernetes
    * Kubernetes URL: https://kubernetes.default:443
    * Kubernetes Namespace: jenkins
    * Credentials | Add | Jenkins (Choose Kubernetes service account option & Global + Save)
    * Test Connection | Should be successful! If not, check RBAC permissions and fix it!
    * Jenkins URL: http://jenkins
    * Tunnel : jenkins:50000
    * Apply cap only on alive pods : yes!
    * Add Kubernetes Pod Template
        * Name: jenkins-slave
        * Namespace: jenkins
        * Service Account: jenkins
        * Labels: jenkins-slave (you will need to use this label on all jobs)
        * Containers | Add Template
            * Name: jnlp
            * Docker Image: aimvector/jenkins-slave
            * Command to run : <Make this blank>
            * Arguments to pass to the command: <Make this blank>
            * Allocate pseudo-TTY: yes
            * Add Volume
                * HostPath type
                * HostPath: /var/run/docker.sock
                * Mount Path: /var/run/docker.sock
        * Timeout in seconds for Jenkins connection: 300
* Save

# Test a build

To run docker commands inside a jenkins agent you will need a custom jenkins agent with docker-in-docker working.
Take a look and build the docker file in `./dockerfiles/jenkins-agent`
Push it to a registry and use it instead of above configured `* Docker Image: jenkins/jnlp-slave`
If you do not use the custom image, the below pipeline will not work because default `* Docker Image: jenkins/jnlp-slave` public image does not have docker ability.

* Add a Jenkins Pipeline

```
node('jenkins-slave') {
    
     stage('unit-tests') {
        sh(script: """
            docker run --rm alpine /bin/sh -c "echo hello world"
        """)
    }
}
```

---

### Overview (`kubernetes/readme.md`)

# Learn Kubernetes <img src="https://www.shareicon.net/data/128x128/2017/04/11/883708_media_512x512.png" alt="YouTube" width="5%" height="5%"> :hammer::wrench:

## Full playlist

<a href="https://www.youtube.com/playlist?list=PLHq1uqvAteVvUEdqaBeMK2awVThNujwMd" title="Kubernetes"><img src="https://i.ytimg.com/vi/8h4FoWK7tIA/hqdefault.jpg" width="50%" height="50%" alt="Kubernetes Guide" /></a>

## Getting Started with Kubernetes on Windows for beginners

<a href="https://www.youtube.com/watch?v=8h4FoWK7tIA" title="Kubernetes"><img src="https://i.ytimg.com/vi/8h4FoWK7tIA/hqdefault.jpg" width="25%" height="25%" alt="Kubernetes Guide" /></a>

## Kubectl

Checkout [kubectl](./kubectl.md) for detailed steps

<a href="https://www.youtube.com/watch?v=feLpGydQVio" title="Kubernetes"><img src="https://i.ytimg.com/vi/feLpGydQVio/hqdefault.jpg" width="25%" height="25%" alt="Kubernetes Guide" /></a>

## Deployments

Checkout [deployments](./deployments/readme.md) for detailed steps

<a href="https://www.youtube.com/watch?v=DMpEZEakYVc" title="Kubernetes"><img src="https://i.ytimg.com/vi/DMpEZEakYVc/hqdefault.jpg" width="25%" height="25%" alt="Kubernetes Guide" /></a>

## Configuration Management

<a href="https://www.youtube.com/watch?v=o-gXx7r7Rz4" title="Kubernetes"><img src="https://i.ytimg.com/vi/o-gXx7r7Rz4/hqdefault.jpg" width="25%" height="25%" alt="Kubernetes Guide" /></a>

## Secret Management

<a href="https://www.youtube.com/watch?v=o36yTfGDmZ0" title="Kubernetes"><img src="https://i.ytimg.com/vi/o36yTfGDmZ0/hqdefault.jpg" width="25%" height="25%" alt="Kubernetes Guide" /></a>

## Services

<a href="https://www.youtube.com/watch?v=xhva6DeKqVU" title="Kubernetes"><img src="https://i.ytimg.com/vi/xhva6DeKqVU/hqdefault.jpg" width="25%" height="25%" alt="Kubernetes Guide" /></a>

## Ingress

<a href="https://www.youtube.com/watch?v=u948CURLDJA" title="Kubernetes"><img src="https://i.ytimg.com/vi/u948CURLDJA/hqdefault.jpg" width="25%" height="25%" alt="Kubernetes Guide" /></a>
<a href="https://www.youtube.com/watch?v=izWCkcJAzBw" title="Kubernetes"><img src="https://i.ytimg.com/vi/izWCkcJAzBw/hqdefault.jpg" width="25%" height="25%" alt="Kubernetes Guide" /></a>

## StatefulSets

<a href="https://www.youtube.com/watch?v=zj6r_EEhv6s" title="Kubernetes"><img src="https://i.ytimg.com/vi/zj6r_EEhv6s/hqdefault.jpg" width="25%" height="25%" alt="Kubernetes Guide" /></a>

## Persistent Volumes

<a href="https://www.youtube.com/watch?v=ZxC6FwEc9WQ" title="Kubernetes"><img src="https://i.ytimg.com/vi/ZxC6FwEc9WQ/hqdefault.jpg" width="25%" height="25%" alt="Kubernetes Guide" /></a>

## Manage YAML

<a href="https://www.youtube.com/watch?v=5gsHYdiD6v8" title="Kubernetes"><img src="https://i.ytimg.com/vi/5gsHYdiD6v8/hqdefault.jpg" width="25%" height="25%" alt="Kubernetes Guide" /></a>

---

### Overview (`tracing/README.md`)

# Introduction to Distributed Tracing

<a href="https://youtu.be/idDu_jXqf4E" title="tracing-intro"><img src="https://i.ytimg.com/vi/idDu_jXqf4E/hqdefault.jpg" width="20%" alt="tracing-intro" /></a> 

In this episode we take a look at distributed tracing.
We'll take a look at the concept, what distributed tracing is, what problems it solves, how to emit traces and the platform architecture to collect traces.

## Example microservice architecture

First of all, we need an example application. 
In this demo, I have a few microservices that work together to form a video catalog.

## A simple Web UI: videos-web
<hr/>
<br/>

Consider `videos-web` <br/>
It's an HTML application that lists a bunch of playlists with videos in them.

```
+------------+
| videos-web |
|            |
+------------+
```
<br/>

## A simple API: playlists-api
<hr/>
<br/>

For `videos-web` to get any content, it needs to make a call to `playlists-api`

```
+------------+     +---------------+
| videos-web +---->+ playlists-api |
|            |     |               |
+------------+     +---------------+

```

Playlists consist of data like `title`, `description` etc, and a list of `videos`. <br/>
Playlists are stored in a database. <br/>
`playlists-api` stores its data in a database

```
+------------+     +---------------+    +--------------+
| videos-web +---->+ playlists-api +--->+ playlists-db |
|            |     |               |    |              |
+------------+     +---------------+    +--------------+

```

<br/>

## A little complexity
<hr/>
<br/>

Each playlist item contains only a list of video id's. <br/>
A playlist does not have the full metadata of each video. <br/>

Example `playlist`:
```
{
  "id" : "playlist-01",
  "title": "Cool playlist",
  "videos" : [ "video-1", "video-x" , "video-b"]
}
```
Take not above `videos: []` is a list of video id's <br/>

Videos have their own `title` and `description` and other metadata. <br/>

To get this data, we need a `videos-api` <br/>
This `videos-api` has its own database too <br/>

```
+------------+       +-----------+
| videos-api +------>+ videos-db |
|            |       |           |
+------------+       +-----------+
```

For the `playlists-api` to load all the video data, it needs to call `videos-api` for each video ID it has.<br/>
<br/>

## Traffic flow
<hr/>
<br/>
A single `GET` request to the `playlists-api` will get all the playlists 
from its database with a single DB call <br/>

For every playlist and every video in each list, a separate `GET` call will be made to the `videos-api` which will
retrieve the video metadata from its database. <br/>

This will result in many network fanouts between `playlists-api` and `videos-api` and many call to its database. <br/>
This is intentional to demonstrate a busy network.

<br/>

## Full application architecture
<hr/>
<br/>

```

+------------+     +---------------+    +--------------+
| videos-web +---->+ playlists-api +--->+ playlists-db |
|            |     |               |    |    [redis]   |
+------------+     +-----+---------+    +--------------+
                         |
                         v
                   +-----+------+       +-----------+
                   | videos-api +------>+ videos-db |
                   |            |       |  [redis]  |
                   +------------+       +-----------+

```
<br/>

## Run the apps: Docker
<hr/>
<br/>
There is a `docker-compose.yaml`  in this directory. <br/>
Change your terminal to this folder and run:

```
cd tracing

docker-compose build

docker-compose up

```

You can access the app on `http://localhost`. <br/> 
You should now see the complete architecture in the browser 
<br/>

## Traces

<hr/>
<br/>

To see our traces, we can access the Jaeger UI on `http://localhost:16686`

---

### Overview (`ai/agents/README.md`)

# Introduction to Agents

<a href="https://youtu.be/k3lS_wRNruA" title="agents"><img src="https://i.ytimg.com/vi/k3lS_wRNruA/hqdefault.jpg" width="40%" alt="agents" /></a>

In this video guide we discuss what an AI agent is from a DevOps, SRE & Platform Engineering perspective. </br>

Engineers will play a key role in:

* Hosting Agents in cloud, Clusters, Local, etc
* Understand traffic routing to agents
* Securing Agentic systems

In this guide we learn about:

* What an agent is
* Different forms of AI Agents
* Context engineering
* Agents.md & System Instructions
* Subagents
* Agent Skills

## Agents.md

AI Agents usually follow their own System Instructions depending on the Agent type. For example Gemini looks for `GEMINI.md` at the root of the repo. Claude looks for `CLAUDE.md`

There is a open standard for [AGENTS.md](https://agents.md/). </br>

Our example [AGENTS.md](./AGENTS.md) can be copied to the root of this repo to demonstrate a system instruction for an agent using a tool like OpenCode. </br>

For a deeper dive in setting up agents for each AI (Gemini,Claude,Copilot,etc), checkout the playlist [here](https://www.youtube.com/playlist?list=PLHq1uqvAteVtCsuixHwiNPQGZ1x5UVLo9)

## Subagents

Agent usually have one contenxt window. </br>
Subagents provide capability to have dedicated agents for each specilized task with their own context windows. </br> For example, you can have a dedicated agent for troubleshooting or debugging, vs Code Review. This means you can keep the context window of the main agent free. </br> 

Our example [subagent](./k8s_expert.md) can be copied into the subagent directory for OpenCode which is `.opencode/agents/k8s_expert/k8s_expert.md` and our OpenCode terminal will pick it up on restart. </br>

For a deeper dive in setting up subagents for each AI (Gemini,Claude,Copilot,etc), checkout the playlist [here](https://www.youtube.com/playlist?list=PLHq1uqvAteVtCsuixHwiNPQGZ1x5UVLo9)


## Agent Skills

If you look at Agents, you will notice the markdown file may become quite large. </br>
We have a lot of commands and scripts inside that file. 

Agent Skills allow us to take this to the next level. Instead of having all the "brains" in the Agents.md file, we can break up functionality and place them in skills. </br>

In this video, we'll discuss why skills are so important and how to use them </br>

There is an open standard written for [Agent Skills](https://agentskills.io/home) as well. </br>

Our example skills folder can be copied to the dedicated `.agents/skills/<name-of-skills>` folder where our OpenCode terminal will pick it up. 

The main thing you will notice once we use skills, is that the Agents instruction file will become a lot leaner and we can essentially move all the "how to"'s out and into scripts. </br>

The instructions then just clearly point to skills that the agent can execute. </br>

We can summarise it as: 

```markdown
---
name: k8s_expert
description: A specialized kubernetes agent specialised in local cluster development and troubleshooting.
---

You help engineers provision, inspect, and debug Kubernetes clusters running on their local machine.

## Persona & Behaviour

- Speak like a senior platform engineer: direct, precise, no hand-holding
- Always confirm tool availability before running commands
- Prefer `kind` for local clusters unless the user specifies otherwise
- When creating clusters, always confirm the cluster name and Kubernetes version first
- When troubleshooting, always check events before logs

## Available Skills

* Provision test k8s infrastructure for testing technical guides using provided available skill

```

We can proceed to use the skill by either referring to it in a prompt or depending on the AI app we are using, for example we can use `/skills` in OpenCode to execute a skill. </br>

---

### Overview (`ai/mcp/README.md`)

# Introduction to MCP

<a href="https://youtu.be/OKIyGSUgOD0" title="mcp"><img src="https://i.ytimg.com/vi/OKIyGSUgOD0/hqdefault.jpg" width="40%" alt="mcp" /></a>

## Running an MCP

Start our simple portable sandbox in a linux container. </br>
We'll expose a port so we can showcase `HTTP` calls as well. </br>

```shell
docker run -it --rm \
  -p 8080:8080 \
  ubuntu:latest bash

# install some tools we'll use:
apt-get update && apt-get install -y curl tree git jq tar

#install nodejs and python as a requirement for many MCP servers
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash
# in lieu of restarting the shell
\. "$HOME/.nvm/nvm.sh"
nvm install 24
node -v

apt-get install -y python3 python3-pip python3-venv# Create the environment
python3 -m venv /opt/venv
source /opt/venv/bin/activate
```

### Example 1: STDIO (File & GIT MCP Demo)

Firstly, let's look at a `git` MCP server. </br>
To do this we need a basic `git` repo, lets create one:

```shell
mkdir -p /workspace && cd /workspace
echo "# Hello MCP" > README.md
echo "console.log('hello')" > app.js
mkdir src && echo "export default {}" > src/index.js
tree

git init
git config user.email "demo@mcp.dev"
git config user.name "MCP Demo"
git add .
git commit -m "initial commit"

# leave an uncommitted change for the diff demo
echo "## Updated" >> README.md
```


#### File MCP Example:

Use the MCP in three steps:

```shell
(
  echo '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1}'
  echo '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"list_directory","arguments":{"path":"/workspace"}},"id":2}'
  echo '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"read_text_file","arguments":{"path":"/workspace/README.md"}},"id":3}'
  sleep 2
) | npx -y @modelcontextprotocol/server-filesystem /workspace 2>/dev/null | jq -c .
```

#### GIT MCP Example:

Setup `git` MCP:

```shell
pip3 install mcp-server-git
```

Use the MCP in three steps:

```shell
(
  echo '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1}'
  echo '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"git_log","arguments":{"repo_path":"/workspace","max_count":3}},"id":2}'
  echo '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"git_diff_unstaged","arguments":{"repo_path":"/workspace"}},"id":3}'
  sleep 2
) | python3 -u -m mcp_server_git --repository /workspace 2>/dev/null | jq -c .
```

### Example 2: HTTP (GitHub & K8s MCP Demo)

In this example, we move from stdio to HTTP which is basically just MCP hosted over a Web Endpoint. </br>

#### Github MCP Example:

There are official MCP servers available online. The [Official GitHub MCP server](https://github.com/github/github-mcp-server) is what we will be using </br>

Let's install it:

```shell
curl -fsSL https://github.com/github/github-mcp-server/releases/download/v1.0.5/github-mcp-server_Linux_x86_64.tar.gz \
  | tar -xz -C /usr/local/bin github-mcp-server
github-mcp-server --help
```

Start it:

```shell 
github-mcp-server http --port 8080
```

We need to provide authentication with Github:

```shell
export GITHUB_PERSONAL_ACCESS_TOKEN=''
```
Call it in three steps:

```shell
# step-1: initialise
curl -s -i http://localhost:8080/mcp \
       -H "Content-Type: application/json" \
       -H "Accept: application/json, text/event-stream" \
       -H "Authorization: Bearer ${GITHUB_PERSONAL_ACCESS_TOKEN}" \
       -d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1}'

MCP_SESSION_ID=''

# step-2: list tools
curl -s http://localhost:8080/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer ${GITHUB_PERSONAL_ACCESS_TOKEN}" \
  -H "Mcp-Session-Id: ${MCP_SESSION_ID}" \
  -d '{"jsonrpc":"2.0","method":"tools/list","id":2}'

# step-3: tool call
curl -s http://localhost:8080/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer ${GITHUB_PERSONAL_ACCESS_TOKEN}" \
  -H "Mcp-Session-Id: ${MCP_SESSION_ID}" \
  -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"search_repositories","arguments":{"query":"kubernetes gateway-api","sort":"stars","perPage":5}},"id":3}'
```

#### Kubernetes MCP Example:

Lets create a Kubernetes cluster to play with using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/)

```shell
kind create cluster --name mcp --image kindest/node:v1.34.0
```

In this example we will use a Kubernetes MCP and setup a read-only `ServiceAccount` so it can read our cluster. </br>

```shell
kubectl apply -f kubernetes/gateway-api/agentgateway/mcp/kubernetes/02-mcp.yaml

# check mcp server
kubectl get pods
kubectl get sa
kubectl get service

# forward ports
kubectl port-forward svc/mcp-k8s 8080
```

Call it in three steps:

```shell 
# step-1: initialise
curl -s -i http://localhost:8080/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1}'

MCP_SESSION_ID=''

# step-2: list tools
curl -s http://localhost:8080/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Mcp-Session-Id: ${MCP_SESSION_ID}" \
  -d '{"jsonrpc":"2.0","method":"tools/list","id":2}'

# step-3: tool call
curl -s http://localhost:8080/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Mcp-Session-Id: ${MCP_SESSION_ID}" \
  -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"namespaces_list","arguments":{}},"id":3}'
```

---

### Overview (`apache/kafka/README.md`)

# Introduction to Kafka

This guide is under the messaging section alongside other message brokers like `RabbitMQ` etc. </br>
Checkout the guide under the [messaging/kafka](../../messaging/kafka/README.md) folder

---

### Overview (`argo/argo-cd/README.md`)

# Introduction to Argo CD

<a href="https://youtu.be/2WSJF7d8dUg" title="argo"><img src="https://i.ytimg.com/vi/2WSJF7d8dUg/hqdefault.jpg" width="20%" alt="introduction to argo cd" /></a>

---

### Overview (`golang/introduction/readme.md`)

# Introduction to Learning Go

<a href="https://youtu.be/jpKysZwllVw" title="golang-part-1"><img src="https://i.ytimg.com/vi/jpKysZwllVw/hqdefault.jpg" width="20%" alt="introduction to Go part 1" /></a>

Go can be downloaded from [golang.org](https://golang.org/doc/install) <br/>

Test your `go` installation:

```
go version
```

# Run Go in Docker

We can also run go in a small docker container: <br/>

```
cd golang\introduction

docker build --target dev . -t go
docker run -it -v ${PWD}:/work go sh
go version

```

# Code Structure

https://golang.org/doc/code.html

* Package: 
  - Source files in same directory that are compiled together
  - Have visibility on all source files in the same package

* Modules:
  - Collection of packages that are released together

Our repository can contain one or more go modules, but usually 1.
  - At the root of the repo

`go.mod` Declares module path + import path for packages. (Where to download them)
  - When we write our own program, we can define a module path
  - This allows us to publish our code (if we want), so others can download it
  - The module path could be something like `github.com/google/go-cmp`
  - Makes it easy for other programs to consume our module

# Our first Program

* Create a folder containing our application

```
mkdir app

```

* Define a module path (github.com/docker-development-youtube-series/golang/introdouction/app)

```
# change directory to your application source code

cd app

# create a go module file

go mod init github.com/docker-development-youtube-series/golang/introdouction/app

```

* Create basic source code

In the `app` folder, create a program called `app.go`
Paste the following content into it.

```
package main

import "fmt"

func main() {
	fmt.Println("Hello, world.")
}
```

* Run your application code

You can run your application 

```
go run app.go
```

# Building our Program

Build your application into a static binary: <br/>

```
go build 
```

This will produce a compiled program called `app`
You can run this program easily:

```
./app
```

# Install your application (optional)

"This command builds the app command, producing an executable binary. <br/> 
It then installs that binary as $HOME/go/bin/app (or, under Windows, %USERPROFILE%\go\bin\app.exe)"

```
go install github.com/docker-development-youtube-series/golang/introdouction/app
```

# The Code

## Functions 

In the video we cover writing functions. </br>
It allows us to execute a block of code <br/>
You want to give your function a single purpose <br/>
Functions can have an input and return an output <br/>
Well thought out functions makes it easier to write tests <br/>

Instead of doing a boring `x + y` function that adds two numbers, let's do something a little
more realistic but still basic: 

```
// This function returns some data 
// The data could be coming from a database, or a file.
// The person calling the function should not care
// Since the function does not leak its Data provider

func getData(inputs)(outputs){
}

// functions can take multiple inputs, and return multiple outputs

// lets say we have 1) customers and 2) the cities they are from
// we may want to 1) get a list of customers and 2) get a list of cities
// therefore we have 2 types of data, 1) customers 2) cities
// let's improve our function so its gets data based on the type

func getData(customerId int) (customer string) {
}
```

## Variables

To hold data in programming languages, we use variables. <br/>
Variables take up space in memory, so we want to keep it minimal. <br/>
Let's declare variables in our function

```
func getData(customerId int) (customer string) {
  var firstName = "Marcel"
  lastName := "Dempers"
  
  fullName := firstName + " " + lastName
  return fullName

  //or we can return the computation instead of adding another variable!
  return firstName + " " + lastName

  //or we dont even need to declare variables :)
  return "Marcel Dempers"

}

```

## Control Flows (if\else)

You can see we're not using the `customerId` input in our function. <br/>
Let's use it! <br/>

Control flows allow us to add "rules" to our code. </br>
"If this is the case, then do that, else do something else".

So let's say we have a customer ID 1 coming in, we may only want to
return our customer if it matches the `customerId`

```

func getData(customerId int) (customer string) {

  if customerId == 1 {
    return "Marcel Dempers"
  } else if customerId == 2 {
    return "Bob Smith"
  } else {
    return ""
  }
  
}


```

Let's invoke our function :

```
//in the main() function

//get our customer
customer := getData(1)
fmt.Println(customer)

//get the wrong customer
customer := getData(3)
fmt.Println(customer)


```
## Arrays

At the moment, we can only return 1 customer at a time on our function. <br/>
Realistically we need the ability to return more data, not just a single customer. <br/>

Arrays allow us to make a collection of variables of the same type. <br/>
We can now return a list of customers! <br/>

Let's change our function to get an array of customers!

```
func getData() (customers [2]string) {
  //create 1 record
  customer := "Marcel Dempers"

  //assign our customer to the array
  customers[0] = customer

  //OR we can assign it like this
  customers[1] = "Bob Smith"

  //send it back to the caller
  return customers
  
}

```

Now we also have to change our calling function to expect an array:

```
customers := getData()

fmt.Println(customers)

```

## Slices

Since arrays are fixed size, Slices are a dynamically-sized view into arrays.
Let's create a slice instead of array so we can add customers dynamically!

```
func getData() (customers []string) {
  
  //initialise our slice of type string
  customers = []string{ "Marcel Dempers", "Bob Smith", "John Smith"}
  
  //add more legendary customers dynamically 
  customers = append(customers, "Ben Spain")
  customers = append(customers, "Aleem Janmohamed")
  customers = append(customers, "Jamie le Notre")
  customers = append(customers, "Victor Savkov")
  customers = append(customers, "P The Admin")
  customers = append(customers, "Adrian Oprea")
  customers = append(customers, "Jonathan D")

  //send it back to the caller
  return customers
  
}


```

## Loops 

Loops are used to iterate over collections, lists, arrays etc. <br/>
Let's say we need to loop through our customers

In the `main()` function, we can grab the list of customers and loop them.
In this demo, we'll cover a basic for loop, but there are several approaches to writing loops.

```
//loop forever
for {
  //any code in here will run forever!
  
  fmt.Println("Infinite Loop 1")
	time.Sleep(time.Second)

  //unless we break out the loop like this
  break
}

//loop for x number of loops
for x := 0; x < 10; x++ {

  //any code in here will run 10 times! (unless we break!)
  fmt.Println(customers[x])

}

//loop for ALL our customer

for x, customer := range customers {

  //we can access the "customer" variable in this approach
  customer = customers[x]
  fmt.Println(customer)

  //OR
  //we can use the supplied customer from the loop
  // and silence the x variable, replace it with a _ character
  fmt.Println(customer)
}

```

## Structs

So far so good, however, customer data is not useful as strings. <br/>
Customers can have a firstname, lastname, and more properties. <br/>

For this purpose we'd like to group some variables into a single variable. <br/>
This is what `struct` allows us to do. <br/>
Let's create a `struct` for our customer

Let's create a new `go` file called `customers.go`

```
package main

Customer struct {
  FirstName string
  LastName string
  FullName string
}

```

Let's put it all together:

In `customers.go`, let's create a function to get customers

```
func GetCustomers()(customers []Customer) {

  //we can declare customers like this:
  marcel := 	Customer{ FirstName: "Marcel", LastName: "Dempers" }

	customers = append(customers,
		Customer{ FirstName: "Marcel", LastName: "Dempers" },
		Customer{ FirstName: "Ben", LastName: "Spain" },
		Customer{ FirstName: "Aleem", LastName: "Janmohamed" },
		Customer{ FirstName: "Jamie", LastName: "le Notre" },
		Customer{ FirstName: "Victor", LastName: "Savkov" },
		Customer{ FirstName: "P", LastName: "The Admin" },
		Customer{ FirstName: "Adrian", LastName: "Oprea" },
		Customer{ FirstName: "Jonathan", LastName: "D" },
	)

	return customers

}
```

In `main()` we can now call our shiny new function


```
customers := GetCustomers()

for _, customer := range customers {
  //we can access the "customer" variable in this approach
  fmt.Println(customer)
}
```

# Docker

For our dev environment, we have a simple image using `go` <br/>
We also set a work directory and alias the target as `dev`

This means we can use this container layer as a development environment. <br/>
Later down the track we can add debuggers in here for example. <br/> 
Checkout my debugging video for go: https://youtu.be/kToyI16IFxs  <br/>


## Development environment

```
FROM golang:1.15 as dev

WORKDIR /work
```

## Building our code

```
FROM golang:1.15 as build

WORKDIR /app
COPY ./app/* /app/
RUN go build -o app
```

## The Runtime

```
FROM alpine as runtime 
COPY --from=build /app/app /
CMD ./app
```

## Building the Container

```
docker build . -t customer-app

```

## Running the Container

```
docker run customer-app
```

---

### Overview (`hashicorp/vault-2022/readme.md`)

# Hashicorp Vault Guide

<a href="https://youtu.be/2Owo4Ioo9tQ" title="hashicorp-vault"><img src="https://i.ytimg.com/vi/2Owo4Ioo9tQ/hqdefault.jpg" width="20%" alt="introduction hashicorp vault" /></a>

Requirements:

* Kubernetes 1.21
* Kind or Minikube

For this tutorial, I will be using Kubernetes 1.21.
If you are watching the old guide for Kubernetes 1.17, go [here](..\vault\readme.md)

Lets create a Kubernetes cluster to play with using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/)

```
cd hashicorp/vault-2022

kind create cluster --name vault --image kindest/node:v1.21.1 --config kind.yaml
```

Next up, I will be running a small container where I will be doing all the work from:
You can skip this part if you already have `kubectl` and `helm` on your machine.

```
docker run -it --rm --net host -v ${HOME}/.kube/:/root/.kube/ -v ${PWD}:/work -w /work alpine sh
```

Install `kubectl`

```
apk add --no-cache curl
curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl
chmod +x ./kubectl
mv ./kubectl /usr/local/bin/kubectl
```

Install `helm`

```
curl -LO https://get.helm.sh/helm-v3.7.2-linux-amd64.tar.gz
tar -C /tmp/ -zxvf helm-v3.7.2-linux-amd64.tar.gz
rm helm-v3.7.2-linux-amd64.tar.gz
mv /tmp/linux-amd64/helm /usr/local/bin/helm
chmod +x /usr/local/bin/helm
```

Now we have `helm` and `kubectl` and can access our `kind` cluster:

```
kubectl get nodes
NAME                  STATUS   ROLES                  AGE   VERSION
vault-control-plane   Ready    control-plane,master   37s   v1.21.1
```

Let's add the Helm repositories, so we can access the Kubernetes manifests

```
helm repo add hashicorp https://helm.releases.hashicorp.com
```

## Storage: Consul

We will use a very basic Consul cluster for our Vault backend. </br>
Let's find what versions of Consul are available:

```
helm search repo hashicorp/consul --versions
```

We can use chart `0.39.0` which is the latest at the time of this demo
Let's create a manifests folder and grab the YAML:

```

mkdir manifests

helm template consul hashicorp/consul \
  --namespace vault \
  --version 0.39.0 \
  -f consul-values.yaml \
  > ./manifests/consul.yaml
```

Deploy the consul services:

```
kubectl create ns vault
kubectl -n vault apply -f ./manifests/consul.yaml
kubectl -n vault get pods
```


## TLS End to End Encryption

See steps in [./tls/ssl_generate_self_signed.md](./tls/ssl_generate_self_signed.md)
You'll need to generate TLS certs (or bring your own)
Remember not to check-in your TLS to GIT :)

Create the TLS secret 

```
kubectl -n vault create secret tls tls-ca \
 --cert ./tls/ca.pem  \
 --key ./tls/ca-key.pem

kubectl -n vault create secret tls tls-server \
  --cert ./tls/vault.pem \
  --key ./tls/vault-key.pem
```

## Generate Kubernetes Manifests


Let's find what versions of vault are available:

```
helm search repo hashicorp/vault --versions
```

In this demo I will use the `0.19.0` chart </br>

Let's firstly create a `values` file to customize vault.
Let's grab the manifests:

```
helm template vault hashicorp/vault \
  --namespace vault \
  --version 0.19.0 \
  -f vault-values.yaml \
  > ./manifests/vault.yaml
```

## Deployment

```
kubectl -n vault apply -f ./manifests/vault.yaml
kubectl -n vault get pods
```

## Initialising Vault

```
kubectl -n vault exec -it vault-0 -- sh
kubectl -n vault exec -it vault-1 -- sh
kubectl -n vault exec -it vault-2 -- sh

vault operator init
vault operator unseal

kubectl -n vault exec -it vault-0 -- vault status
kubectl -n vault exec -it vault-1 -- vault status
kubectl -n vault exec -it vault-2 -- vault status

```
## Web UI

Let's checkout the web UI:

```
kubectl -n vault get svc
kubectl -n vault port-forward svc/vault-ui 443:8200
```
Now we can access the web UI [here](https://localhost/)

## Enable Kubernetes Authentication

For the injector to be authorised to access vault, we need to enable K8s auth

```
kubectl -n vault exec -it vault-0 -- sh 

vault login
vault auth enable kubernetes

vault write auth/kubernetes/config \
token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
kubernetes_host=https://${KUBERNETES_PORT_443_TCP_ADDR}:443 \
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
issuer="https://kubernetes.default.svc.cluster.local"
exit
```

# Summary

So we have a vault, an injector, TLS end to end, stateful storage.
The injector can now inject secrets for pods from the vault.

Now we are ready to use the platform for different types of secrets:

## Secret Injection Guides

### Basic Secrets

Objective:
---------- 
* Let's create a basic secret in vault manually
* Application consumes the secret automatically

[Try it](./example-apps/basic-secret/readme.md)

---

### Overview (`hashicorp/vault/readme.md`)

# Hashicorp Vault Guide - Deprecated

<a href="https://www.youtube.com/playlist?list=PLHq1uqvAteVtq-NRX3yd1ziA_wJSBu3Oj" title="vault"><img src="https://i.ytimg.com/vi/L_o_CG_AGKA/hqdefault.jpg" width="20%" alt="introduction to vault" /></a>

# Vault

For this tutorial, I use Kubernetes 1.17
It's critical because we'll need certain [admission controllers](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/) enabled.

To get 1.17 for Linux\Windows, just use `kind` since you can create a 1.17 with admissions all setup.

```
#Windows
kind create cluster --name vault --image kindest/node:v1.17.0@sha256:9512edae126da271b66b990b6fff768fbb7cd786c7d39e86bdf55906352fdf62

#Linux
kind create cluster --name vault --kubeconfig ~/.kube/kind-vault --image kindest/node:v1.17.0@sha256:9512edae126da271b66b990b6fff768fbb7cd786c7d39e86bdf55906352fdf62
```

## TLS End to End Encryption

VIDEO: ```<Coming-Soon>```  
See steps in [./tls/ssl_generate_self_signed.txt](./tls/ssl_generate_self_signed.txt)
You'll need to generate TLS certs (or bring your own)
Create base64 strings from the files, place it in the `server-tls-secret.yaml` and apply it.
Remember not to check-in your TLS to GIT :)

## Deployment

```
kubectl create ns vault-example
kubectl -n vault-example apply -f ./hashicorp/vault/server/
kubectl -n vault-example get pods
```

## Storage

```
kubectl -n vault-example get pvc
```
ensure vault-claim is bound, if not, `kubectl -n vault-example describe pvc vault-claim`
ensure correct storage class is used for your cluster.
if you need to change the storage class, delete the pvc, edit YAML and re-apply

## Initialising Vault

```
kubectl -n vault-example exec -it vault-example-0 vault operator init
# unseal 3 times
kubectl -n vault-example exec -it vault-example-0 vault operator unseal
kubectl -n vault-example get pods
```

## Deploy the Injector

VIDEO: ```<Coming-Soon>```  
Injector allows pods to automatically get secrets from the vault.

```
kubectl -n vault-example apply -f ./hashicorp/vault/injector/
kubectl -n vault-example get pods
```

## Injector Kubernetes Auth Policy

For the injector to be authorised to access vault, we need to enable K8s auth

```
kubectl -n vault-example exec -it vault-example-0 vault login
kubectl -n vault-example exec -it vault-example-0 vault auth enable kubernetes

kubectl -n vault-example exec -it vault-example-0 sh
vault write auth/kubernetes/config \
token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
kubernetes_host=https://${KUBERNETES_PORT_443_TCP_ADDR}:443 \
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
exit

kubectl -n vault-example get pods
```

# Summary

So we have a vault, an injector, TLS end to end, stateful storage.
The injector can now inject secrets for pods from the vault.

Now we are ready to use the platform for different types of secrets:

## Secret Injection Guides

I've broken this down into basic guides to avoid this document from becoming too large.

### Basic Secrets

Objective:
---------- 
* Let's create a basic secret in vault manually
* Application consumes the secret automatically

[Try it](./example-apps/basic-secret/readme.md)

### Dynamic Secrets: Postgres

Objective:
---------- 
* We have a Postgres Database
* Let's delegate Vault to manage life cycles of our database credentials
* Deploy an app, that automatically gets it's credentials from vault

[Try it](./example-apps/dynamic-postgresql/readme.md)

---

### Overview (`jenkins/amazon-eks/readme.md`)

# Jenkins on Amazon Kubernetes 

<a href="https://youtu.be/eqOCdNO2Nmk" title="jenkins eks"><img src="https://i.ytimg.com/vi/eqOCdNO2Nmk/hqdefault.jpg" width="20%" alt="jenkins eks" /></a> 

## Create a cluster

Follow my Introduction to Amazon EKS for beginners guide, to create a cluster <br/>
Video [here](https://youtu.be/QThadS3Soig)

## Setup our Cloud Storage 

```
# deploy EFS storage driver
kubectl apply -k "github.com/kubernetes-sigs/aws-efs-csi-driver/deploy/kubernetes/overlays/stable/?ref=master"

# get VPC ID
aws eks describe-cluster --name getting-started-eks --query "cluster.resourcesVpcConfig.vpcId" --output text
# Get CIDR range
aws ec2 describe-vpcs --vpc-ids vpc-id --query "Vpcs[].CidrBlock" --output text

# security for our instances to access file storage
aws ec2 create-security-group --description efs-test-sg --group-name efs-sg --vpc-id VPC_ID
aws ec2 authorize-security-group-ingress --group-id sg-xxx  --protocol tcp --port 2049 --cidr VPC_CIDR

# create storage
aws efs create-file-system --creation-token eks-efs

# create mount point 
aws efs create-mount-target --file-system-id FileSystemId --subnet-id SubnetID --security-group GroupID

# grab our volume handle to update our PV YAML
aws efs describe-file-systems --query "FileSystems[*].FileSystemId" --output text
```

More details about EKS storage [here](https://aws.amazon.com/premiumsupport/knowledge-center/eks-persistent-storage/)

### Setup a namespace
```
kubectl create ns jenkins
```

### Setup our storage for Jenkins

```
kubectl get storageclass

# create volume
kubectl apply -f ./jenkins/amazon-eks/jenkins.pv.yaml 
kubectl get pv

# create volume claim
kubectl apply -n jenkins -f ./jenkins/amazon-eks/jenkins.pvc.yaml
kubectl -n jenkins get pvc
```

### Deploy Jenkins

```
# rbac
kubectl apply -n jenkins -f ./jenkins/jenkins.rbac.yaml 

kubectl apply -n jenkins -f ./jenkins/jenkins.deployment.yaml

kubectl -n jenkins get pods

```

### Expose a service for agents

```

kubectl apply -n jenkins -f ./jenkins/jenkins.service.yaml 

```

## Jenkins Initial Setup

```
kubectl -n jenkins exec -it <podname> cat /var/jenkins_home/secrets/initialAdminPassword
kubectl port-forward -n jenkins <podname> 8080

# setup user and recommended basic plugins
# let it continue while we move on!

```

## SSH to our node to get Docker user info

```
eval $(ssh-agent)
ssh-add ~/.ssh/id_rsa
ssh -i ~/.ssh/id_rsa ec2-user@ec2-13-239-41-67.ap-southeast-2.compute.amazonaws.com
id -u docker
cat /etc/group
# Get user ID for docker
# Get group ID for docker
```
## Docker Jenkins Agent

Docker file is [here](../dockerfiles/dockerfile) <br/>

```
# you can build it

cd ./jenkins/dockerfiles/
docker build . -t aimvector/jenkins-slave

```

## Continue Jenkins setup


Install Kubernetes Plugin <br/>
Configure Plugin: Values I used are [here](../readme.md) <br/>

Install Kubernetes Plugin <br/>

## Try a pipeline
 
```
/* Detailed source-code truncated for AI context efficiency. */
```

---

### Overview (`kubernetes/affinity/README.md`)

# Kubernetes Concept: Affinity \ Anti-Affinity 

## Create a kubernetes cluster

In this guide we we''ll need a Kubernetes cluster for testing. Let's create one using [kind](https://kind.sigs.k8s.io/) </br>

```
cd kubernetes/affinity
kind create cluster --name demo --image kindest/node:v1.28.0 --config kind.yaml
```

Test the cluster:
```
kubectl get nodes
NAME                 STATUS   ROLES           AGE   VERSION
demo-control-plane   Ready    control-plane   59s   v1.28.0
demo-worker          Ready    <none>          36s   v1.28.0
demo-worker2         Ready    <none>          35s   v1.28.0
demo-worker3         Ready    <none>          35s   v1.28.0

```

## Node Affinity 

[Node Affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity) is similar to `nodeSelector` however you can define more complex expressions. "Like my pods must run on SSD nodes or preffer SSD nodes"

For example: 
* Node selector is a hard and fast rule meaning a pod will not be scheduled if the selection is not satisfied
* For example, when using `os` selector as `linux` , a pod can only be scheduled if there is a node available where `os` label is `linux` 

Node Affinity allows an expression.

```
kubectl apply -f node-affinity.yaml
```

We can see our pods are prefering SSD and are always going to `us-east`

```
kubectl get pods -owide 

#introduce more pods
kubectl scale deploy app-disk --replicas 10

#observe all pods on demo-worker
```

If there is some trouble with our `ssd` disk, `kubectl taint nodes demo-worker type=ssd:NoSchedule`, we can see pods going to the non-ssd disk nodes in `us-east` </br>

This is because our pods prefer SSD, however there is no SSD available, so would still go to non-SSD nodes as long as there are nodes available in `us-east` </br>

If something goes wrong in our last `us-east` node: `kubectl taint nodes demo-worker3 type=ssd:NoSchedule` and we roll out more pods `kubectl scale deploy app-disk --replicas 20`,
notice that our new pods are now in `Pending` status because no nodes satisfy our node affinity rules </br>


Fix our nodes.
```
kubectl taint nodes demo-worker type=ssd:NoSchedule-
kubectl taint nodes demo-worker3 type=ssd:NoSchedule-
```
Scale back down to 0
```
kubectl scale deploy app-disk --replicas 0
kubectl scale deploy app-disk --replicas 1

# pod should go back to demo-worker , node 1
kubectl get pods -owide
```

## Pod Affinity 

Now [Pod Affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity) is an expression to allow us to state that pods should gravitate towards other pods

```
kubectl apply -f pod-affinity.yaml

# observe where pods get deployed
kubectl get pods -owide

kubectl scale deploy app-disk --replicas 3
kubectl scale deploy web-disk --replicas 3
```

## Pod Anti-Affinity

Let's say we observe our `app-disk` application disk usage is quite intense, and we would like to prevent `app-disk` pods from running together. </br>
This is where anti-affinity comes in:

```
podAntiAffinity:
  requiredDuringSchedulingIgnoredDuringExecution:
  - labelSelector:
      matchExpressions:
      - key: app
        operator: In
        values:
        - app-disk
    topologyKey: "kubernetes.io/hostname"
```

After applying the above, we can roll it out and observe scheduling:

```
kubectl scale deploy app-disk --replicas 0
kubectl scale deploy web-disk --replicas 0
kubectl apply -f node-affinity.yaml
kubectl get pods -owide

kubectl scale deploy app-disk --replicas 2 #notice pending pods when scaling to 3
kubectl get pods -owide
kubectl scale deploy web-disk --replicas 2
kubectl get pods -owide

```

---

### Overview (`kubernetes/autoscaling/readme.md`)

# Kubernetes Autoscaling Guide

## Cluster Autoscaling

Cluster autoscaler allows us to scale cluster nodes when they become full <br/>
I would recommend to learn about scaling your cluster nodes before scaling pods. <br/>
Video [here](https://youtu.be/jM36M39MA3I)

<a href="https://youtu.be/jM36M39MA3I" title="Kubernetes"><img src="https://i.ytimg.com/vi/jM36M39MA3I/hqdefault.jpg" width="20%" alt="Kubernetes cluster auto scaling" /></a> 

## Horizontal Pod Autoscaling

HPA allows us to scale pods when their resource utilisation goes over a threshold <br/>

<a href="https://youtu.be/FfDI08sgrYY" title="Kubernetes"><img src="https://i.ytimg.com/vi/FfDI08sgrYY/hqdefault.jpg" width="20%" alt="Pod auto scaling" /></a> 

## Requirements

### A Cluster 

* For both autoscaling guides, we'll need a cluster. <br/>
* For `Cluster Autoscaler` You need a cloud based cluster that supports the cluster autoscaler <br/>
* For `HPA` We'll use [kind](http://kind.sigs.k8s.io/)

### Cluster Autoscaling - Creating an AKS Cluster

```
# azure example

NAME=aks-getting-started
RESOURCEGROUP=aks-getting-started
SERVICE_PRINCIPAL=
SERVICE_PRINCIPAL_SECRET=

az aks create -n $NAME \
--resource-group $RESOURCEGROUP \
--location australiaeast \
--kubernetes-version 1.16.10 \
--nodepool-name default \
--node-count 1 \
--node-vm-size Standard_F4s_v2  \
--node-osdisk-size 250 \
--service-principal $SERVICE_PRINCIPAL \
--client-secret $SERVICE_PRINCIPAL_SECRET \
--output none \
--enable-cluster-autoscaler \
--min-count 1 \
--max-count 5
```

### Horizontal Pod Autocaling - Creating a Kind Cluster

My Node has 6 CPU cores for this demo <br/>

```
kind create cluster --name hpa --image kindest/node:v1.18.4
```

### Metric Server

* For `Cluster Autoscaler` - On cloud-based clusters, Metric server may already be installed. <br/>
* For `HPA` - We're using kind

[Metric Server](https://github.com/kubernetes-sigs/metrics-server) provides container resource metrics for use in autoscaling pipelines <br/>

Because I run K8s `1.18` in `kind`, the Metric Server version i need is `0.3.7` <br/>
We will need to deploy Metric Server [0.3.7](https://github.com/kubernetes-sigs/metrics-server/releases/tag/v0.3.7) <br/>
I used `components.yaml`from the release page link above. <br/>

<b>Important Note</b> : For Demo clusters (like `kind`), you will need to disable TLS <br/>
You can disable TLS by adding the following to the metrics-server container args <br/>

<b>For production, make sure you remove the following :</b> <br/>

```
- --kubelet-insecure-tls
- --kubelet-preferred-address-types="InternalIP"

```

Deployment: <br/>


```
cd kubernetes\autoscaling
kubectl -n kube-system apply -f .\components\metric-server\metricserver-0.3.7.yaml

#test 
kubectl -n kube-system get pods

#note: wait for metrics to populate!
kubectl top nodes

```

## Example Application

For all autoscaling guides, we'll need a simple app, that generates some CPU load <br/>

* Build the app
* Push it to a registry
* Ensure resource requirements are set
* Deploy it to Kubernetes
* Ensure metrics are visible for the app

```
# build

cd kubernetes\autoscaling\components\application
docker build . -t aimvector/application-cpu:v1.0.0

# push
docker push aimvector/application-cpu:v1.0.0

# resource requirements
resources:
  requests:
    memory: "50Mi"
    cpu: "500m"
  limits:
    memory: "500Mi"
    cpu: "2000m"

# deploy 
kubectl apply -f deployment.yaml

# metrics
kubectl top pods

```

## Cluster Autoscaler

For cluster autoscaling, you should be able to scale the pods manually and watch the cluster scale. </br>
Cluster autoscaling stops here. </br>
For Pod Autoscaling (HPA), continue</br>

## Generate some traffic

Let's deploy a simple traffic generator pod

```
cd kubernetes\autoscaling\components\application
kubectl apply -f .\traffic-generator.yaml

# get a terminal to the traffic-generator
kubectl exec -it traffic-generator sh

# install wrk
apk add --no-cache wrk

# simulate some load
wrk -c 5 -t 5 -d 99999 -H "Connection: Close" http://application-cpu

#you can scale to pods manually and see roughly 6-7 pods will satisfy resource requests.
kubectl scale deploy/application-cpu --replicas 2
```

## Deploy an autoscaler

```
# scale the deployment back down to 2
kubectl scale deploy/application-cpu --replicas 2

# deploy the autoscaler
kubectl autoscale deploy/application-cpu --cpu-percent=95 --min=1 --max=10

# pods should scale to roughly 6-7 to match criteria of 95% of resource requests

kubectl get pods
kubectl top pods
kubectl get hpa/application-cpu  -owide

kubectl describe hpa/application-cpu 

```

## Vertical Pod Autoscaling

The vertical pod autoscaler allows us to automatically set request values on our pods <br/>
based on recommendations.
This helps us tune the request values based on actual CPU and Memory usage.<br/>

More [here](./vertical-pod-autoscaling/readme.md)

---

### Overview (`kubernetes/cert-manager/README.md`)

# Introduction to cert-manager for Kubernetes

<a href="https://youtu.be/hoLUigg4V18" title="certmanager"><img src="https://i.ytimg.com/vi/hoLUigg4V18/hqdefault.jpg" width="20%" alt="introduction to certmanager" /></a>

## We need a Kubernetes cluster

Lets create a Kubernetes cluster to play with using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/)

```shell
kind create cluster --name certmanager --image kindest/node:v1.34.0
```

## Concepts 

It's important to understand the various concepts and new Kubernetes resources that <br/>
`cert-manager` introduces.

* Issuers [docs](https://cert-manager.io/docs/concepts/issuer/)
* Certificate [docs](https://cert-manager.io/docs/concepts/certificate/)
* CertificateRequests [docs](https://cert-manager.io/docs/concepts/certificaterequest/)
* Orders and Challenges [docs](https://cert-manager.io/docs/concepts/acme-orders-challenges/)

## Installation 

You can find the latest release for `cert-manager` on their [GitHub Releases page](https://github.com/cert-manager/cert-manager) <br/>

For this demo, I will use K8s 1.34 and `cert-manager` [v1.19.2](https://github.com/cert-manager/cert-manager/releases/tag/v1.19.2)

```shell
#test cluster access:
kubectl get nodes
NAME                        STATUS   ROLES           AGE     VERSION
certmanager-control-plane   Ready    control-plane   4m25s   v1.34.0

# note: in the legacy video, we used kubectl to install cert-manager.
#       this is now upgraded to use helm instead using the new OCI repo

# install cert-manager 
CHART_VERSION="v1.19.2"

# checkout the values
helm show values oci://quay.io/jetstack/charts/cert-manager > kubernetes/cert-manager/default-values.yaml

helm install \
  cert-manager oci://quay.io/jetstack/charts/cert-manager \
  --version ${CHART_VERSION} \
  --namespace cert-manager \
  --create-namespace \
  --set crds.enabled=true \
  --set config.enableGatewayAPI=true
```

## Cert Manager Resources

We can see our components deployed

```shell
kubectl -n cert-manager get all
NAME                                           READY   STATUS    RESTARTS   AGE
pod/cert-manager-75bb65b7b9-qmjt5              1/1     Running   0          6m57s
pod/cert-manager-cainjector-5cd89979d6-bs6m8   1/1     Running   0          6m57s
pod/cert-manager-webhook-8fc5dcf5f-xn5pq       1/1     Running   0          6m57s

NAME                              TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)            AGE
service/cert-manager              ClusterIP   10.96.50.192    <none>        9402/TCP           6m57s
service/cert-manager-cainjector   ClusterIP   10.96.144.242   <none>        9402/TCP           6m57s
service/cert-manager-webhook      ClusterIP   10.96.223.22    <none>        443/TCP,9402/TCP   6m57s

NAME                                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/cert-manager              1/1     1            1           6m57s
deployment.apps/cert-manager-cainjector   1/1     1            1           6m57s
deployment.apps/cert-manager-webhook      1/1     1           1           6m57s

NAME                                                 DESIRED   CURRENT   READY   AGE
replicaset.apps/cert-manager-75bb65b7b9              1         1         1       6m57s
replicaset.apps/cert-manager-cainjector-5cd89979d6   1         1         1       6m57s
replicaset.apps/cert-manager-webhook-8fc5dcf5f       1         1         1       6m57s

```

## Test Certificate Issuing 

Let's create some test certificates

```shell
kubectl create ns cert-manager-test

# create a self signed certificate
kubectl apply -f kubernetes/cert-manager/selfsigned/issuer.yaml
kubectl apply -f kubernetes/cert-manager/selfsigned/certificate.yaml

# see progress
kubectl describe certificate -n cert-manager-test
kubectl get secrets -n cert-manager-test

# cleanup
kubectl delete ns cert-manager-test
```

## Configuration 

https://cert-manager.io/docs/configuration/

## Setup my DNS

I can get the public IP address of my computer by running a simple command:

```
curl ifconfig.co
```

I can log into my DNS provider and point my DNS A record to my IP.<br/>
Also setup my router to allow 80 and 443 to come to my PC <br/>

<i>Note: If you are running in the cloud, your Ingress controller or Gateway API and Cloud provider will give you a
public IP and you can point your DNS to that accordingly.
</i>


## Deploy pods that require SSL\TLS

```shell
kubectl apply -f kubernetes/deployments/
kubectl apply -f kubernetes/services/

kubectl get pods
```

## Option 1: Using an Ingress Controller

Let's deploy an Ingress controller: <br/>
<i> Note: In the legacy vide, we used Kubernetes Ingress NGINX which is now deprecated. This guide is updated to use Traefik instead. You should be able to use any Ingress controller. </i>

```shell
CHART_VERSION="37.3.0"
helm repo add traefik https://helm.traefik.io/traefik
helm repo update

helm show values traefik/traefik > kubernetes/cert-manager/traefik-default-values.yaml

helm install traefik traefik/traefik \
  --version $CHART_VERSION \
  --namespace traefik \
  --set service.type=NodePort \
  --set ports.web.exposedPort=30911 \
  --set ports.websecure.exposedPort=30787 \
  --create-namespace

# check install 
kubectl -n traefik get pods

# get our service IP
kubectl -n traefik get svc

# deploy an ingress route that needs TLS for our example app
kubectl apply -f kubernetes/cert-manager/ingress.yaml

# curl over HTTP 
curl http://test.marceldempers.dev
```

### Create an Ingress Let's Encrypt Issuer

We create a `ClusterIssuer` for Ingress that allows us to issue certs in any namespace

```shell
kubectl apply -f kubernetes/cert-manager/issuer-ingress.yaml

# check the issuer
kubectl describe clusterissuer letsencrypt-issuer

```

### Issue Certificate

```shell
kubectl apply -f kubernetes/cert-manager/certificate.yaml

# check the cert issue status
kubectl describe certificate example-app

# you can track and diagnose the ordering request process
kubectl get CertificateRequest
#note:  you can use kubectl describe on the resource too

# TLS created as a secret
kubectl get secrets
NAME                  TYPE                                  DATA   AGE
secret-tls       kubernetes.io/tls                     2      1m

# test TLS 
curl https://test.marceldempers.dev
Hello World!

# cleanup
kubectl delete certificate example-app
kubectl delete clusterissuer letsencrypt-issuer
kubectl delete secret secret-tls
```

## Option 2: Using a Gateway API 

To use this option, we will need a Gateway API enabled cluster. This means you need the Gateway API CRD's installed. </br>

See our [Introduction to Gateway API guide](../gateway-api/README.md)
At the bottom of the guide, there is a matrix of many other Gateway APIs to choose from. </br>

<i> Note: `cert-manager` needs to have `config.enableGatewayAPI=true` enabled.  </i>

### Create a Gateway Class 

```shell
 kubectl apply -f kubernetes/gateway-api/traefik/01-gatewayclass.yaml 
```

### Create a Gateway 

We can use our Gateway from our [Traefik Gateway API Guide](../gateway-api/traefik/README.md)

```shell 
kubectl apply -f kubernetes/gateway-api/traefik/02-gateway.yaml

```

### Create a Gateway API Let's Encrypt Issuer

We create a `ClusterIssuer` for Ingress that allows us to issue certs in any namespace

```shell
kubectl apply -f kubernetes/cert-manager/issuer-gatewayapi.yaml

# check the issuer
kubectl describe clusterissuer letsencrypt-issuer

```

### Issue Certificate

```shell
kubectl apply -f kubernetes/cert-manager/certificate.yaml

# check the cert issue status
kubectl describe certificate example-app

# you can track and diagnose the ordering request process
kubectl get CertificateRequest
#note:  you can use kubectl describe on the resource too

# TLS created as a secret
kubectl get secrets
NAME                  TYPE                                  DATA   AGE
secret-tls       kubernetes.io/tls                     2      1m

# test TLS 
kubectl apply -f kubernetes/cert-manager/httproute.yaml
curl https://test.marceldempers.dev
Hello World!

# cleanup
kubectl delete certificate example-app
kubectl delete clusterissuer letsencrypt-issuer
kubectl delete secret secret-tls
```

---

### Overview (`kubernetes/cloud/readme.md`)

# Kubernetes in the Cloud

## Introduction to Kubernetes on Cloud Providers

Microsoft Azure [here](./azure/getting-started.md) <br/>
Digital Ocean [here](./digitalocean/getting-started.md) <br/>
Linode Cloud [here](./linode/getting-started.md) <br/>
Amazon Web Services [here](./amazon/getting-started.md) <br/>
Google Cloud [here](./google/getting-started.md) <br/>

---

### Overview (`kubernetes/configmaps/README.md`)

# Introduction to Kubernetes: Configmaps

<a href="https://youtu.be/o-gXx7r7Rz4" title="k8s-cm"><img src="https://i.ytimg.com/vi/o-gXx7r7Rz4/hqdefault.jpg" width="20%" alt="k8s-cm" /></a> 

## Create a cluster with Kind

```
kind create cluster --name demo --image kindest/node:v1.33.1
```

Checkout our cluster 

```
kubectl get nodes
NAME                 STATUS   ROLES           AGE   VERSION
demo-control-plane   Ready    control-plane   19m   v1.33.1
```

Deploy a `ConfigMap` 

```
kubectl apply -f kubernetes/configmaps/configmap.yaml
```

Deploy a pod that consumes the `ConfigMap` 

```
kubectl apply -f kubernetes/configmaps/deployment.yaml
```

Checking the deployment pods
The `kubectl describe` command is essential for checking out kubernetes resources

```
kubectl describe deploy example-deploy
```

We can checkout the config contents inside the pod's container with the `kubectl exec` command to get shell

```
kubectl exec -it <pod-name> -- bash
```

When we make a change to the `ConfigMap`, we will notice that the configuration file changes and reflects inside the pod. </br>
This takes a number of seconds to a minute or so to propogate

---

### Overview (`kubernetes/crossplane/README.md`)

# Introduction to Crossplane

[Crossplane](https://www.crossplane.io/) </br>
[Crossplane Documentation](https://docs.crossplane.io/v1.19/) </br>

## We need a Kubernetes cluster

Lets create a Kubernetes cluster to play with using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/)

```
kind create cluster --name crossplane --image kindest/node:v1.33.0
```

## Installing Crossplane

In this guide we will reference the official document steps in the links above. </br>
I've recorded the commands we follow in the video too 


```
helm repo add crossplane-stable https://charts.crossplane.io/stable
helm repo update

helm search repo crossplane-stable --versions
```

We'll install version `1.19.1` at the time of this guide 

```
VERSION=1.19.1

helm install crossplane \
crossplane-stable/crossplane \
--namespace crossplane-system \
--version $VERSION \
--create-namespace
```

View our install: 

```
kubectl get pods -n crossplane-system
kubectl get deployments -n crossplane-system
```

Once the pods are all running, we can see the `api-versions` 

```
kubectl api-versions  | grep crossplane
```

We can also see the new k8s objects that are installed with 

```
kubectl api-resources | grep crossplane
```

## Providers 

[Providers](https://docs.crossplane.io/latest/concepts/providers/) allow us to setup external providers that helps provision infrastructure for external services. </br>

For example, our crossplane cluster may have providers for deploying Azure, AWS, GCP or any other external infrastructure </br>

Furthermore, there is marketplace that hosts many providers, configurations and extensions for Crossplane called [Upbound](https://marketplace.upbound.io/providers)


Install a Provider for a cloud provider Azure:

```
kubectl apply -f kubernetes/crossplane/provider-azure.yaml
```

Check our provider:

```
kubectl get provider
kubectl describe provider provider-family-azure
```

## Provider Configuration

Once we have a provider setup, we can configure it using a `ProviderConfig` in Kubernetes </br>
An impotrant configuration is to tell the Crossplane Provider how to authenticate with its external service. </br>

For example, when using an Azure Provider, you need an Azure Service Principal, and for AWS you may need a service account with AWS account id and key. </br>
Each provider will have their own supported authentication methods. </br>

### Create Provider credentials 

```
SUBSCRIPTION_ID=<subscription-id>
RESOURCE_GROUP=marcel-test

az account set -s $SUBSCRIPTION_ID
az group create -n $RESOURCE_GROUP -l australiaeast
az ad sp create-for-rbac --sdk-auth  \
-n marcel-test \
--role Contributor \
--scopes "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP" > azure-credentials.json
```

### Create Provider Kubernetes Secret 

```
kubectl create secret \
generic azure-secret \
-n crossplane-system \
--from-file=creds=./azure-credentials.json
```

### Deploy the Provider Configuration

```
kubectl apply -f kubernetes/crossplane/providerconfig-azure.yaml
```

### Create Provider Resources 

```
kubectl apply -f kubernetes/crossplane/resources/azure/resource-vnet.yaml

error: resource mapping not found for name: "marcel-test-vnet" namespace: "" from "kubernetes/crossplane/resources/azure/resource-vnet.yaml": no matches for kind "VirtualNetwork" in version "network.azure.upbound.io/v1beta1"
ensure CRDs are installed first

```
We see there is no CRD for Azure VNETs, that is because every type of resource in Azure is modularized into a separate provider, so we will need the networking provider first </br>

Install the Azure Network Provider:

```
kubectl apply -f kubernetes/crossplane/provider-azure-network.yaml
```

Retry the resource creation:

```
kubectl apply -f kubernetes/crossplane/resources/azure/resource-vnet.yaml
kubectl get virtualnetwork
```

### Deploy a Virtual Network Subnet 

```
kubectl apply -f kubernetes/crossplane/resources/azure/resource-subnet.yaml
kubectl get subnet
```

### Deploy a Virtual Network Card 

```
kubectl apply -f kubernetes/crossplane/resources/azure/resource-networkcard.yaml
kubectl get networkinterface
```

### Deploy a Virtual Machine 

Firstly need to add the compoute provider for Azure

```
kubectl apply -f kubernetes/crossplane/provider-azure-compute.yaml
```

Deploy a Virtual Machine:

```
kubectl apply -f kubernetes/crossplane/resources/azure/resource-virtualmachine.yaml
```

## Cleanup Resources

```
kubectl delete linuxvirtualmachine marcel-test
kubectl delete networkinterface marcel-test
kubectl delete subnet marcel-test
kubectl delete virtualnetwork marcel-test
```

---

### Overview (`kubernetes/daemonsets/README.md`)

# Kubernetes Daemonsets

<a href="https://youtu.be/RGSeeN-o-kQ" title="k8s-daemonset"><img src="https://i.ytimg.com/vi/RGSeeN-o-kQ/hqdefault.jpg" width="20%" alt="k8s-daemonset" /></a> 

## We need a Kubernetes cluster

Lets create a Kubernetes cluster to play with using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/) </br>
Because a Daemonset is all about running pods on every node, lets create a 3 node cluster:

```
cd kubernetes/daemonsets
kind create cluster --name daemonsets --image kindest/node:v1.20.2 --config kind.yaml
```

Test our cluster:

```
kubectl get nodes
NAME                       STATUS     ROLES                  AGE   VERSION
daemonsets-control-plane   Ready      control-plane,master   65s   v1.20.2
daemonsets-worker          Ready      <none>                 31s   v1.20.2
daemonsets-worker2         Ready      <none>                 31s   v1.20.2
daemonsets-worker3         NotReady   <none>                 31s   v1.20.2
```

# Introduction

Kubernetes provide [documentation](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/) for what a Daemonset is with examples.


## Basic Daemonset

Let's deploy a daemonset that runs a pod on each node and collects the name of the node

```
kubectl apply -f daemonset.yaml

kubectl get pods -o wide
NAME                      READY   STATUS    RESTARTS   AGE     IP           NODE                       NOMINATED NODE   READINESS GATES
example-daemonset-8lcr5   1/1     Running   0          3m21s   10.244.2.4   daemonsets-worker          <none>           <none>
example-daemonset-9jhgx   1/1     Running   0          81s     10.244.3.4   daemonsets-worker2         <none>           <none>
example-daemonset-lvvsd   1/1     Running   0          2m41s   10.244.1.4   daemonsets-worker3         <none>           <none>
example-daemonset-xxcv9   1/1     Running   0          119s    10.244.0.7   daemonsets-control-plane   <none>           <none>

```

We can see the logs of any pod

```
kubectl logs <example-daemonset-xxxx>
```

Cleanup:

```
kubectl delete ds example-daemonset
```

## Basic Daemonset: Exposing HTTP

Let's deploy a daemonset that runs a pod on each node and exposes an HTTP endpoint on each node. </br>
In this demo we'll use a simple NGINX on port 80

```
kubectl apply -f daemonset-communication.yaml
kubectl get pods
```

## Communicating with Daemonset Pods 

https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/#communicating-with-daemon-pods

Let's deploy a pod that can talk to our daemonset

```
kubectl apply -f pod.yaml

kubectl exec -it pod -- bash
```

### Service Type: ClusterIP or LoadBalancer

Let's deploy a service of type ClusterIP

```
kubectl apply -f ./services/clusterip-service.yaml

while true; do curl http://daemonset-svc-clusterip; sleep 1s; done
Hello from daemonsets-worker2
Hello from daemonsets-control-plane
Hello from daemonsets-worker2
Hello from daemonsets-worker3
Hello from daemonsets-worker
Hello from daemonsets-worker
```

### Node IP and Node Port

We can add the `nodePort` field to the pods port section to expose a port on the node. </br>

Let's expose the node port in the pod spec:

```
ports:
- containerPort: 80
  hostPort: 80
  name: "http"
```
This means we can contact the daemonset pod using the Node IP and port:

```
# get the node ips
kubectl get nodes -owide

NAME                       STATUS   ROLES                  AGE    VERSION   INTERNAL-IP   EXTERNAL-IP       KERNEL-VERSION                CONTAINER-RUNTIME
daemonsets-control-plane   Ready    control-plane,master   112m   v1.20.2   172.18.0.4    <none>
daemonsets-worker          Ready    <none>                 111m   v1.20.2   172.18.0.3    <none>
daemonsets-worker2         Ready    <none>                 111m   v1.20.2   172.18.0.2    <none>
daemonsets-worker3         Ready    <none>                 111m   v1.20.2   172.18.0.6    <none>

#example:

bash-5.1# curl http://172.18.0.4:80
Hello from daemonsets-control-plane
bash-5.1# curl http://172.18.0.2:80
Hello from daemonsets-worker2
```

### Service: Headless service

Let's deploy a headless service where `clusterIP: None`

```
kubectl apply -f ./services/headless-service.yaml
```

There are a few ways to discover our pods:
1) Discover the [DNS](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#services) records via the "Headless" services


```
apk add --no-cache bind-tools
dig daemonset-svc-headless.default.svc.cluster.local
```

Notice it resolves to multiple DNS records for each pod:

```

;; ANSWER SECTION:
daemonset-svc-headless.default.svc.cluster.local. 30 IN A 10.244.0.5
daemonset-svc-headless.default.svc.cluster.local. 30 IN A 10.244.2.2
daemonset-svc-headless.default.svc.cluster.local. 30 IN A 10.244.3.2
daemonset-svc-headless.default.svc.cluster.local. 30 IN A 10.244.1.2
```

2) Discover pods endpoints by retrieving the endpoints for the headless service

```
kubectl describe endpoints daemonset-svc-headless
```

Example:
```
Addresses:          10.244.0.5,10.244.1.2,10.244.2.2,10.244.3.2

```

Get A records for each pod by using the following format: </br>

`<pod-ip-address>.<my-namespace>.pod.<cluster-domain.example>.` </br>

```
#examples:

10-244-0-5.default.pod.cluster.local
10-244-1-2.default.pod.cluster.local
10-244-2-2.default.pod.cluster.local
10-244-3-2.default.pod.cluster.local
```

Communicate with the pods over DNS:

```
curl http://10-244-0-5.default.pod.cluster.local
Hello from daemonsets-control-plane
```

# Real world Examples:

## Monitoring Nodes: Node-Exporter Daemonset

<br/>
We clone the official kube-prometheus repo to get monitoring manifests for Kubernetes.

```
git clone https://github.com/prometheus-operator/kube-prometheus.git
```

Check the compatibility matrix [here](https://github.com/prometheus-operator/kube-prometheus/tree/v0.8.0#kubernetes-compatibility-matrix)

For this demo, we will use the compatible version tag 0.8

```
git checkout v0.8.0
```

Deploy Prometheus Operator and CRDs
```
cd .\manifests\
kubectl create -f .\setup\
```

Deploy remaining resources including node exporter daemonset

```
kubectl create -f .

# wait for pods to be up
kubectl get pods -n monitoring

#access prometheus in the browser
kubectl -n monitoring port-forward svc/prometheus-k8s 9090

```
See the Daemonset communications on the Prometheus [targets](http://localhost:9090/targets) page

Checkout my [monitoring guide for kubernetes](../../monitoring/prometheus/kubernetes/README.md) for more in depth info

## Monitoring: Logging via Fluentd

Take a look at my monitoring guide for [Fluentd](../../monitoring/logging/fluentd/kubernetes/README.md)

---

### Overview (`kubernetes/datree/README.md`)

# Introduction to Datree

<a href="https://youtu.be/aqiOyXPPadk" title="Kubernetes"><img src="https://i.ytimg.com/vi/aqiOyXPPadk/hqdefault.jpg" width="20%" alt="Kubernetes Guide" /></a> 

## Installation 

Best place to start is the [documentation](https://hub.datree.io/)

I like to start all my work inside a docker container. </br>
Let's run a small Alpine linux container

```
docker run -it -v ${PWD}:/work -v ${HOME}/.kube/:/root/.kube/ -w /work --net host alpine sh 
```

### Install some dependancies 

Let's install `curl` and `unzip` because the installation script uses those. <br/>
We will also install `sudo` since we are running in a container as root and install scripts have `sudo` commands in them.

```
apk add curl unzip bash sudo
```

### Automatic Installation

We can install the latest version of Datree with the command advertised:

```
curl https://get.datree.io | /bin/bash
```

### Manual Installation

Or we can grab a specific version of `datree` on the GitHub releases page. </br>
For example: [1.5.20](https://github.com/datreeio/datree/releases/tag/1.5.20) binary

```
curl -L https://github.com/datreeio/datree/releases/download/1.5.20/datree-cli_1.5.20_Linux_x86_64.zip -o /tmp/datree.zip

unzip /tmp/datree.zip -d /tmp && \
chmod +x /tmp/datree && \
mv /tmp/datree /usr/local/bin/datree

```

Now we can run the `datree` command:

```
datree
Datree is a static code analysis tool for kubernetes files. Full code can be found at https://github.com/datreeio/datree

Usage:
  datree [command]

Available Commands:
  completion       Generate completion script for bash,zsh,fish,powershell
  config           Configuration management
  help             Help about any command
  kustomize        Render resources defined in a kustomization.yaml file and run a policy check against them
  publish          Publish policies configuration for given <fileName>.
  test             Execute static analysis for given <pattern>
  version          Print the version number

Flags:
  -h, --help   help for datree

Use "datree [command] --help" for more information about a command.

```

## Testing Kubernetes Manifests

We have a number of Kubernetes manifests in this repo. </br>
Datree does a few things for us: </br>
* YAML validation ( Is this YAML well formatted ? )
* Schema validation. ( Is this a Kubernetes YAML file ? For the right version ? )
* Policy checks ( Checks YAML to ensure good practises are followed )

</br>

Let's test my example manifests under our datree folder `kubernetes\datree\example`

### YAML validation

If we break the YAML file format, we can detect that with the YAML validation feature

```
datree test ./kubernetes/datree/example/deployment.yaml
```

### Policy checks

When we fix our YAML file, notice if we run `datree test` again, we get some policy checks failing

```
datree test ./kubernetes/datree/example/deployment.yaml

```

Let's test some other types of Kubernetes objects

```
datree test ./kubernetes/services/service.yaml
datree test ./kubernetes/configmaps/configmap.yaml
datree test ./kubernetes/statefulsets/statefulset.yaml
datree test ./kubernetes/ingress/ingress.yaml
```

### Schema validation

Datree can also check if our YAML matches the target Kubernetes version schema.
For example, our Ingress YAML is a newer version of Kubernetes

```
datree test --schema-version 1.14.0 ./kubernetes/ingress/ingress-nginx-example.yaml
datree test --schema-version 1.19.0 ./kubernetes/ingress/ingress-nginx-example.yaml

```

We can also test a directory of YAML files and include `*` wildcard in your scans. </br>
Let's test my latest Kubernetes tutorial that contains a Wordpress + MySQL + Ingress setup:

```
datree test kubernetes/tutorials/basics/yaml/*.y*ml
```

# Policies

Now if we take a look at the CLI output of `datree` we notice a link in the Summary output. </br>
The URL is in the form of `https://app.datree.io/login?t=<token>` </br>

```
(Summary)

- Passing YAML validation: 4/4

- Passing Kubernetes (1.20.0) schema validation: 4/4

- Passing policy check: 2/4

+-----------------------------------+------------------------------------------------------+
| Enabled rules in policy "Default" | 21                                                   |
| Configs tested against policy     | 5                                                    |
| Total rules evaluated             | 84                                                   |
| Total rules skipped               | 0                                                    |
| Total rules failed                | 14                                                   |
| Total rules passed                | 70                                                   |
| See all rules in policy           | https://app.datree.io/login?t=xxxxxxxxxxxxxxxxxxxxxx |
+-----------------------------------+------------------------------------------------------+
```

We can use this URL to access the Datree UI to get a view of the policy management screens </br>
Checkout the link to access the UI which helps us manage our policies. </br>

## Policy examples

One of the key features about policies is that we can apply rule sets for specific environments. </br>
Perhaps you have a development environment where policies are a little loose and a staging server that has tighter restrictions to match production, or even a regulated environment that has very tight controls. </br>

We can use the Datree UI to create policies with different sets of rules. </br>
We can then tell `datree` about the policy we want it to test against:

```
datree test kubernetes/datree/example/deployment.yaml -p production
```

For a new policy, we notice that 0 rules are enabled, so now we have the flexibility to set up the rules we want to protect this environment. </br>

## Helm

What if I don't use `kubectl` and use `helm` instead ? </br>
Let's install `helm` in our container </br>

```
apk add tar git
curl -L https://get.helm.sh/helm-v3.5.4-linux-amd64.tar.gz -o /tmp/helm.tar.gz && \
tar -xzf /tmp/helm.tar.gz -C /tmp && \
chmod +x /tmp/linux-amd64/helm && \
mv /tmp/linux-amd64/helm /usr/local/bin/helm

```

Let's install the `helm` plugin for `datree` <br/>

```
helm plugin install https://github.com/datreeio/helm-datree 

```

Now we can test a `helm` chart we have in our repo from my `helm` tutorial </br>

```

cd kubernetes/helm

helm datree test example-app \
-- --values ./example-app/example-app-01.values.yaml
```

## Kustomize

What if I don't use `helm` and use `kustomize` instead ? <br/>
Datree has out the box built-in `kustomize` support <br/>
Let's test our `kustomize` template from a video I did on `kustomize`

```
datree kustomize test .\kubernetes\kustomize\application\
```

# CI/CD examples

We can even run datree in GitHub Actions and various [CI/CD integrations](https://hub.datree.io/cicd-examples). </br>


# Admission Controller

So far, `datree` helps us detect misconfigurations on our local machine as well as at our CI level. </br>
But what about the things that don't flow via our CI ? </br>

When folks deploy stuff directly to our clusters via `kubectl` or `helm`. </br>
Datree now allows us to not only detect but prevent  misconfigurations being applied using a new admission controller feature. </br>

The admission controller is available [here](https://github.com/datreeio/admission-webhook-datree)

## Create a Kubernetes cluster

Let's start by creating a local `kind` [cluster](https://kind.sigs.k8s.io/)

Note that we create a Kubernetes 1.23 cluster. </br>
So we want to use `datree` to validate and ensure our manifests comply with that version of Kubernetes. <br/>

```
kind create cluster --name datree --image kindest/node:v1.23.6
```

Let's also grab `kubectl`:

```
curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.23.6/bin/linux/amd64/kubectl
chmod +x ./kubectl
mv ./kubectl /usr/local/bin/kubectl
```

We'll need a `datree` token so our admission controller can read our policies

```
export DATREE_TOKEN=[your-token]

```

## Installation 

I will need some dependencies since I am running in a lightweight `alpine` container. </br>
OpenSSL is needed by the webhook install to generate certificates. </br>

```
apk add openssl
```

Let's grab the `datree` manifests
```
curl -L https://get.datree.io/admission-webhook -o datree.sh
chmod +x datree.sh
bash datree.sh
```

With the admission controller now deployed, `datree` will validate things coming into the cluster. <br/>
For example, if we bypass our CI/CD, `datree` will catch our deployment and run our policy checks

I have a separate example deployment in our datree folder that we can play with:

```
kubectl apply -f kubernetes/datree/example/deployment.yaml
```

Output:

```
/* Detailed source-code truncated for AI context efficiency. */
```

Now to get this deployment fixed up, let's go ahead and comply to some of the policies </br>
Under the `deployment.yaml` I have included a `livenessProbe` as well as a `readinessProbe` </br>
Let's add those in. </br>
And finally we need to also add CPU and Memory requests and limit values. </br>

The last one is simple. We should avoid using the default namespace. So I will create an `example` namespace where I will keep all example apps.

```
kubectl create ns examples
```

And finally we can deploy our resource, and specify a namespace:

```
kubectl apply -n examples -f kubernetes/datree/example/deployment.yaml
deployment.apps/example-deploy created

```

## Kubectl

But what about resources already in your cluster ? </br>
Datree covers this with their `kubectl` plugin.

We can grab the install script right off the [GitHub Release](https://github.com/datreeio/kubectl-datree/releases) page. </br>
For this demo I'll grab the `v0.11` version </br>

Installation: 

```
curl -L https://github.com/datreeio/kubectl-datree/releases/download/v0.1.1/manual_install.sh -o /tmp/kubectl-plugin.sh
chmod +x /tmp/kubectl-plugin.sh
bash /tmp/kubectl-plugin.sh

```

Now we have datree inside `kubectl` and can perform checks in our cluster. </br>
We can check our entire namespace now, which should be pretty clean:

```
kubectl datree test -- --namespace examples
Fetching resources, this may take some time depending on the amount of resources in your cluster...

(Summary)

- Passing YAML validation: 1/1

- Passing Kubernetes (1.24.2) schema validation: 1/1

- Passing policy check: 1/1

+-----------------------------------+------------------------------------------------------+
| Enabled rules in policy "Default" | 21                                                   |
| Configs tested against policy     | 1                                                    |
| Total rules evaluated             | 21                                                   |
| Total rules skipped               | 0                                                    |
| Total rules failed                | 0                                                    |
| Total rules passed                | 21                                                   |
| See all rules in policy           | https://app.datree.io/login?t=xxxxxxxxxxxxxxxxxxxxxx |
+-----------------------------------+------------------------------------------------------+

The following cluster resources in namespace 'examples' were checked:

deployment.apps/example-deploy

```

---

### Overview (`kubernetes/deployments/readme.md`)

# Introduction to Kubernetes: Deployments

<a href="https://youtu.be/DMpEZEakYVc" title="k8s-deployments"><img src="https://i.ytimg.com/vi/DMpEZEakYVc/hqdefault.jpg" width="20%" alt="k8s-deployments" /></a> 

Build an example app:

```
#   Important!
#   make sure you are at root of the repository
#   in your terminal

#   you can choose which app you want to build!


#   aimvector/golang:1.0.0
docker-compose build golang


#   aimvector/csharp:1.0.0
docker-compose build csharp

#   aimvector/nodejs:1.0.0
docker-compose build nodejs

#   aimvector/python:1.0.0
docker-compose build python

```

Take a look at example [deployment yaml](./deployment.yaml)

---

### Overview (`kubernetes/fluxcd/README.md`)

# Introduction to Flux CD v2

## Create a kubernetes cluster

In this guide we we''ll need a Kubernetes cluster for testing. Let's create one using [kind](https://kind.sigs.k8s.io/) </br>

```
kind create cluster --name fluxcd --image kindest/node:v1.26.3
```

## Run a container to work in

### run Alpine Linux: 
```
docker run -it --rm -v ${HOME}:/root/ -v ${PWD}:/work -w /work --net host alpine sh
```

### install some tools

```
# install curl 
apk add --no-cache curl

# install kubectl 
curl -sLO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl
chmod +x ./kubectl
mv ./kubectl /usr/local/bin/kubectl

```

### test cluster access:
```
/work # kubectl get nodes
NAME                    STATUS   ROLES    AGE   VERSION
fluxcd-control-plane   Ready    control-plane   54s   v1.26.3
```

## Get the Flux CLI

Let's download the `flux` command-line utility. </br>
We can get this utility from the GitHub [Releases page](https://github.com/fluxcd/flux2/releases) </br>

It's also worth noting that you want to ensure you get a compatible version of flux which supports your version of Kubernetes. Checkout the [prerequisites](https://fluxcd.io/flux/installation/#prerequisites) page. </br>

```
curl -o /tmp/flux.tar.gz -sLO https://github.com/fluxcd/flux2/releases/download/v2.1.1/flux_2.1.1_linux_amd64.tar.gz
tar -C /tmp/ -zxvf /tmp/flux.tar.gz
mv /tmp/flux /usr/local/bin/flux
chmod +x /usr/local/bin/flux
```

Now we can run `flux --help` to see its installed

## Check our cluster

```
flux check --pre
```

## Documentation

As with every guide, we start with the documentation </br>
The [Core Concepts](https://fluxcd.io/flux/concepts/) is a good place to start. </br>

We begin by following the steps under the [bootstrap](https://fluxcd.io/flux/installation/#bootstrap) section for GitHub </br>

We'll need to generate a [personal access token (PAT)](https://github.com/settings/tokens/new) that can create repositories by checking all permissions under `repo`.  </br>

Once we have a token, we can set it: 

```
export GITHUB_TOKEN=<your-token>
```

Then we can bootstrap it using the GitHub bootstrap method

```
flux bootstrap github \
  --token-auth \
  --owner=marcel-dempers \
  --repository=docker-development-youtube-series \
  --path=kubernetes/fluxcd/repositories/infra-repo/clusters/dev-cluster \
  --personal \
  --branch fluxcd-2022

flux check

# flux manages itself using GitOps objects:
kubectl -n flux-system get GitRepository
kubectl -n flux-system get Kustomization
```

Check the source code that `flux bootstrap` created 

```
git pull origin <branch-name>
```

# Understanding GitOps Repository structures

Generally, in GitOps you have a dedicated repo for infrastructure templates. </br>
Your infrastructure will "sync" from the this repo </br>

```
                                                    
 developer    +-----------+     +----------+           
              |           |     | CI       |           
  ----------> | REPO(code)|---> | PIPELINE |           
              +-----------+     +----------+           
                                         |  commit     
                                         v             
           +----------+ sync    +----------+           
           |  INFRA   |-------> |INFRA     |           
           |  (k8s)   |         |REPO(yaml)|           
           +----------+         +----------+           
                                                            
```

Flux repository structure [documentation](https://fluxcd.io/flux/guides/repository-structure/)

* Mono Repo (all k8s YAML in same "infra repo")
* Repo per team
* Repo per app 

Take note in this guide the folders under `kubernetes/fluxcd/repositories` represent different GIT repos

```
- repositories
  - infra-repo
  - example-app-1
  - example-app-2
```

## build our example apps

Let's say we have a microservice called `example-app-1` and it has its own GitHub repo somewhere. </br>
For demo, it's code is under `kubernetes/fluxcd/repositories/example-app-1/`

```
# go to our "git repo"
cd kubernetes/fluxcd/repositories/example-app-1
# check the files
ls

cd src
docker build . -t example-app-1:0.0.1

#load the image to our test cluster so we dont need to push to a registry
kind load docker-image example-app-1:0.0.1 --name fluxcd 
```

## setup our gitops pipeline

Now we will also have a "infra-repo" GitHub repo where infrastructure configuration files for GitOps live.

```
cd kubernetes/fluxcd

# tell flux where our Git repo is and where the YAML is
# this is once off
# flux will monitor the example-app-1 Git repo for when any infrastructure changes, it will sync
kubectl -n default apply -f repositories/infra-repo/apps/example-app-1/gitrepository.yaml
kubectl -n default apply -f repositories/infra-repo/apps/example-app-1/kustomization.yaml

# check our flux resources 
kubectl -n default describe gitrepository example-app-1
kubectl -n default describe kustomization example-app-1

# check deployed resources
kubectl get all

kubectl port-forward svc/example-app-1 80:80

```

Now we have setup CD, let's take a look at CI </br>

## changes to our example apps

Once we make changes to our `app.py` we can build a new image with a new tag </br>

```
docker build . -t example-app-1:0.0.2

#load the image to our test cluster so we dont need to push to a registry
kind load docker-image example-app-1:0.0.2 --name fluxcd 

# update our kubernetes deployment YAML image tag
# git commit with [skip ci] as the prefix of commit message & git push to branch!
```

If we wait a minute or so we can ` kubectl port-forward svc/example-app-1 80:80` again and see the changes

## automate deploy by updating manifest 

So all we did to update our app is to build a new image, push it to our registry and update the image tag in our kubernetes deployment YAML file and `flux` will sync it. </br>
This is generally the role of CI, where `flux` concern is mainly CD. </br>

Here is an example on [how to automate that](https://fluxcd.io/flux/use-cases/gh-actions-manifest-generation/) 

## automate deploy by image scanning

```                                                                                                                                               
                                              docker push                   
                                                                            
        developer    +-----------+     +----------+       +-------------+   
                     |           |     | CI       |       |IMAGE        |   
         ----------> | REPO(code)|---> | PIPELINE | ----->|REGISTRY     |   
                     +-----------+     +----------+       +-------------+   
                                                           ^                
                                                           |sync            
                                                           |                
                                   +----------+  commit   +----------+      
                                   |INFRA     | <-------- |  INFRA   |      
                                   |REPO(yaml)|           |  (k8s)   |      
                                   +----------+           +----------+      
                                                                         
```

An alternative method is to use your CI to build and push a newly tagged image to your registry (same as first option) and use [Flux image scanner](https://fluxcd.io/flux/guides/image-update/#configure-image-updates) to trigger the rollout instead of automating a commit to your config repo. </br>

We firstly need to enable image scanning as its not enabled by default. </br>
To do this we just need to re-bootstrap `flux` with an addition flag

```
flux bootstrap github \
  --token-auth \
  --owner=marcel-dempers \
  --repository=docker-development-youtube-series \
  --path=kubernetes/fluxcd/repositories/infra-repo/clusters/dev-cluster \
  --components-extra=image-reflector-controller,image-automation-controller \
  --personal \
  --branch fluxcd-2022
```
We need to create a image registry credential where we will push our image:

```
kubectl -n default create secret docker-registry dockerhub-credential --docker-username '' --docker-password '' --docker-email 'test@test.com'

```

# build and push example-app-2

```
cd kubernetes\fluxcd\repositories\example-app-2\
ls
cd src
ls
docker build . -t aimvector/example-app-2:0.0.1
docker push aimvector/example-app-2:0.0.1

```
We will need to tell Flux how to manage our image deployment </br>
Note that this time our Kubernetes YAML is in the `configs` repo. </br>
This is because our application repo triggers it's CI which will build and push a new image to our cluster </br>
Flux will then detect the new image tag and update our Kubernetes YAML in our configs repo. </br>
If Flux pushed the update to our application repo, it will cause a CI/CD loop.

## add image policy and repository

```

kubectl -n default apply -f kubernetes/fluxcd/repositories/infra-repo/apps/example-app-2/gitrepository.yaml
kubectl -n default apply -f kubernetes/fluxcd/repositories/infra-repo/apps/example-app-2/kustomization.yaml

# see our application 
kubectl get deploy
kubectl get pods

# tell flux about our image update policy
kubectl -n default apply -f kubernetes/fluxcd/repositories/infra-repo/apps/example-app-2/imagerepository.yaml
kubectl -n default apply -f kubernetes/fluxcd/repositories/infra-repo/apps/example-app-2/imagepolicy.yaml
kubectl -n default apply -f kubernetes/fluxcd/repositories/infra-repo/apps/example-app-2/imageupdateautomation.yaml

# we will also need to provide authentication for our git repo
flux create secret git example-app-2-github --url https://github.com/marcel-dempers/docker-development-youtube-series --username '' --password '' --namespace default
```

There are a number of ways to authenticate with [GitRepositories](https://fluxcd.io/flux/components/source/gitrepositories/#secret-reference)

```
kubectl describe imagepolicy example-app-2
kubectl describe  imagerepository example-app-2
kubectl describe imageupdateautomation example-app-2
```

## Build and push our example-app-2

```
#make application changes and rebuild + push

docker build . -t aimvector/example-app-2:0.0.2
docker push aimvector/example-app-2:0.0.2


#see changes new tags
kubectl describe imagerepository

#see image being updated
kubectl describe imagepolicy example-app-2

# see flux commiting back to the repo
kubectl describe imageupdateautomation example-app-2

```

---

### Overview (`kubernetes/fluxcd/flux-v1-readme.md`)

# Flux Getting Started Guide (old v1)

<a href="https://youtu.be/OFgziggbCOg" title="flux cd"><img src="https://i.ytimg.com/vi/OFgziggbCOg/hqdefault.jpg" width="20%" alt="introduction to flux cd" /></a> 

# 1 - Kubernetes

Get a Kubernetes Cluster. In this video, I use Docker for Windows.
If you are new to Kubernetes, checkout my videos [here](https://marceldempers.dev/videos/guides/kubernetes-getting-started)

# 2 - Flux CTL

I used Flux 1.18 which I got from the GitHub [Release Page](https://github.com/fluxcd/flux/releases/tag/1.18.0)
Rename it to `fluxctl.exe` & place it in a folder that is on your `$env:Path` environment variable.
Open a new terminal and try
```
fluxctl
```

# 4 - Installing Flux

Make sure you are pointing to the kubernetes cluster you want to use
```
kubectl config current-context
kubectl get nodes
```
```
kubectl create ns flux

$GHUSER = "marcel-dempers"
fluxctl install `
--git-user=${GHUSER} `
--git-email=${GHUSER}@users.noreply.github.com `
--git-url=git@github.com:${GHUSER}/docker-development-youtube-series `
--git-path=kubernetes/configmaps,kubernetes/secrets,kubernetes/deployments `
--git-branch=flux-test `
--namespace=flux | kubectl apply -f -

kubectl -n flux rollout status deployment/flux

$env:FLUX_FORWARD_NAMESPACE = "flux"
fluxctl list-workloads
fluxctl identity


https://github.com/marcel-dempers/docker-development-youtube-series/settings/keys/new

fluxctl sync

  annotations:
    fluxcd.io/tag.example-app: semver:~1.0
    fluxcd.io/automated: 'true'

fluxctl policy -w default:deployment/example-deploy --tag "example-app=1.0.*"
```

---

### Overview (`kubernetes/gateway-api/README.md`)

# Introduction to Gateway API

<a href="https://youtu.be/q76XVCTDZCY" title="intro"><img src="https://i.ytimg.com/vi/q76XVCTDZCY/hqdefault.jpg" width="40%" alt="intro" /></a>

In this video, we will dive into the features of Gateway API. </br>
Once you finish this video, click [here]() to deep dive each of the Gateway API controllers. </br>

[Documentation](https://gateway-api.sigs.k8s.io/)

## We need a Kubernetes cluster

Lets create a Kubernetes cluster to play with using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/)

```
kind create cluster --name gatewayapi --image kindest/node:v1.34.0
```

Test our cluster and makes sure `kubectl` is configured for it:

```
kubectl get nodes
NAME                       STATUS   ROLES           AGE   VERSION
gatewayapi-control-plane   Ready    control-plane   40s   v1.34.0
```

## Gateway API CRDs

The Kubernetes Gateway API is not installed on kubernetes by default. My guess is it may at some point. </br>
For now, we can grab it from the [Gateway API SIGS Guide](https://gateway-api.sigs.k8s.io/guides/#installing-gateway-api)

<i><b>Important Note: </b>At the time of recording this guide, we'd like to look at as many features as possible, hence the `experimental` install is used.</i> </br>

```shell
kubectl apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.1/standard-install.yaml
```

This will install (Stable Channel):

* Gateway Classes: `kubectl get gatewayclass`
* Gateways: `kubectl get gateway`
* HTTP Routes:  `kubectl get httproute`

These APIs are part of the Experimental Channel:
* TLS Routes: `kubectl get tlsroute`
* TCP Routes: `kubectl get tcproute`
* UDP Routes: `kubectl get udproute`

<b>Note: Gateway API is very new, and all of the above is subject to change quite rapidly </br></b>

## Setup some example applications

The following will deploy a `deployment`, `service` and require `configMap` and `secret` for the applications to work.

```shell
# deploy example apps
kubectl apply -f python/deployment.yaml 
kubectl apply -f golang/deployment.yaml
kubectl apply -f kubernetes/gateway-api/web-app.yaml

# check example apps
kubectl get pods
NAME                             READY   STATUS    RESTARTS
go-deploy-b9c69978d-529qb        1/1     Running   0
go-deploy-b9c69978d-jfb2b        1/1     Running   0
python-deploy-54cbfc948b-5ch7w   1/1     Running   0
python-deploy-54cbfc948b-qh22f   1/1     Running   0
web-app-67fbb5d844-68wq4         1/1     Running   0

kubectl get service
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)
go-svc       ClusterIP   10.96.173.22   <none>        5000/TCP
python-svc   ClusterIP   10.96.113.49   <none>        5000/TCP
web-app      ClusterIP   10.96.44.18    <none>        80/TCP
```

### Create test Domains 

We also need to imagine we have a domain called `example-app.com` , so let's set that up on our hosts file

```log
127.0.0.1  example-app.com
127.0.0.1  example-app-go.com
127.0.0.1  example-app-python.com
```


## Install a Gateway API controller

To use the Gateway API features in Kubernetes, you need a controller that implements the above CRDs. </br>
In this introduction guide I will use an existing Gateway API controller called Traefik. </br>

<i><b>Note:</b> Keep in mind that this introduction has no dependency on Traefik specifically, therefore any controller that supports Gateway API can be used.
At the bottom of this guide, I will provide guides on each of the popular Gateway API implementations.
</i>

A Basic Gateway API controller install:

```shell
CHART_VERSION="37.3.0" # traefik version v3.6.0
helm repo add traefik https://helm.traefik.io/traefik
helm repo update
helm search repo traefik --versions


helm install traefik traefik/traefik \
  --version $CHART_VERSION \
  --values kubernetes/gateway-api/values.yaml \
  --namespace traefik \
  --create-namespace
```

As we don't have a LoadBalancer service in `kind`, let's `port-forward` so we can pretend we have one 

```shell

# check the pods
kubectl -n traefik get pods 

# check the logs 
kubectl -n traefik logs -l app.kubernetes.io/instance=traefik-traefik

# port forward for access
kubectl -n traefik port-forward svc/traefik 80
```


## Install a Gateway Class

To start enabling traffic to our newly created apps, we will start with installing a Gateway Class. </br>

Note that we use a Traefik Class in our example. </br>

[Documentation](https://gateway-api.sigs.k8s.io/api-types/gatewayclass/)

`GatewayClass` is a cluster-scoped resource defined by the infrastructure provider. This resource represents a class of Gateways that can be instantiated. </br>

```shell
kubectl apply -f kubernetes/gateway-api/traefik/01-gatewayclass.yaml

# check
kubectl get gatewayclass

# describe
kubectl describe gatewayclass
```

## Install a Gateway 

Next we need to install a Gateway that implements our Gateway Class </br>
Note that we use a Traefik Gateway in our example. </br>

[Documentation](https://gateway-api.sigs.k8s.io/api-types/gateway)

This gateway lives in the same namespace as the routes and applications

```shell
kubectl apply -f kubernetes/gateway-api/traefik/02-gateway.yaml
```

## Traffic Management Features : HTTP Routes

[Documentation](https://gateway-api.sigs.k8s.io/api-types/httproute/)

The important fields on HTTP Route we will cover:

* `parentRefs`
* `sectionName` 
* `hostnames`
* `rules` 
* `matches`
* `filters`

Feature Table: 

| Feature | Example |
| ---|---|
| Route by Hostname | [example](#route-by-hostname)|
| Route by Path | [example](#route-by-path) | 
| Route using URL Rewrite | [example](#route-using-url-rewrite)|
| Header Modification | [example](#requestresponse-header-manipulation)|
| HTTPS & TLS | [example](#https-and-tls) |

For traffic management, we can take a look at some basic HTTP routes.</br>

### Route by Hostname

We can route by host. </br>
This will route all traffic that matches the `Host` header with the `hostnames` field: </br>


http://example-app-python.com/ 👉🏽 http://python-svc:5000 </br>
http://example-app-go.com/ 👉🏽 http://go-svc:5000 </br>

```shell
kubectl apply -f kubernetes/gateway-api/03-httproute-by-hostname.yaml
```

### Route by Path

We can also route by host and path with different matching strategies. </br>

Exact: </br>
http://example-app-python.com/ 👉🏽 http://python-svc:5000/ </br>
http://example-app-go.com/ 👉🏽 http://go-svc:5000/ </br>

PathPrefix: </br>
http://example-app-python.com/* 👉🏽 http://python-svc:5000/* </br>
http://example-app-go.com/* 👉🏽 http://go-svc:5000/* </br>


```shell
kubectl apply -f kubernetes/gateway-api/04-httproute-by-path-exact.yaml
```

### Route using URL Rewrite

We can rewrite the hostname or URL using URL rewrite. </br>
This way, we can combine our services under one domain and our controller can act as a true API gateway:

http://example-app.com/api/python 👉🏽 http://python-svc:5000/ </br>
http://example-app.com/api/go 👉🏽 http://go-svc:5000/ </br>
As well as: </br>
http://example-app.com/api/go/status 👉🏽 http://go-svc:5000/status </br>

```shell
kubectl apply -f kubernetes/gateway-api/05-httproute-by-path-rewrite.yaml
```

### Request\Response Header Manipulation

With Gateway API, you can modify request and response headers. </br>
This is possible with the `ResponseHeaderModifier` filter </br>

At the time of this recording, Gateway API does not natively support CORS. </br>
Even with it in the Experimental channel, many controllers do not support it yet. </br>

Let's do a basic CORS header modification for our Go HTTPRoute </br>

```shell
kubectl port-forward svc/web-app 8000:80
```

The Header modification:

```shell
kubectl apply -f kubernetes/gateway-api/06-httproute-header-modify.yaml
```

### HTTPS and TLS

In my video, I generate a test TLS cert using [mkcert](https://github.com/FiloSottile/mkcert)

```shell
curl -L https://github.com/FiloSottile/mkcert/releases/download/v1.4.4/mkcert-v1.4.4-linux-amd64 -o mkcert && chmod +x mkcert && mv mkcert /usr/local/bin/

#linux
export CAROOT=${PWD}/kubernetes/gateway-api/tls
#windows
$env:CAROOT = "${PWD}" + "\kubernetes\gateway-api\tls"

mkcert -key-file kubernetes/gateway-api/tls/key.pem -cert-file kubernetes/gateway-api/tls/cert.pem example-app.com

mkcert -install
```

Now that we have a TLS cert, we can create a Kubernetes secret to store it:

```
kubectl create secret tls secret-tls -n default --cert kubernetes/gateway-api/tls/cert.pem --key kubernetes/gateway-api/tls/key.pem
```

We need to 

* Adjust our Gateway, to enable the TLS Listener first!
* Then apply the TLS listener in our HTTP Route to enable TLS, using `sectionName`

```shell
kubectl apply -f kubernetes/gateway-api/07-httproute-tls.yaml
```

Let's `port-forward` to 443 since that is where TLS is exposed:

```shell
kubectl -n traefik port-forward svc/traefik 443
```

Result: </br>
https://example-app.com/api/go 👉🏽 http://go-svc:5000/ </br>

Checkout [More Official Guides](https://gateway-api.sigs.k8s.io/guides/) on the Kubernetes Gateway API SIGs page.</br>

## Infrastructure Labels

A useful feature is the ability to customize infrastructure under the hood for Gateways. </br>
For example, cloud load balancers etc. 

We can use [Infrastructure Labels](https://kubernetes.io/blog/2023/11/28/gateway-api-ga/#gateway-infrastructure-labels) to do so. This will set annotations or labels on any infrastructure that gets created. </br>

## Gateway API Controller Guides

| Name | Guide | Video |
|------|-------|-------|
| Traefik | [Introduction to Traefik Gateway API](./traefik/README.md)   |  <a href="https://youtu.be/MN-k29g97ik" title="traefik"><img src="https://i.ytimg.com/vi/MN-k29g97ik/hqdefault.jpg" width="30%" alt="traefik" /></a>      |
| Envoy | [Introduction to Envoy Gateway API](./envoy/README.md)   | <a href="https://youtu.be/me_5W_Q4ZWg" title="envoy"><img src="https://i.ytimg.com/vi/me_5W_Q4ZWg/hqdefault.jpg" width="30%" alt="envoy" /></a>      |
| Istio | [Introduction to Istio Gateway API](./istio/README.md)   | <a href="https://youtu.be/du9h71WYToo" title="istio"><img src="https://i.ytimg.com/vi/du9h71WYToo/hqdefault.jpg" width="30%" alt="istio" /></a>     |
| NGINX Fabric | [Introduction to NGINX Fabric Gateway API](./nginx-fabric/README.md)   | <a href="https://youtu.be/zbAb2e_Q_Y0" title="nginx"><img src="https://i.ytimg.com/vi/zbAb2e_Q_Y0/hqdefault.jpg" width="30%" alt="nginx" /></a>       |
| Cilium | [Introduction to Cilium Gateway API](./cilium/README.md)   | <a href="https://youtu.be/q4bucRkDOm8" title="cilium"><img src="https://i.ytimg.com/vi/q4bucRkDOm8/hqdefault.jpg" width="30%" alt="cilium" /></a>      |
| kgateway | [Introduction to kgateway (a.k.a Gloo Gateway)](./kgateway/README.md) | <a href="https://youtu.be/4VTD5Z9YUu0" title="kgateway"><img src="https://i.ytimg.com/vi/4VTD5Z9YUu0/hqdefault.jpg" width="30%" alt="kgateway" /></a> |
| HAProxy | [Introduction to HAProxy & Gateway API](./haproxy/README.md) | <a href="https://youtu.be/5clOvqDh-zA" title="haproxy"><img src="https://i.ytimg.com/vi/5clOvqDh-zA/hqdefault.jpg" width="30%" alt="haproxy" /></a> |
| Linkerd | [Introduction to Linkerd Gateway API](./linkerd/README.md)   | <a href="https://youtu.be/B9gKwJdWW7A" title="linkerd"><img src="https://i.ytimg.com/vi/B9gKwJdWW7A/hqdefault.jpg" width="30%" alt="linkerd" /></a>

---

### Overview (`kubernetes/helm/README.md`)

# Introduction to Helm

<a href="https://youtu.be/5_J7RWLLVeQ" title="k8s-helm"><img src="https://i.ytimg.com/vi/5_J7RWLLVeQ/hqdefault.jpg" width="20%" alt="k8s-helm" /></a> 

## We need a Kubernetes cluster

Lets create a Kubernetes cluster to play with using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/)

```
kind create cluster --name helm --image kindest/node:v1.31.1
```

# Getting Started with Helm

Firstly, I like to do most of my work in containers so everything is reproducible  <br/>
and my machine remains clean.

## Get a container to work in
<br/>
Run a small `alpine linux` container where we can install and play with `helm`: <br/>

```
docker run -it --rm -v ${HOME}:/root/ -v ${PWD}:/work -w /work --net host alpine sh

# install curl & kubectl
apk add --no-cache curl nano
curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl
chmod +x ./kubectl
mv ./kubectl /usr/local/bin/kubectl
export KUBE_EDITOR="nano"

# test cluster access:
/work # kubectl get nodes
NAME                    STATUS   ROLES    AGE   VERSION
helm-control-plane   Ready    control-plane   26m   v1.31.1

```

## Install Helm CLI

```
curl -LO https://get.helm.sh/helm-v3.17.2-linux-amd64.tar.gz
tar -C /tmp/ -zxvf helm-v3.17.2-linux-amd64.tar.gz
rm helm-v3.17.2-linux-amd64.tar.gz
mv /tmp/linux-amd64/helm /usr/local/bin/helm
chmod +x /usr/local/bin/helm

```

## Create our first Chart

For reference in the rest of the guide, I have left my full templates in: <br/>
`<GitRepo>/kubernetes/helm/example-app`
```
cd kubernetes/helm

mkdir temp && cd temp
helm create example-app
```

## Cleanup the template 

We can delete unwanted files:

* delete everything under /templates, keeping only `_helpers.tpl`
* delete `tests` folder under `templates`

## Add Kubernetes files to our new Chart

Copy the following files into our `example-app/templates/` folder

* `<GitRepo>/kubernetes/deployments/deployment.yaml`
* `<GitRepo>/kubernetes/services/service.yaml`
* `<GitRepo>/kubernetes/configmaps/configmap.yaml`
* `<GitRepo>/kubernetes/secrets/secret.yaml`

## Test the rendering of our template

```
helm template example-app example-app
```

## Install our app using our Chart

```
helm install example-app example-app

# list our releases

helm list

# see our deployed components

kubectl get all
kubectl get cm
kubectl get secret
```
 
## Value injections for our Chart

For CI systems, we may want to inject an image tag as a build number <br/>

Basic parameter injection: <br/>

```
# values.yaml

deployment:
  image: "aimvector/python"
  tag: "1.0.4"

# deployment.yaml

image: {{ .Values.deployment.image }}:{{ .Values.deployment.tag }}

# upgrade our release

helm upgrade example-app example-app --values ./example-app/values.yaml

# see revision increased

helm list
```

## Make our Chart more generic

Let's make our chart generic so it can be reused: <br/>
For the following objects, replace `example-deploy` and `example-app` to inject: `"{{ .Values.name }}"`

* deployment.yaml
* services.yaml
* secret.yaml
* configmap.yaml

Now that our application is generic <br/>
We can deploy another copy of it.<br/>
<br/>

Rename `values.yaml` to `example-app.values.yaml`
Create our second app values file `example-app-02.values.yaml`

```
helm install example-app-02 example-app --values ./example-app/example-app-02.values.yaml
```

## Trigger deployment change when config changes

By default, a deployment will not rollout new pods when a configmap changes. <br/>
Some application read configuration at start up and deployment may need to roll out </br>
new pods when a configmap changes. Let's do that:

```
# deployment.yaml

kind: Deployment
spec:
  template:
    metadata:
      annotations:
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}

# rollout the change

helm upgrade example-app example-app --values ./example-app/example-app-01.values.yaml
```

## If\Else and Default values

You can also set default values in case they are not supplied by the `values.yaml` file. <br/>
This may help you keep the `values.yaml` file small <br/>

```
{{- if .Values.deployment.resources }}
  resources:
    {{- if .Values.deployment.resources.requests }}
    requests:
      memory: {{ .Values.deployment.resources.requests.memory | default "50Mi" | quote }}
      cpu: {{ .Values.deployment.resources.requests.cpu | default "10m" | quote }}
    {{- else}}
    requests:
      memory: "50Mi"
      cpu: "10m"
    {{- end}}
    {{- if .Values.deployment.resources.limits }}
    limits:
      memory: {{ .Values.deployment.resources.limits.memory | default "1024Mi" | quote }}
      cpu: {{ .Values.deployment.resources.limits.cpu | default "1" | quote }}
    {{- else}}  
    limits:
      memory: "1024Mi"
      cpu: "1"
    {{- end }}
  {{- else }}
  resources:
    requests:
      memory: "50Mi"
      cpu: "10m"
    limits:
      memory: "1024Mi"
      cpu: "1"
  {{- end}} 


# rollout the change

helm upgrade example-app example-app --values ./example-app/example-app-01.values.yaml
```

---

### Overview (`kubernetes/kubectl/README.md`)

# Introduction to KUBECTL

<a href="https://youtu.be/1zcXudjSVUs" title="k8s-kubectl"><img src="https://i.ytimg.com/vi/1zcXudjSVUs/hqdefault.jpg" width="20%" alt="k8s-kubectl" /></a> 

To start off this tutorial, we will be using [kind](https://kind.sigs.k8s.io/) to create our test cluster. </br>
You can use `minikube` or any Kubernetes cluster. </br>

Kind is an amazing tool for running test clusters locally as it runs in a container which makes it lightweight and easy to run throw-away clusters for testing purposes. </br>

## Download KUBECTL

We can download `kubectl` from the [Official Docs](https://kubernetes.io/docs/tasks/tools/) </br>

## Create a kubernetes cluster

In this guide we will run two clusters side by side so we can demonstrate cluster access. </br>
Create two clusters:

```
kind create cluster --name dev --image kindest/node:v1.23.5
kind create cluster --name prod --image kindest/node:v1.23.5

```

See cluster up and running:

```
kubectl get nodes
NAME                  STATUS   ROLES                  AGE     VERSION
prod-control-plane   Ready    control-plane,master   2m12s   v1.23.5
```

## Understanding the KUBECONFIG

Default location of the `kubeconfig` file is in `<users-directory>/.kube/config`

```
kind: Config
apiVersion: v1
clusters:
 - list of clusters (addresses \ endpoints) 
users:
 - list of users (thing that identifies us when accessing a cluster [certificate]) 
contexts:
 - list of contexts ( which user and cluster to use when running commands)
```

Commands to interact with `kubeconfig` are `kubectl config`. </br>
Key commands are telling `kubectl` which context to use 

```
kubectl config current-context
kubectl config get-contexts
kubectl config use-context <name>
```

You can also tell your `kubectl` to use different config files. </br>
This is useful to keep your production config separate from your development ones </br>

Set the `$KUBECONFIG` environment variable to a path:
```
#linux
export KUBECONFIG=<path>

#windows 
$ENV:KUBECONFIG="C:\Users\aimve\.kube\config"
```

We can export seperate configs using `kind` </br>
This is possible with cloud based clusters as well:

```
kind --name dev export kubeconfig --kubeconfig C:\Users\aimve\.kube\dev-config 

kind --name prod export kubeconfig --kubeconfig C:\Users\aimve\.kube\prod-config 

#switch to prod
$ENV:KUBECONFIG="C:\Users\aimve\.kube\prod-config"
kubectl get nodes
```

## Working with Kubernetes resources

Now that we have cluster access, next we can read resources from the cluster
with the `kubectl get` command.

## Namespaces 

Most kubernetes resources are namespace scoped:

```
kubectl get namespaces
```

By default, `kubectl` commands will run against the `default` namespace

## List resources in a namespace

```
kubectl get <resource>

kubectl get pods
kubectl get deployments
kubectl get services
kubectl get configmaps
kubectl get secrets
kubectl get ingress
```

## Create resources in a namespace

We can create a namespace with the `kubectl create` command:

```
kubectl create ns example-apps
```

Let's create a couple of resources:

```

kubectl -n example-apps create deployment webserver --image=nginx --port=80
kubectl -n example-apps get deploy
kubectl -n example-apps get pods

kubectl -n example-apps create service clusterip webserver --tcp 80:80
kubectl -n example-apps get service
kubectl -n example-apps port-forward svc/webserver 80
# we can access http://localhost/

kubectl -n example-apps create configmap webserver-config --from-file config.json=./kubernetes/kubectl/config.json
kubectl -n example-apps get cm

kubectl -n example-apps create secret generic webserver-secret --from-file secret.json=./kubernetes/kubectl/secret.json
kubectl -n example-apps get secret

```

## Working with YAML

As you can see we can create resources with `kubectl` but this is only for basic testing purposes.
Kubernetes is a declarative platform, meaning we should provide it what to create instead
of running imperative line-by-line commands. </br>

We can also get the YAML of pre-existing objects in our cluster with the `-o yaml` flag on the `get` command </br>

Let's output all our YAML to a `yaml` folder:

```
kubectl -n example-apps get cm webserver-config -o yaml > .\kubernetes\kubectl\yaml\config.yaml
kubectl -n example-apps get secret webserver-secret -o yaml > .\kubernetes\kubectl\yaml\secret.yaml
kubectl -n example-apps get deploy webserver -o yaml > .\kubernetes\kubectl\yaml\deployment.yaml
kubectl -n example-apps get svc webserver -o yaml > .\kubernetes\kubectl\yaml\service.yaml   
```

## Create resources from YAML files

The most common and recommended way to create resources in Kubernetes is with the `kubectl apply` command. </br>
This command takes in declarative `YAML` files.

To show you how powerful it is, instead of creating things line-by-line, we can deploy all our infrastructure
with a single command. </br>

Let's deploy a Wordpress CMS site, with a back end MySQL database. </br>
This is a snippet taken from my `How to learn Kubernetes` video:

```
kubectl create ns wordpress-site
kubectl -n wordpress-site apply -f ./kubernetes/tutorials/basics/yaml/
```

We can checkout our site with the `port-forward` command:

```
kubectl -n wordpress-site get svc

NAME        TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
mysql       ClusterIP   10.96.146.75   <none>        3306/TCP   17s
wordpress   ClusterIP   10.96.157.6    <none>        80/TCP     17s

kubectl -n wordpress-site port-forward svc/wordpress 80
```

## Clean up

```
kind delete cluster --name dev
kind delete cluster --name prod

```

---

### Overview (`kubernetes/kustomize/readme.md`)

# The Basics

<a href="https://youtu.be/5gsHYdiD6v8" title="k8s-kustomize"><img src="https://i.ytimg.com/vi/5gsHYdiD6v8/hqdefault.jpg" width="20%" alt="k8s-kustomize" /></a> 


```

kubectl apply -f kubernetes/kustomize/application/namespace.yaml
kubectl apply -f kubernetes/kustomize/application/configmap.yaml
kubectl apply -f kubernetes/kustomize/application/deployment.yaml
kubectl apply -f kubernetes/kustomize/application/service.yaml

# OR 

kubectl apply -f kubernetes/kustomize/application/

kubectl delete ns example

```

# Kustomize

## Build
```
kubectl kustomize .\kubernetes\kustomize\ | kubectl apply -f -
# OR
kubectl apply -k .\kubernetes\kustomize\

kubectl delete ns example
```

## Overlays

```
kubectl kustomize .\kubernetes\kustomize\environments\production | kubectl apply -f -
# OR
kubectl apply -k .\kubernetes\kustomize\environments\production

kubectl delete ns example
```

---

### Overview (`kubernetes/persistentvolume/readme.md`)

# Persistent Volumes Demo

<a href="https://youtu.be/ZxC6FwEc9WQ" title="k8s-pv"><img src="https://i.ytimg.com/vi/ZxC6FwEc9WQ/hqdefault.jpg" width="20%" alt="k8s-pv" /></a> 

## Container Storage

By default containers store their data on the file system like any other process.
Container file system is temporary and not persistent during container restarts
When container is recreated, so is the file system



```
# run postgres
docker run -d --rm -e POSTGRES_DB=postgresdb -e POSTGRES_USER=admin -e POSTGRES_PASSWORD=admin123 postgres:10.4

# enter the container 
docker exec -it <container-id> bash

# login to postgres
psql --username=admin postgresdb

#create a table
CREATE TABLE COMPANY(
   ID INT PRIMARY KEY     NOT NULL,
   NAME           TEXT    NOT NULL,
   AGE            INT     NOT NULL,
   ADDRESS        CHAR(50),
   SALARY         REAL
);

#show table
\dt

# quit 
\q
```

Restarting the above container and going back in you will notice `\dt` commands returning no tables.
Since data is lost.

Same can be demonstrated using Kubernetes

```
cd .\kubernetes\persistentvolume\

kubectl create ns postgres
kubectl apply -n postgres -f ./postgres-no-pv.yaml
kubectl -n postgres get pods 
kubectl -n postgres exec -it postgres-0 bash

# run the same above mentioned commands to create and list the database table

kubectl delete po -n postgres postgres-0

# exec back in and confirm table does not exist.
```

# Persist data Docker

```
docker volume create postges
docker run -d --rm -v postges:/var/lib/postgresql/data -e POSTGRES_DB=postgresdb -e POSTGRES_USER=admin -e POSTGRES_PASSWORD=admin123 postgres:10.4

# run the same tests as above and notice
```

# Persist data Kubernetes


```
kubectl apply -f persistentvolume.yaml
kubectl apply -n postgres -f persistentvolumeclaim.yaml

kubectl apply -n postgres -f postgres-with-pv.yaml

kubectl -n postgres get pods

```

---

### Overview (`kubernetes/portainer/README.md`)

# Introduction to Portainer

<a href="https://youtu.be/FC8pABzxZVU" title="k8s-portainer"><img src="https://i.ytimg.com/vi/FC8pABzxZVU/hqdefault.jpg" width="20%" alt="k8s-portainer" /></a> 

Start here 👉🏽[https://www.portainer.io/](https://www.portainer.io/) </br>
Documentation 👉🏽[https://docs.portainer.io/](https://docs.portainer.io/)

## Portainer installation

In this demo, I will be running Kubernetes 1.22 using `kind` </br>
Which is compatible with portainer 2.11.1 </br>

Let's go ahead with a local docker install: 

```
cd kubernetes\portainer
mkdir volume-ce 

docker run -d -p 9443:9443 -p 8000:8000 --name portainer-ce `
--restart=always `
-v /var/run/docker.sock:/var/run/docker.sock `
-v ${PWD}/volume-ce:/data `
portainer/portainer-ce:2.11.1
```

## SSL & DOMAIN

We can also upload SSL certificates for our portainer.</br> 
In this demo, portainer will issue self signed certificates. </br> 
We will need a domain for our portainer server so our clusters can contact it. </br>
Let's use [nip.io](https://nip.io/) to create a public endpoint for portainer.

## Create Kubernetes Cluster

Let's start by creating a local `kind` [cluster](https://kind.sigs.k8s.io/)

For local clusters, we can use the public endpoint Agent. </br>
We can get a public endpoint for the portainer agent by: </br>

* Ingress
* LoadBalancer
* NodePort

So we'll deploy portainer agent with `NodePort` for local </br>

For production environments, I would recommend not to expose the portainer agent. </br>
In this case, for Production, we'll use the portainer edge agent.  </br>


To get `NodePort` exposed in `kind`, we'll open a host port with a [kind.yaml](./kind.yaml) config

```
kind create cluster --name local --config kind.yaml
```

## Manage Kubernetes Environments 

The portainer UI gives us a one line command to deploy the portainer agent. </br>
Note that in the video, we pick the `node port` option.

## Local: Portainer Agent

I download the YAML from [here](https://downloads.portainer.io/portainer-agent-ce211-k8s-nodeport.yaml) to take a closer look at what it is deploying </br>

Deploy the portainer agent in my `kind` cluster:

```
kubectl apply -f portainer-agent-ce211-k8s-nodeport.yaml
```

See the agent: 

```
kubectl -n portainer get pods
```

See the service with the endpoint it exposes: 

```
kubectl -n portainer get svc
```

Now since we dont have a public load balancer and using nodeport, our service will be exposed on the node IP. </br>
Since the Kubernetes node is our local machine, we should be able to access the portainer agent on `<computer-IP>:30778` </br>

We can obtain our local IP with `ipconfig` </br>
The IP and NodePort will be used to connect our portainer server to the new agent. </br>

## Production: Portainer Edge Agent

For the Edge agent, we get the command in the portainer UI. </br>
Once deployed, we can see the egde agent in our AKS cluster:

```
kubectl -n portainer get pods
```

## Helm 

Let's showcase how to deploy helm charts. </br>
Most folks would have helm charts for their ingress controllers, monitoring, logging and other 
platform dependencies.</br>

Let's add Kubernetes NGINX Ingress repo:

```
https://kubernetes.github.io/ingress-nginx
```

## GitOps

So from the Application menu, we can add an application from a `git` repository. </br>
Let's add this repo:

```
https://github.com/marcel-dempers/docker-development-youtube-series
```

We also specify all our manifests path that portainer needs to deploy:

* kubernetes/portainer/example-application/deployment.yaml
* kubernetes/portainer/example-application/configmap.yaml
* kubernetes/portainer/example-application/service.yaml
* kubernetes/portainer/example-application/ingress.yaml

Portainer will now poll our repo and deploy any updates, GitOps style!

## Oauth Setup example | Business edition

Here are the values I used for the Oauth settings:

| Field | Value |
|-------|-------|
|  Client ID     |  xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx     |
|  Client secret     |   xxxxxxxxxxxxxxxxxxxx    |
|  Authorization URL  |  https://login.microsoftonline.com/`<tenant-id>`/oauth2/authorize  |    
|  Access token URL  | https://login.microsoftonline.com/`<tenant-id>`/oauth2/token   |     
|  Resource URL   | https://login.microsoftonline.com/`<tenant-id>`/openid/userinfo   |     
|  Redirect URL   | https://localhost:9443/   |     
|  Logout URL   |  https://login.microsoftonline.com/`<tenant-id>`/oauth2/logout  |     
|  User identifier   |  unique_name  |     
|  Scopes   | openid profile   |

---

### Overview (`kubernetes/probes/README.md`)

# Introduction to Kubernetes Probes


## Create a kubernetes cluster

In this guide we we''ll need a Kubernetes cluster for testing. Let's create one using [kind](https://kind.sigs.k8s.io/) </br>

```
cd kubernetes/probes
kind create cluster --name demo --image kindest/node:v1.28.0 
```

Test the cluster:
```
kubectl get nodes
NAME                 STATUS   ROLES           AGE   VERSION
demo-control-plane   Ready    control-plane   59s   v1.28.0

```

## Applications

Client app is used to act as a client that sends web requests :

```
kubectl apply -f client.yaml
```

The server app is the app that will receive web requests:

```
kubectl apply -f server.yaml
```

Test making web requests constantly:

```
while true; do curl http://server; sleep 1s; done
```

Bump the server `version` label up and apply to force a new deployment </br>
Notice the client throws an error, so traffic is interupted, not good! </br>

This is because our new pod during deployment is not ready to take traffic!

## Readiness Probes

Let's add a readiness probe that tells Kubernetes when we are ready:

```
readinessProbe:
  httpGet:
    path: /
    port: 5000
  initialDelaySeconds: 3
  periodSeconds: 3
  failureThreshold: 3
```

### Automatic failover with Readiness probes

Let's pretend our application starts hanging and not longer returns responses </br>
This is common with some web servers and may need to be manually restarted 

```
kubectl exec -it podname -- sh -c "rm /data.txt"   
```

Now we will notice our client app starts getting errors. </br>
Few things to notice:

* Our readiness probe detected an issue and removed traffic from the faulty pod.
* We should be running more than one application so we would be highly available

```
kubectl scale deploy server --replicas 2
```

* Notice traffic comes back as its routed to the healthy pod

Fix our old pod:  `kubectl exec -it podname -- sh -c "echo 'ok' > /data.txt"` </br>

* If we do this again with 2 pods, notice we still get an interuption but our app automaticall stabalises after some time
* This is because readinessProbe has `failureThreshold` and some failure will be expected before recovery
* Do not set this `failureThreshold` too low as you may remove traffic frequently. Tune accordingly!

Readiness probes help us automatically remove traffic when there are intermittent network issues </br>

## Liveness Probes

Liveness probe helps us when we cannot automatically recover. </br>
Let's use the same mechanism to create a vaulty pod:

```
kubectl exec -it podname -- sh -c "rm /data.txt" 
```

Our readiness probe has saved us from traffic issues. </br>
But we want the pod to recover automatically, so let's create livenessProbe:

```
livenessProbe:
  httpGet:
    path: /
    port: 5000
  initialDelaySeconds: 3
  periodSeconds: 4
  failureThreshold: 8
```

Scale back up: `kubectl scale deploy server --replicas 2`
Create a vaulty pod: `kubectl exec -it podname -- sh -c "rm /data.txt" `

If we observe we will notice the readinessProbe saves our traffic, and livenessProbe will eventually replace the bad pod </br>

## Startup Probes

The [startup probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-startup-probes) is for slow starting applications </br>
It's important to understand difference between start up and readiness probes. </br>
In our examples here, readiness probe acts as a startup probe too, since our app is fairly slow starting! </br>
This difference is explained in the video. </br>

---

### Overview (`kubernetes/rancher/README.md`)

# Introduction to Rancher: On-prem Kubernetes

<a href="https://youtu.be/1j5lhDzlFUM" title="k8s-rancher"><img src="https://i.ytimg.com/vi/1j5lhDzlFUM/hqdefault.jpg" width="20%" alt="k8s-rancher" /></a> 

This guide follows the general instructions of running a [manual rancher install](https://rancher.com/docs/rancher/v2.5/en/quick-start-guide/deployment/quickstart-manual-setup/) and running our own infrastructure on Hyper-v

# Hyper-V : Prepare our infrastructure

In this demo, we will use Hyper-V to create our infrastructure.  </br>
For on-premise, many companies use either Hyper-V, VMWare Vsphere and other technologies to create virtual infrastructure on bare metal. </br>

Few points to note here:

* Benefit of Virtual infrastructure is that it's immutable
  a) We can add and throw away virtual machines at will.
  b) This makes maintenance easier as we can roll updated virtual machines instead of 
     patching existing machines and turning them to long-living snowflakes.
  c) Reduce lifespan of machines

* Bare Metal provides the compute. 
  a) We don't want Kubernetes directly on bare metal as we want machines to be immutable.
  b) This goes back to the previous point on immutability.

* Every virtual machine needs to be able to reach each other on the network
  a) This is a kubernetes networking requirements that all nodes can communicate with one another

# Hyper-V : Create our network

In order for us to create virtual machines all on the same network, I am going to create a virtual switch in Hyper-v </br>
Open Powershell in administrator

```
# get our network adapter where all virtual machines will run on
# grab the name we want to use
Get-NetAdapter

Import-Module Hyper-V
$ethernet = Get-NetAdapter -Name "Ethernet 2"
New-VMSwitch -Name "virtual-network" -NetAdapterName $ethernet.Name -AllowManagementOS $true -Notes "shared virtual network interface"
```

# Hyper-V : Create our machines

We firstly need harddrives for every VM. </br>
Let's create three:

```
mkdir c:\temp\vms\linux-0\
mkdir c:\temp\vms\linux-1\
mkdir c:\temp\vms\linux-2\

New-VHD -Path c:\temp\vms\linux-0\linux-0.vhdx -SizeBytes 20GB
New-VHD -Path c:\temp\vms\linux-1\linux-1.vhdx -SizeBytes 20GB
New-VHD -Path c:\temp\vms\linux-2\linux-2.vhdx -SizeBytes 20GB
```

```
New-VM `
-Name "linux-0" `
-Generation 1 `
-MemoryStartupBytes 2048MB `
-SwitchName "virtual-network" `
-VHDPath "c:\temp\vms\linux-0\linux-0.vhdx" `
-Path "c:\temp\vms\linux-0\"

New-VM `
-Name "linux-1" `
-Generation 1 `
-MemoryStartupBytes 2048MB `
-SwitchName "virtual-network" `
-VHDPath "c:\temp\vms\linux-1\linux-1.vhdx" `
-Path "c:\temp\vms\linux-1\"

New-VM `
-Name "linux-2" `
-Generation 1 `
-MemoryStartupBytes 2048MB `
-SwitchName "virtual-network" `
-VHDPath "c:\temp\vms\linux-2\linux-2.vhdx" `
-Path "c:\temp\vms\linux-2\"

```

Setup a DVD drive that holds the `iso` file for Ubuntu Server

```
Set-VMDvdDrive -VMName "linux-0" -ControllerNumber 1 -Path "C:\temp\ubuntu-20.04.3-live-server-amd64.iso"
Set-VMDvdDrive -VMName "linux-1" -ControllerNumber 1 -Path "C:\temp\ubuntu-20.04.3-live-server-amd64.iso"
Set-VMDvdDrive -VMName "linux-2" -ControllerNumber 1 -Path "C:\temp\ubuntu-20.04.3-live-server-amd64.iso"
```

Start our VM's

```
Start-VM -Name "linux-0"
Start-VM -Name "linux-1"
Start-VM -Name "linux-2"
```

Now we can open up Hyper-v Manager and see our infrastructure. </br>
In this video we'll connect to each server, and run through the initial ubuntu setup. </br>
Once finished, select the option to reboot and once it starts, you will notice an `unmount` error on CD-Rom </br>
This is ok, just shut down the server and start it up again.

# Hyper-V : Setup SSH for our machines

Now in this demo, because I need to copy rancher bootstrap commands to each VM, it would be easier to do so
using SSH. So let's connect to each VM in Hyper-V and setup SSH. </br>
This is because `copy+paste` does not work without `Enhanced Session` mode in Ubuntu Server. </br>

Let's temporarily turn on SSH on each server:

```
sudo apt update
sudo apt install -y nano net-tools openssh-server
sudo systemctl enable ssh
sudo ufw allow ssh
sudo systemctl start ssh
```

Record the IP address of each VM so we can SSH to it:

```
sudo ifconfig
# record eth0
linux-0 IP=192.168.0.16
linux-1 IP=192.168.0.17
linux-2 IP=192.168.0.18
```

In new Powershell windows, let's SSH to our VMs

```
ssh linux-0@192.168.0.16
ssh linux-1@192.168.0.17
ssh linux-2@192.168.0.18
```

# Setup Docker

It is required that every machine that needs to join our cluster, has docker running on it. </br>
Firstly, rancher will use docker to run it's agent as well as bootstrap the cluster.</br>

Install docker on each VM:
```
curl -sSL https://get.docker.com/ | sh
sudo usermod -aG docker $(whoami)
sudo service docker start
```

# Running Rancher in Docker

So Rancher can be [deployed](https://rancher.com/docs/rancher/v2.5/en/quick-start-guide/deployment/) almost anywhere. </br>
We can run it in Kubernetes on-prem or the cloud.  </br>

Now because we want Rancher to manage kubernetes clusters, we dont want it running in the clusters we are managing. </br>
So I would like to keep my Rancher server outside and separate from my Kubernetes clusters.</br>

So let's setup a single server with [docker](https://rancher.com/docs/rancher/v2.5/en/quick-start-guide/deployment/quickstart-manual-setup/)

## Persist data

We will want to persist ranchers data across reboots. </br>
Rancher stores its data under `/var/lib/rancher` </br> 

In this repo, let's create a space to persist data:

```
cd kubernetes/rancher
mkdir volume

```

## Run Rancher

```
docker run -d --name rancher-server  -v ${PWD}/volume:/var/lib/rancher --restart=unless-stopped -p 80:80 -p 443:443 --privileged rancher/rancher
```

## Unlock Rancher

Once its up and running we can extract the Rancher initial boostrap password from the logs 

```
docker logs rancher-server > rancher.log
```

## Get Rancher IP

It's important that our servers can reach the Rancher server. </br>
As all the VMs and my machine are on the same network, we can use my machine IP as the server IP so the VM's can reach it. </br>
let's grab the IP:

```
ipconfig
```

We can now access Rancher on [localhost](https://localhost)

## Deploy Sample Workloads

To deploy some sample basic workloads, let's get the `kubeconfig` for our cluster </br>

Set kubeconfig:

```
$ENV:KUBECONFIG="<path-to-kubeconfig>"
```

Deploy 2 pods, and a service:

```
kubectl create ns marcel
kubectl -n marcel apply -f .\kubernetes\configmaps\configmap.yaml
kubectl -n marcel apply -f .\kubernetes\secrets\secret.yaml
kubectl -n marcel apply -f .\kubernetes\deployments\deployment.yaml
kubectl -n marcel apply -f .\kubernetes\services\service.yaml
```

One caveat is because we are not a cloud provider, Kubernetes does not support our service `type=LoadBalancer`. </br>
For that, we need something like `metallb`. </br>
However - we can `port-forward`

```
kubectl -n marcel get svc 
NAME              TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
example-service   LoadBalancer   10.43.235.240   <pending>     80:31310/TCP   13s

kubectl -n marcel port-forward svc/example-service 81:80
```

We can access our example-app on port 81

---

### Overview (`kubernetes/rbac/README.md`)

# Introduction to Kubernetes: RBAC

<a href="https://youtu.be/jvhKOAyD8S8" title="k8s-rbac"><img src="https://i.ytimg.com/vi/jvhKOAyD8S8/hqdefault.jpg" width="20%" alt="k8s-rbac" /></a> 

## Create Kubernetes cluster

```
kind create cluster --name rbac --image kindest/node:v1.20.2
```

## Kubernetes CA Certificate

Kubernetes does not have a concept of users, instead it relies on certificates and would only 
trust certificates signed by its own CA. </br>

To get the CA certificates for our cluster, easiest way is to access the master node. </br>
Because we run on `kind`, our master node is a docker container. </br>
The CA certificates exists in the `/etc/kubernetes/pki` folder by default. </br>
If you are using `minikube` you may find it under `~/.minikube/.`

Access the master node:

```
docker exec -it rbac-control-plane bash

ls -l /etc/kubernetes/pki
total 60
-rw-r--r-- 1 root root 1135 Sep 10 01:38 apiserver-etcd-client.crt
-rw------- 1 root root 1675 Sep 10 01:38 apiserver-etcd-client.key
-rw-r--r-- 1 root root 1143 Sep 10 01:38 apiserver-kubelet-client.crt
-rw------- 1 root root 1679 Sep 10 01:38 apiserver-kubelet-client.key
-rw-r--r-- 1 root root 1306 Sep 10 01:38 apiserver.crt
-rw------- 1 root root 1675 Sep 10 01:38 apiserver.key
-rw-r--r-- 1 root root 1066 Sep 10 01:38 ca.crt
-rw------- 1 root root 1675 Sep 10 01:38 ca.key
drwxr-xr-x 2 root root 4096 Sep 10 01:38 etcd
-rw-r--r-- 1 root root 1078 Sep 10 01:38 front-proxy-ca.crt
-rw------- 1 root root 1679 Sep 10 01:38 front-proxy-ca.key
-rw-r--r-- 1 root root 1103 Sep 10 01:38 front-proxy-client.crt
-rw------- 1 root root 1675 Sep 10 01:38 front-proxy-client.key
-rw------- 1 root root 1679 Sep 10 01:38 sa.key
-rw------- 1 root root  451 Sep 10 01:38 sa.pub

exit the container
```

Copy the certs out of our master node:

```
cd kubernetes/rbac
docker cp rbac-control-plane:/etc/kubernetes/pki/ca.crt ca.crt
docker cp rbac-control-plane:/etc/kubernetes/pki/ca.key ca.key
```

# Kubernetes Users

As mentioned before, Kubernetes has no concept of users, it trusts certificates that is signed by its CA. <br/>
This allows a lot of flexibility as Kubernetes lets you bring your own auth mechanisms, such as [OpenID Connect](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens) or OAuth. </br>

 <p> This allows managed Kubernetes offerings to use their cloud logins to authenticate. </p>

 So on Azure, I can use my Microsoft account, GKE my Google account and AWS EKS my Amazon account. </br>

 You will need to consult your cloud provider to setup authentication. </br>
 Example [Azure AKS](https://docs.microsoft.com/en-us/azure/aks/azure-ad-integration-cli)

## User Certificates

First thing we need to do is create a certificate signed by our Kubernetes CA. </br>
We have the CA, let's make a certificate. </br>

Easy way to create a cert is use `openssl` and the easiest way to get `openssl` is to simply run a container:

```
docker run -it -v ${PWD}:/work -w /work -v ${HOME}:/root/ --net host alpine sh

apk add openssl
```

Let's create a certificate for Bob Smith:


```
#start with a private key
openssl genrsa -out bob.key 2048

```

Now we have a key, we need a certificate signing request (CSR). </br>
We also need to specify the groups that Bob belongs to. </br>
Let's pretend Bob is part of the `Shopping` team and will be developing 
applications for the `Shopping` 

```
openssl req -new -key bob.key -out bob.csr -subj "/CN=Bob Smith/O=Shopping"
```

Use the CA to generate our certificate by signing our CSR. </br>
We may set an expiry on our certificate as well

```
openssl x509 -req -in bob.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out bob.crt -days 1
```

## Building a kube config

Let's install `kubectl` in our container to make things easier:

```
apk add curl nano
curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl
chmod +x ./kubectl
mv ./kubectl /usr/local/bin/kubectl
```

We'll be trying to avoid messing with our current kubernetes config. </br>
So lets tell `kubectl` to look at a new config that does not yet exists 

```
export KUBECONFIG=~/.kube/new-config
```

Create a cluster entry which points to the cluster and contains the details of the CA certificate:

```
kubectl config set-cluster dev-cluster --server=https://127.0.0.1:52807 \
--certificate-authority=ca.crt \
--embed-certs=true

#see changes 
nano ~/.kube/new-config
```


kubectl config set-credentials bob --client-certificate=bob.crt  --client-key=bob.key --embed-certs=true

kubectl config set-context dev --cluster=dev-cluster --namespace=shopping --user=bob 

kubectl config use-context dev

kubectl get pods
Error from server (Forbidden): pods is forbidden: User "Bob Smith" cannot list resource "pods" in API group "" in the namespace "shopping"


## Give Bob Smith Access

```
cd kubernetes/rbac
kubectl create ns shopping

kubectl -n shopping apply -f .\role.yaml
kubectl -n shopping apply -f .\rolebinding.yaml
```

## Test Access as Bob

kubectl get pods
No resources found in shopping namespace.

# Kubernetes Service Accounts

So we've covered users, but what about applications or services running in our cluster ? </br>
Most business apps will not need to connect to the kubernetes API unless you are building something that integrates with your cluster, like a CI/CD tool, an autoscaler or a custom webhook. </br>

Generally applications will use a service account to connect. </br>
You can read more about [Kubernetes Service Accounts](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/).

Let's deploy a service account 

```
kubectl -n shopping apply -f serviceaccount.yaml

```
Now we can deploy a pod that uses the service account 
```
kubectl -n shopping apply -f pod.yaml
```
Now we can test the access from within that pod by trying to list pods:

```
kubectl -n shopping exec -it shopping-api -- bash

# Point to the internal API server hostname
APISERVER=https://kubernetes.default.svc

# Path to ServiceAccount token
SERVICEACCOUNT=/var/run/secrets/kubernetes.io/serviceaccount

# Read this Pod's namespace
NAMESPACE=$(cat ${SERVICEACCOUNT}/namespace)

# Read the ServiceAccount bearer token
TOKEN=$(cat ${SERVICEACCOUNT}/token)

# Reference the internal certificate authority (CA)
CACERT=${SERVICEACCOUNT}/ca.crt

# List pods through the API
curl --cacert ${CACERT} --header "Authorization: Bearer $TOKEN" -s ${APISERVER}/api/v1/namespaces/shopping/pods/ 

# we should see an error not having access
```

Now we can allow this pod to list pods in the shopping namespace
```
kubectl -n shopping apply -f serviceaccount-role.yaml
kubectl -n shopping apply -f serviceaccount-rolebinding.yaml
```

If we try run `curl` command again we can see now we are able to get a json 
response with pod information

---

### Overview (`kubernetes/reloader/README.md`)

# Introduction to K8s Reloader 

In this demo we will walk through a project called [reloader](https://github.com/stakater/Reloader)

## Create a cluster with Kind

```
kind create cluster --name demo --image kindest/node:v1.33.1
```

Checkout our cluster 

```
kubectl get nodes
NAME                 STATUS   ROLES           AGE   VERSION
demo-control-plane   Ready    control-plane   19m   v1.33.1
```

For this demo, we'll want to deploy a `ConfigMap` and/or a `Secret` and consume these using a `Deployment`

Deploy a `ConfigMap`

```
kubectl apply -f kubernetes/configmaps/configmap.yaml
```

Deploy a `Secret`

```
kubectl apply -f kubernetes/secrets/secret.yaml
```

## Install Reloader

```
helm repo add stakater https://stakater.github.io/stakater-charts
helm repo update

# see versions
helm search repo stakater/reloader -l

#pick a version
CHART_VERSION="2.1.5"


helm install reloader stakater/reloader \
--create-namespace \
--namespace reloader \
--version $CHART_VERSION \
--values kubernetes/reloader/values.yaml
```

### Usage

Deploy an application that consumes the 'ConfigMap' and 'Secret' with auto reload enabled:

```
kubectl apply -f kubernetes/reloader/deployment.yaml
```

We can now update the `ConfigMap` object and observe our `example-app` pods restarting when the `ConfigMap` changes.
We can also run `kubectl logs -l app=example-app` in a separate terminal and observe the changing values inside pods.

---

### Overview (`kubernetes/secrets/README.md`)

# Introduction to Kubernetes: Secrets

<a href="https://youtu.be/EkUN4V4Hmws" title="k8s-secrets"><img src="https://i.ytimg.com/vi/EkUN4V4Hmws/hqdefault.jpg" width="20%" alt="k8s-secrets" /></a> 

## Create a cluster with Kind

```
kind create cluster --name secrets --image kindest/node:v1.31.1
```

## Our Secret

We have a secret under `kubernetes/secrets/secret.json`

```
cat kubernetes/secrets/secret.json
```

## Using our secret in a container

As a file:
```
docker run -it -v $PWD/kubernetes/secrets/secret.json:/secrets/secret.json ubuntu:latest bash

cat /secrets/secret.json
```

As environment variables:

```
api_key="somesecretgoeshere"
docker run -it -e API_KEY=$api_key ubuntu:latest bash

echo $API_KEY
```

## Kubernetes Secret

Read more about [Kubernetes secrets](https://kubernetes.io/docs/concepts/configuration/secret/)


## Create our secret

There are two main ways we can create a Kubernetes secret. </br>
Either by creating the secret object with `kubectl create secret` or apply\create it declaratively using YAML with `kubectl apply -f`

`kubectl create secret`:

```
kubectl create secret generic mysecret --from-file kubernetes/secrets/secret.json
```

`kubectl apply -f` or `kubectl create -f` allows us to define things declaratively using YAML files:

```
kubectl apply -f kubernetes/secrets/secret.yaml
```

## Use our secret

In order to use our secret we add a `volume` to our pod spec and then mount that using a `volumeMount` </br>
We can also use a secret references as `env` variable </br>


```
kubectl apply -f kubernetes/secrets/pod.yaml
```

---

### Overview (`kubernetes/servicemesh/readme.md`)

# Introduction to Service Mesh

To understand service mesh, we need a good use case. <br/>
We need some service-to-service communication. <br/>
A basic microservice architecture will do. <br/>

Read Me : [The Introduction Guide](./introduction.md)

Video :point_down: <br/>

<a href="https://www.youtube.com/playlist?list=PLHq1uqvAteVsmxHpGsMjTOROn3i99lzTA" title="Service Mesh Intro"><img src="https://i.ytimg.com/vi/rVNPnHeGYBE/hqdefault.jpg" width="45%" height="45%" alt="Cloud Guide" /></a>


# Service Mesh Guides

## Introduction to Linkerd

Getting started with Linkerd

Read Me:  [readme](./linkerd/README.md)

Video :point_down: <br/>

<a href="https://youtu.be/Hc-XFPHDDk4" title="Cloud K8s"><img src="https://i.ytimg.com/vi/Hc-XFPHDDk4/hqdefault.jpg" width="45%" height="45%" alt="Linkerd" /></a>

## Introduction to Istio

Getting started with Istio

Read Me:  [readme](./istio/README.md)

Video :point_down: <br/>

---

### Overview (`kubernetes/servicemonitors/README.md`)

# Introduction to Service Monitors

<a href="https://youtu.be/_NtRkBipepg" title="k8s-servicemonitors"><img src="https://i.ytimg.com/vi/_NtRkBipepg/hqdefault.jpg" width="20%" alt="k8s-servicemonitors" /></a> 

In order to understand service monitors, we will need to understand how to monitor 
kubernetes environment. </br>
You will need a base understanding of Kubernetes and have a basic understanding of the `kube-prometheus` monitoring stack. </br>

Checkout the video [How to monitor Kubernetes in 2022](https://youtu.be/YDtuwlNTzRc): 

<a href="https://youtu.be/YDtuwlNTzRc" title="Monitoring Kubernetes"><img src="https://i.ytimg.com/vi/YDtuwlNTzRc/hqdefault.jpg" width="50%" alt="Monitoring Kubernetes" /></a>


## Create a kubernetes cluster

```
# create cluster
kind create cluster --name monitoring --image kindest/node:v1.23.5

# see cluster up and running
kubectl get nodes
NAME                  STATUS   ROLES                  AGE     VERSION
monitoring-control-plane   Ready    control-plane,master   2m12s   v1.23.5
```

## Deploy kube-prometheus

Installation: 

```
kubectl create -f ./monitoring/prometheus/kubernetes/1.23/manifests/setup/
kubectl create -f ./monitoring/prometheus/kubernetes/1.23/manifests/
```

Check the install:

```
kubectl -n monitoring get pods
```

After a few minutes, everything should be up and running:

```
kubectl -n monitoring get pods
NAME                                   READY   STATUS    RESTARTS   AGE
alertmanager-main-0                    2/2     Running   0          3m10s
alertmanager-main-1                    2/2     Running   0          3m10s
alertmanager-main-2                    2/2     Running   0          3m10s
blackbox-exporter-6b79c4588b-t4czf     3/3     Running   0          4m7s
grafana-7fd69887fb-zm2d2               1/1     Running   0          4m7s
kube-state-metrics-55f67795cd-f7frb    3/3     Running   0          4m6s
node-exporter-xjdtn                    2/2     Running   0          4m6s
prometheus-adapter-85664b6b74-bvmnj    1/1     Running   0          4m6s
prometheus-adapter-85664b6b74-mcgbz    1/1     Running   0          4m6s
prometheus-k8s-0                       2/2     Running   0          3m9s
prometheus-k8s-1                       2/2     Running   0          3m9s
prometheus-operator-6dc9f66cb7-z98nj   2/2     Running   0          4m6s
```

## View dashboards

```
kubectl -n monitoring port-forward svc/grafana 3000
```

Then access Grafana on [localhost:3000](http://localhost:3000)

## Access Prometheus 

```
kubectl -n monitoring port-forward svc/prometheus-operated 9090
```

Then access Prometheus on [localhost:9090](http://localhost:9090).

## Create our own Prometheus 


```
kubectl apply -n monitoring -f ./kubernetes/servicemonitors/prometheus.yaml

```

View our prometheus `prometheus-applications-0` instance:

```
kubectl -n monitoring get pods
```

Checkout our prometheus UI

```
kubectl -n monitoring port-forward prometheus-applications-0 9090
```

## Deploy a service monitor for example app

```
kubectl -n default apply -f ./kubernetes/servicemonitors/servicemonitor.yaml
```

After applying the service monitor, if Prometheus is correctly selecting it, we should see the item appear under the [Service Discovery](http://localhost:9090/service-discovery) page in Prometheus. </br>
Double check with with `port-forward` before proceeding. </br>
If it does not appear, that means your Prometheus instance is not selecting the service monitor accordingly. Either a label mismatch on the namespace or the service monitor. </br>

## Deploy our example app

```
kubectl -n default apply -f ./kubernetes/servicemonitors/example-app/
```

Now we should see a target in the Prometheus [Targets](http://localhost:9090/targets) page. </br>

---

### Overview (`kubernetes/services/README.md`)

# Introduction to Kubernetes: Services

<a href="https://youtu.be/hiiiGLnPdq4" title="k8s-services"><img src="https://i.ytimg.com/vi/hiiiGLnPdq4/hqdefault.jpg" width="20%" alt="k8s-services" /></a> 

## Create a Kubernetes cluster 

Firstly, we will need a Kubernetes cluster and will create one using [kind](https://kind.sigs.k8s.io/)

```
kind create cluster --name services --image kindest/node:v1.31.1
```

Test the cluster 

```
kubectl get nodes
NAME                     STATUS   ROLES           AGE     VERSION
services-control-plane   Ready    control-plane   5m48s   v1.31.1
```

## Deploy a few pods

Let's deploy a few pods using a Kubernetes Deployment. To understand services, we need to deploy some pods that become our "upstream" or "endpoint" that we want to access. </br>

```
kubectl apply -f kubernetes/deployments/deployment.yaml 
```

## Deploy a service

We can now deploy a Kubernetes service that targets our deployment:


```
kubectl apply -f kubernetes/services/service.yaml
```

---

### Overview (`kubernetes/shipa/README.md`)

# Introduction to Shipa

<a href="https://youtu.be/PW44JaAlI_8" title="shipa"><img src="https://i.ytimg.com/vi/PW44JaAlI_8/hqdefault.jpg" width="20%" alt="shipa" /></a> 

## We need a Kubernetes cluster

To get the most out of Shipa, I'll be using real Cloud Provider Kubernetes as well as a local <br/>
`minikube` cluster. <br/>

## Minikube 

I will start with a local minikube cluster to get Shipa running: <br/>

```
# start up a cluster

minikube start --kubernetes-version='v1.18.2' --memory='5gb' --disk-size='20gb' --driver=hyperv

# check our cluster

kubectl get nodes
NAME       STATUS   ROLES    AGE   VERSION
minikube   Ready    master   45s   v1.18.2

```

## Minikube Traffic Routes

```
# add a route for accessing Shipa API
route add 10.100.10.10/32 MASK 255.255.255.255  $(minikube ip)

# add a route for accessing our Applications
route add 10.100.10.20/32 MASK 255.255.255.255  $(minikube ip)
```

# Getting Started with Shipa

## Install Dependencies

```
docker run -it --rm  -v ${PWD}:/work -w /work alpine sh
apk add --no-cache curl unzip

cd kubernetes/shipa/
mkdir installs && cd installs

```

## Install Kubectl

```
curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.19.4/bin/windows/amd64/kubectl.exe

```

## Install Helm CLI

```
curl -LO https://get.helm.sh/helm-v3.4.1-windows-amd64.zip && \
unzip helm-v3.4.1-windows-amd64.zip && \
mv windows-amd64/helm.exe . && \
rm -rf windows-amd64 && \
rm helm-v3.4.1-windows-amd64.zip

```

## Install Shipa CLI

```
curl -LO  https://storage.googleapis.com/shipa-client/v1.1/shipa_windows_amd64.exe
mv shipa_windows_amd64.exe shipa.exe
```


## Add all the CLI to our $PATH

We can now add the CLI executables to a folder somewhere on our machine <br/>
Then we add it to our `$PATH`

* installs/helm.exe
* installs/kubectl.exe
* installs/shipa.exe

## Download Shipa Helm Chart

Official Docs [here](https://learn.shipa.io/docs/installing-shipa)
We can find all the releases of Shipa [here](https://github.com/shipa-corp/helm-chart/releases)
In this demo, I will use version `1.1.1`

Let's download Shipa: <br/>

```
curl  -L -s -o shipa1.1.1.zip https://github.com/shipa-corp/helm-chart/archive/v1.1.1.zip && \
unzip shipa1.1.1.zip && rm shipa1.1.1.zip && \
mv helm-chart-1.1.1 shipa-helm-chart-1.1.1 && \

# we can abandon this container
exit

```

## Install Shipa

Let's add Shipa to our `minikube` cluster: <br/>

```
/* Detailed source-code truncated for AI context efficiency. */
```

## Targets

```
shipa target-add dev 10.100.10.10
shipa target-list
shipa target-set dev
shipa login

#username: admin@shipa.io
#password: shipa2020

```

## Pools 

https://learn.shipa.io/docs/pool-management

```
shipa pool-add dev --public --kube-namespace dev-team --provisioner kubernetes
shipa pool-list

```

## Teams

```
shipa team-create dev-team
shipa pool-constraint-set theonepool team dev-team --append

```

## Clusters

```
shipa cluster-list
+------------+-------------+---------------+-------------+---------+------------+-------+-------+
| Name       | Provisioner | Addresses     | Custom Data | Default | Pools      | Teams | Error |
+------------+-------------+---------------+-------------+---------+------------+-------+-------+
| shipa-core | kubernetes  | 10.96.0.1:443 |             | false   | theonepool |       |       |
+------------+-------------+---------------+-------------+---------+------------+-------+-------+

```

## Deploy Applications to Shipa (minikube)

```
cd kubernetes\shipa\developers\docker\golang

docker build . -t aimvector/shipa-golang:v1
docker push aimvector/shipa-golang:v1

# create an app: Golang
shipa app-create go-helloworld static -t dev-team -o theonepool

# deploy the app: Golang
shipa app-deploy -i aimvector/shipa-golang:v1 -a go-helloworld
shipa app-list

cd .\kubernetes\shipa\developers\docker\python\

docker build . -t aimvector/shipa-python:v1
docker push aimvector/shipa-python:v1

# create an app: Python
shipa app-create python-helloworld static -t dev-team -o theonepool
shipa env set FLASK_APP=/app/server.py -a python-helloworld

# deploy the app: Python
shipa app-deploy -i aimvector/shipa-python:v1 -a python-helloworld
shipa app-list

```

## Kubernetes in the Cloud

To create a Kubernetes cluster, you can follow my guides on each Cloud provider below: <br/>

|Cloud   | Kubernetes   |  Source |  Video |   |
|---|---|---|---|---|
|Azure          | AKS  |[Source Code](../cloud/azure/getting-started.md) | <a href="https://youtu.be/eyvLwK5C2dw" title="AKS"><img src="https://i.ytimg.com/vi/eyvLwK5C2dw/hqdefault.jpg" width="25%" height="25%" alt="AKS Guide" /></a>  |   
|Amazon         | EKS  |[Source Code](../cloud/amazon/getting-started.md)  |  <a href="https://youtu.be/QThadS3Soig" title="EKS"><img src="https://i.ytimg.com/vi/QThadS3Soig/hqdefault.jpg" width="25%" height="25%" alt="EKS Guide" /></a> |   
|Google         | GKE  |[Source Code](../cloud/google/getting-started.md)  | <a href="https://youtu.be/-fbH5Qs3QXU" title="GKE"><img src="https://i.ytimg.com/vi/-fbH5Qs3QXU/hqdefault.jpg" width="25%" height="25%" alt="GKE Guide" /></a>  |   
|Digital Ocean  | DO  |[Source Code](../cloud/digitalocean/getting-started.md)   | <a href="https://youtu.be/PvfBCE-xgBY" title="DO"><img src="https://i.ytimg.com/vi/PvfBCE-xgBY/hqdefault.jpg" width="25%" height="25%" alt="DO Guide" /></a>  |   
|Linode         | LKE |[Source Code](../cloud/linode/getting-started.md)  | <a href="https://youtu.be/VSPUWEtqtnY" title="LKE"><img src="https://i.ytimg.com/vi/VSPUWEtqtnY/hqdefault.jpg" width="25%" height="25%" alt="LKE Guide" /></a>  |   


## Deploy Shipa to Azure AKS

Let's add Shipa to each one of our clusters running in the Cloud.
In the video I will deploy Shipa to an Azure AKS cluster:

```
# ensure we are pointing to AKS Or the
kubectl get nodes

# deploy Shipa to AKS

cd kubernetes/shipa

# create username + password override
cat > ops/values.override.yaml << EOF
auth:
  adminUser: prod-aks-admin@shipa.io
  adminPassword: shipa-prod-aks2020
EOF

kubectl create ns shipa-system

kubectl apply -n shipa-system -f limits.yaml

helm install shipa ./installs/shipa-helm-chart-1.1.1 --timeout=1000s --namespace=shipa-system -f ops/values.override.yaml

#wait until the dashboard pod is up!
kubectl --namespace=shipa-system get pods

# get the ingress IP for the Shipa API
kubectl --namespace=shipa-system get svc shipa-ingress-nginx  -o jsonpath="{.status.loadBalancer.ingress[0].ip}"

# add the ingress IP as our Shipa target for AKS dev
shipa target-add dev-aks 20.53.128.244 -s
shipa target-set dev-aks
shipa target-list

# lets login, view the node, and dashboard app URL
shipa login prod-aks-admin@shipa.io
shipa node-list
shipa app-list

#add a host file entry for the dashboard, I.E:
20.193.20.106 dashboard.20.193.20.106.shipa.cloud

# create a team for developers
shipa team-create dev-team

#allow dev-team to deploy to AKS pool
shipa pool-constraint-set theonepool team dev-team --append

```

## MultiCloud - Add Shipa to Amazon EKS

```
# add service account to EKS cluster to allow Shipa to connect
kubectl apply -f ops/shipa-admin-service-account.yaml 

#add a pool allow people to deploy to Amazon
shipa pool-add amazon --public --kube-namespace dev-team --provisioner kubernetes

# allow dev-team to deploy to Amazon
shipa pool-constraint-set amazon  team dev-team --append

# We will need the EKS token of the service account to allow connection
kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep shipa-admin | awk '{print $1}')

# We will also need the CA cert of Kubernetes
kubectl get secret $(kubectl get secret | grep default-token | awk '{print $1}') -o jsonpath='{.data.ca\.crt}' | base64 -d
```

On the dashboard, we add the cluster using the above information.<br/>
We can obtain the EKS address in the Amazon console. <br/>

## Deploying Apps to MultiCloud

```
# create an app: Golang
shipa app-create go-helloworld static -t dev-team -o theonepool

# deploy the app: Golang
shipa app-deploy -i aimvector/shipa-golang:v1 -a go-helloworld
shipa app-list

# create an app: Python
shipa app-create python-helloworld static -t dev-team -o amazon
shipa env set FLASK_APP=/app/server.py -a python-helloworld

# deploy the app: Python
shipa app-deploy -i aimvector/shipa-python:v1 -a python-helloworld
shipa app-list

```

























```

https://collabnix.com/the-rise-of-shipa-a-continuous-operation-platform/

1. Configured default user:

Username: admin@admin.com
Password: adminadmin123

2. If this is a production cluster, please configure persistent volumes.
   The default reclaimPolicy for dynamically provisioned persistent volumes is "Delete" and
   users are advised to change it for production

   The code snippet below can be used to set reclaimPolicy to "Retain" for all volumes:

PVCs=$(kubectl --namespace=shipa-system get pvc -l release=shipa -o name)

for pvc in $PVCs; do
    volumeName=$(kubectl -n shipa-system get $pvc -o template --template=\{\{.spec.volumeName\}\})
    kubectl -n shipa-system patch pv $volumeName -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'
done

3. Set default target for shipa-client:
export SHIPA_HOST=$(kubectl --namespace=shipa-system get svc shipa-ingress-nginx  -o jsonpath="{.status.loadBalancer.ingress[0].ip}")

shipa target-add shipa $SHIPA_HOST -s

shipa login admin@admin.com
shipa node-list
shipa app-list

```

---

### Overview (`kubernetes/velero/README.md`)

# Introduction to Velero

<a href="https://youtu.be/zybLTQER0yY" title="k8s-velero"><img src="https://i.ytimg.com/vi/zybLTQER0yY/hqdefault.jpg" width="20%" alt="k8s-velero" /></a> 

## We need a Kubernetes cluster

Lets create a Kubernetes cluster to play with using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/)

```
kind create cluster --name velero --image kindest/node:v1.19.1
```

## Get a container to work in
<br/>
Run a small `alpine linux` container where we can install and play with `velero`: <br/>

```
docker run -it --rm -v ${HOME}:/root/ -v ${PWD}:/work -w /work --net host alpine sh

# install curl & kubectl
apk add --no-cache curl nano
curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl
chmod +x ./kubectl
mv ./kubectl /usr/local/bin/kubectl
export KUBE_EDITOR="nano"

#test cluster access:
/work # kubectl get nodes
NAME                    STATUS   ROLES    AGE   VERSION
velero-control-plane   Ready    master   26m   v1.18.4

```

## Velero CLI

Lets download the `velero` command line tool <br/>
I grabbed the `v1.5.1` release using `curl`

You can go to the [releases](https://github.com/vmware-tanzu/velero/releases/tag/v1.5.1) page to get it

```
curl -L -o /tmp/velero.tar.gz https://github.com/vmware-tanzu/velero/releases/download/v1.5.1/velero-v1.5.1-linux-amd64.tar.gz 
tar -C /tmp -xvf /tmp/velero.tar.gz
mv /tmp/velero-v1.5.1-linux-amd64/velero /usr/local/bin/velero
chmod +x /usr/local/bin/velero

velero --help
```


## Deploy some stuff

```
kubectl apply -f kubernetes/configmaps/configmap.yaml
kubectl apply -f kubernetes/secrets/secret.yaml
kubectl apply -f kubernetes/deployments/deployment.yaml
kubectl apply -f kubernetes/services/service.yaml

kubectl get all 
```

## Create storage in Azure and AWS

In this example, we'll create a storage in AWS and Azure to try both scenarios.</br>
You can follow along in the video </br>

Create a storage account and secret for: [Azure](./azure.md) </br>
Create a storage account and secret for: [AWS](./aws.md) </br>

```


```
## Deploy Velero for Azure

Start [here](./azure.md) </br>

```

# Azure credential file
cat << EOF  > /tmp/credentials-velero
AZURE_STORAGE_ACCOUNT_ACCESS_KEY=${AZURE_STORAGE_ACCOUNT_ACCESS_KEY}
AZURE_CLOUD_NAME=AzurePublicCloud
EOF

velero install \
    --provider azure \
    --plugins velero/velero-plugin-for-microsoft-azure:v1.1.0 \
    --bucket $BLOB_CONTAINER \
    --secret-file /tmp/credentials-velero \
    --backup-location-config resourceGroup=$AZURE_BACKUP_RESOURCE_GROUP,storageAccount=$AZURE_STORAGE_ACCOUNT_NAME,storageAccountKeyEnvVar=AZURE_STORAGE_ACCOUNT_ACCESS_KEY,subscriptionId=$AZURE_BACKUP_SUBSCRIPTION_ID \
    --use-volume-snapshots=false


kubectl -n velero get pods
kubectl logs deployment/velero -n velero

```

## Deploy Velero for AWS

Start [here](./aws.md)

```

cat > /tmp/credentials-velero <<EOF
[default]
aws_access_key_id=$AWS_ACCESS_ID
aws_secret_access_key=$AWS_ACCESS_KEY
EOF

velero install \
    --provider aws \
    --plugins velero/velero-plugin-for-aws:v1.1.0 \
    --bucket $BUCKET \
    --backup-location-config region=$REGION \
    --snapshot-location-config region=$REGION \
    --secret-file /tmp/credentials-velero

kubectl -n velero get pods
kubectl logs deployment/velero -n velero

```

## Create a Backup 

```
velero backup create default-namespace-backup --include-namespaces default

# describe
velero backup describe default-namespace-backup

# logs
velero backup logs default-namespace-backup
```

## Do a Restore

```
# delete all resources

kubectl delete -f kubernetes/configmaps/configmap.yaml
kubectl delete -f kubernetes/secrets/secret.yaml
kubectl delete -f kubernetes/deployments/deployment.yaml
kubectl delete -f kubernetes/services/service.yaml

velero restore create default-namespace-backup --from-backup default-namespace-backup

# describe
velero restore describe default-namespace-backup

#logs 
velero restore logs default-namespace-backup

# see items restored

kubectl get all
```

---

### Overview (`messaging/kafka/README.md`)

# Introduction to Kafka

<a href="https://youtu.be/heR3I3Wxgro" title="kafka-intro"><img src="https://i.ytimg.com/vi/heR3I3Wxgro/hqdefault.jpg" width="20%" alt="kafka-intro" /></a> 

Official [Docs](https://kafka.apache.org/)

## Building a Docker file

As always, we start with a `dockerfile` </br>
We can build our `dockerfile`

```
cd .\messaging\kafka\
docker build . -t aimvector/kafka:2.7.0

```

## Exploring the Kafka Install

We can then run it to explore the contents:

```
docker run --rm --name kafka -it aimvector/kafka:2.7.0 bash

ls -l /kafka/bin/
cat /kafka/config/server.properties
```

We can use the `docker cp` command to copy the file out of our container:

```
docker cp kafka:/kafka/config/server.properties ./server.properties
docker cp kafka:/kafka/config/zookeeper.properties ./zookeeper.properties
```

Note: We'll need the Kafka configuration to tune our server and Kafka also requires
at least one Zookeeper instance in order to function. To achieve high availability, we'll run
multiple kafka as well as multiple zookeeper instances in the future

# Zookeeper

Let's build a Zookeeper image. The Apache folks have made it easy to start a Zookeeper instance the same way as the Kafka instance by simply running the `start-zookeeper.sh` script.

```
cd ./zookeeper
docker build . -t aimvector/zookeeper:2.7.0
cd ..
```

Let's create a kafka network and run 1 zookeeper instance

```
docker network create kafka
docker run -d `
--rm `
--name zookeeper-1 `
--net kafka `
-v ${PWD}/config/zookeeper-1/zookeeper.properties:/kafka/config/zookeeper.properties `
aimvector/zookeeper:2.7.0

docker logs zookeeper-1
```

# Kafka - 1

```
docker run -d `
--rm `
--name kafka-1 `
--net kafka `
-v ${PWD}/config/kafka-1/server.properties:/kafka/config/server.properties `
aimvector/kafka:2.7.0

docker logs kafka-1
```

# Kafka - 2

```
docker run -d `
--rm `
--name kafka-2 `
--net kafka `
-v ${PWD}/config/kafka-2/server.properties:/kafka/config/server.properties `
aimvector/kafka:2.7.0
```

# Kafka - 3

```
docker run -d `
--rm `
--name kafka-3 `
--net kafka `
-v ${PWD}/config/kafka-3/server.properties:/kafka/config/server.properties `
aimvector/kafka:2.7.0
```


# Topic

Let's create a Topic that allows us to store `Order` information. </br>
To create a topic, Kafka and Zookeeper have scripts with the installer that allows us to do so. </br>

Access the container:
```
docker exec -it zookeeper-1 bash
```
Create the Topic:
```
/kafka/bin/kafka-topics.sh \
--create \
--zookeeper zookeeper-1:2181 \
--replication-factor 1 \
--partitions 3 \
--topic Orders
```

Describe our Topic:
```
/kafka/bin/kafka-topics.sh \
--describe \
--topic Orders \
--zookeeper zookeeper-1:2181
```

# Simple Producer & Consumer

The Kafka installation also ships with a script that allows us to produce
and consume messages to our Kafka network: <br/>

We can then run the consumer that will receive that message on that Orders topic:

```
docker exec -it zookeeper-1 bash

/kafka/bin/kafka-console-consumer.sh \
--bootstrap-server kafka-1:9092,kafka-2:9092,kafka-3:9092 \
--topic Orders --from-beginning

```

With a consumer in place, we can start producing messages

```
docker exec -it zookeeper-1 bash

echo "New Order: 1" | \
/kafka/bin/kafka-console-producer.sh \
--broker-list kafka-1:9092,kafka-2:9092,kafka-3:9092 \
--topic Orders > /dev/null
```


Once we have a message in Kafka, we can explore where it got stored in which partition:

```
docker exec -it kafka-1 bash

apt install -y tree
tree /tmp/kafka-logs/


ls -lh /tmp/kafka-logs/Orders-*

/tmp/kafka-logs/Orders-0:
total 4.0K
-rw-r--r-- 1 root root 10M May  4 06:54 00000000000000000000.index    
-rw-r--r-- 1 root root   0 May  4 06:54 00000000000000000000.log      
-rw-r--r-- 1 root root 10M May  4 06:54 00000000000000000000.timeindex
-rw-r--r-- 1 root root   8 May  4 06:54 leader-epoch-checkpoint       

/tmp/kafka-logs/Orders-1:
total 4.0K
-rw-r--r-- 1 root root 10M May  4 06:54 00000000000000000000.index    
-rw-r--r-- 1 root root   0 May  4 06:54 00000000000000000000.log      
-rw-r--r-- 1 root root 10M May  4 06:54 00000000000000000000.timeindex
-rw-r--r-- 1 root root   8 May  4 06:54 leader-epoch-checkpoint       

/tmp/kafka-logs/Orders-2:
total 8.0K
-rw-r--r-- 1 root root 10M May  4 06:54 00000000000000000000.index    
-rw-r--r-- 1 root root  80 May  4 06:57 00000000000000000000.log      
-rw-r--r-- 1 root root 10M May  4 06:54 00000000000000000000.timeindex
-rw-r--r-- 1 root root   8 May  4 06:54 leader-epoch-checkpoint
```

By seeing 0 bytes in partition 0 and 1, we know the message is sitting in partition 2 as it has 80 bytes. </br>
We can check the message with :

```
cat /tmp/kafka-logs/Orders-2/*.log
```

# Docker Compose with Kafka

So far we've taken a look at staring up Kafka and Zookeeper instances with
docker commands. </br>
We've explored the kafka configuration and how to produce and consume messages. </br>
Let's put it all together in a docker compose file. </br>

With compose we'd like to be able to build our containers, pointing to a 
dockerfile folder with `build.context`. </br>
We'll also use volumes to mount config files.

<b>Important note</b> that producers and consumers are running kafka images
because kafka installation comes prepacked with example consumers and producers as scripts.
We override the kafka entrypoint with bash and stdin so it starts in a 
paused state so that we have run scripts on these instances.

Let's start with an empty `docker-compose.yaml` file :

```
version: "3.8"
services:

```
## Zookeeper

```
zookeeper-1:
  container_name: zookeeper-1
  image: aimvector/zookeeper:2.7.0
  build:
    context: ./zookeeper
  volumes:
  - ./config/zookeeper-1/zookeeper.properties:/kafka/config/zookeeper.properties
```

## Kafka-1 to 3

We run 3 kafka instances. <br/>
Changing the service name, container name and config mount folder:

```
kafka-1:
  container_name: kafka-1
  image: aimvector/kafka:2.7.0
  build: 
    context: .
  volumes:
  - ./config/kafka-1/server.properties:/kafka/config/server.properties
  - ./data/kafka-1/:/tmp/kafka-logs/
```

## Producer

```
  kafka-producer:
    container_name: kafka-producer
    image: aimvector/kafka:2.7.0
    build: 
      context: .
    working_dir: /kafka
    entrypoint: /bin/bash
    stdin_open: true
    tty: true
```

## Consumer

```
  kafka-consumer:
    container_name: kafka-consumer
    image: aimvector/kafka:2.7.0
    build: 
      context: .
    working_dir: /kafka
    entrypoint: /bin/bash
    stdin_open: true
    tty: true
```

## Start the containers

```
cd messaging\kafka

docker compose build
docker compose up
```

---

### Overview (`messaging/rabbitmq/readme.md`)

# RabbitMQ

<a href="https://youtu.be/hfUIWe1tK8E" title="rabbitmq-intro"><img src="https://i.ytimg.com/vi/hfUIWe1tK8E/hqdefault.jpg" width="20%" alt="rabbitmq-intro" /></a> 

Docker image over [here](https://hub.docker.com/_/rabbitmq)
```
# run a standalone instance
docker network create rabbits
docker run -d --rm --net rabbits --hostname rabbit-1 --name rabbit-1 rabbitmq:3.8 

# how to grab existing erlang cookie
docker exec -it rabbit-1 cat /var/lib/rabbitmq/.erlang.cookie

# clean up
docker rm -f rabbit-1
```

# Management

```
docker run -d --rm --net rabbits -p 8080:15672 -e RABBITMQ_ERLANG_COOKIE=DSHEVCXBBETJJVJWTOWT --hostname rabbit-manager --name rabbit-manager rabbitmq:3.8-management

#join the manager

docker exec -it rabbit-manager rabbitmqctl stop_app
docker exec -it rabbit-manager rabbitmqctl reset
docker exec -it rabbit-manager rabbitmqctl join_cluster rabbit@rabbit-1
docker exec -it rabbit-manager rabbitmqctl start_app
docker exec -it rabbit-manager rabbitmqctl cluster_status
```

# Enable Statistics

```
docker exec -it rabbit-1 rabbitmq-plugins enable rabbitmq_management
docker exec -it rabbit-2 rabbitmq-plugins enable rabbitmq_management
docker exec -it rabbit-3 rabbitmq-plugins enable rabbitmq_management
```

# Message Publisher

```

cd messaging\rabbitmq\applications\publisher
docker build . -t aimvector/rabbitmq-publisher:v1.0.0

docker run -it --rm --net rabbits -e RABBIT_HOST=rabbit-1 -e RABBIT_PORT=5672 -e RABBIT_USERNAME=guest -e RABBIT_PASSWORD=guest -p 80:80 aimvector/rabbitmq-publisher:v1.0.0
```

# Message Consumer

```

docker build . -t aimvector/rabbitmq-consumer:v1.0.0
docker run -it --rm --net rabbits -e RABBIT_HOST=rabbit-1 -e RABBIT_PORT=5672 -e RABBIT_USERNAME=guest -e RABBIT_PASSWORD=guest aimvector/rabbitmq-consumer:v1.0.0
```

# Clustering 

https://www.rabbitmq.com/cluster-formation.html

## Note

Remember we will need the Erlang Cookie to allow instances to authenticate with each other.

# Manual Clustering 

```

docker exec -it rabbit-1 rabbitmqctl cluster_status

#join node 2

docker exec -it rabbit-2 rabbitmqctl stop_app
docker exec -it rabbit-2 rabbitmqctl reset
docker exec -it rabbit-2 rabbitmqctl join_cluster rabbit@rabbit-1
docker exec -it rabbit-2 rabbitmqctl start_app
docker exec -it rabbit-2 rabbitmqctl cluster_status

#join node 3
docker exec -it rabbit-3 rabbitmqctl stop_app
docker exec -it rabbit-3 rabbitmqctl reset
docker exec -it rabbit-3 rabbitmqctl join_cluster rabbit@rabbit-1
docker exec -it rabbit-3 rabbitmqctl start_app
docker exec -it rabbit-3 rabbitmqctl cluster_status

```

# Automated Clustering

```
docker run -d --rm --net rabbits `
-v ${PWD}/config/rabbit-1/:/config/ `
-e RABBITMQ_CONFIG_FILE=/config/rabbitmq `
-e RABBITMQ_ERLANG_COOKIE=WIWVHCDTCIUAWANLMQAW `
--hostname rabbit-1 `
--name rabbit-1 `
-p 8081:15672 `
rabbitmq:3.8-management

docker run -d --rm --net rabbits `
-v ${PWD}/config/rabbit-2/:/config/ `
-e RABBITMQ_CONFIG_FILE=/config/rabbitmq `
-e RABBITMQ_ERLANG_COOKIE=WIWVHCDTCIUAWANLMQAW `
--hostname rabbit-2 `
--name rabbit-2 `
-p 8082:15672 `
rabbitmq:3.8-management

docker run -d --rm --net rabbits `
-v ${PWD}/config/rabbit-3/:/config/ `
-e RABBITMQ_CONFIG_FILE=/config/rabbitmq `
-e RABBITMQ_ERLANG_COOKIE=WIWVHCDTCIUAWANLMQAW `
--hostname rabbit-3 `
--name rabbit-3 `
-p 8083:15672 `
rabbitmq:3.8-management

#NODE 1 : MANAGEMENT http://localhost:8081
#NODE 2 : MANAGEMENT http://localhost:8082
#NODE 3 : MANAGEMENT http://localhost:8083

# enable federation plugin
docker exec -it rabbit-1 rabbitmq-plugins enable rabbitmq_federation 
docker exec -it rabbit-2 rabbitmq-plugins enable rabbitmq_federation
docker exec -it rabbit-3 rabbitmq-plugins enable rabbitmq_federation

```

# Basic Queue Mirroring 

```
docker exec -it rabbit-1 bash

# https://www.rabbitmq.com/ha.html#mirroring-arguments

rabbitmqctl set_policy ha-fed \
    ".*" '{"federation-upstream-set":"all", "ha-mode":"nodes", "ha-params":["rabbit@rabbit-1","rabbit@rabbit-2","rabbit@rabbit-3"]}' \
    --priority 1 \
    --apply-to queues
```

# Automatic Synchronization

https://www.rabbitmq.com/ha.html#unsynchronised-mirrors

```
rabbitmqctl set_policy ha-fed \
    ".*" '{"federation-upstream-set":"all", "ha-sync-mode":"automatic", "ha-mode":"nodes", "ha-params":["rabbit@rabbit-1","rabbit@rabbit-2","rabbit@rabbit-3"]}' \
    --priority 1 \
    --apply-to queues
```

# Further Reading

https://www.rabbitmq.com/ha.html


# Clean up

```
docker rm -f rabbit-1
docker rm -f rabbit-2
docker rm -f rabbit-3
```

# RabbitMQ on Kubernetes

Checkout the Kubernetes walkthrough [here](./kubernetes/readme.md)

---

### Overview (`monitoring/fluentbit/README.md`)

# Introduction to FluentBit 

We always start with the [official documentation](https://docs.fluentbit.io/manual)


## Running fluentbit

```
# starts fluentbit with default config
docker run -it cr.fluentbit.io/fluent/fluent-bit:4.0

# see HELP text 
docker run -it cr.fluentbit.io/fluent/fluent-bit:4.0 -h
```

## Configure fluentbit

We've created a new [fluent-bit.yaml](./fluent-bit.yaml) with the example content from the documentation. </br>

We can update our `docker` command to mount the config and tell `fluent-bit` to use the new configuration file we created 

```shell
docker run -it -p 2020:2020 \
-v ./monitoring/fluentbit/fluent-bit.yaml:/fluent-bit/etc/fluent-bit.yaml \
cr.fluentbit.io/fluent/fluent-bit:4.0 --config=/fluent-bit/etc/fluent-bit.yaml
```

## Inputs

The example config file has a `random` input. </br>
Let's create two inputs, one reading log files and another accepting HTTP inputs 

```yaml
pipeline:
  inputs:
  - name: tail
    path: /var/lib/docker/containers/*/*.log
    multiline.parser: docker

  - name: http
    listen: 0.0.0.0
    port: 8888
```

We'll need to add a volume mount so our fluent-bit container has access to the container logs, so let's mount `/var/lib/docker/` 

We also need port `8888` to match our input plugin port

```shell
docker run -it \
-p 2020:2020 \
-p 8888:8888 \
-v ./monitoring/fluentbit/fluent-bit.yaml:/fluent-bit/etc/fluent-bit.yaml \
-v /var/lib/docker:/var/lib/docker \
cr.fluentbit.io/fluent/fluent-bit:4.0 --config=/fluent-bit/etc/fluent-bit.yaml

```

Test our HTTP input plugin using `curl` 

```shell
curl -d '{"namespace":"product","message":"hello from product"}' -X POST -H "content-type: application/json" http://localhost:8888/product.log
```

## Outputs

In the example config file we had a `stdout` output plugin that writes all data from the input plugin to the terminal. </br>
Let's create another output plugin that writes data to file so we can inspect it:

```yaml
- name: file
  match: '*'
  template: '{log}'
  path: /fluent-bit/.data
```

Let's add a volume mount so we can see the output data. </br>
Let's mount a local `.data` folder to `/fluent-bit/.data` 

```shell
docker run -it \
-p 2020:2020 \
-p 8888:8888 \
-v ./monitoring/fluentbit/fluent-bit.yaml:/fluent-bit/etc/fluent-bit.yaml \
-v /var/lib/docker:/var/lib/docker \
-v ./monitoring/fluentbit/.data:/fluent-bit/.data \
cr.fluentbit.io/fluent/fluent-bit:4.0 --config=/fluent-bit/etc/fluent-bit.yaml
```

## Concepts

There are key [concepts](https://docs.fluentbit.io/manual/concepts/key-concepts#events-or-records) to learn about fluent-bit, now that we have events being ingested. </br>

### Filter plugin

This plugin only includes requests from "product" namespace
```yaml
filters:
- name: grep
  match: '*'
  regex: namespace product
```

This plugin excludes all requests from "search" namespace

```yaml
filters:
- name: grep
  match: '*'
  exclude: namespace search
```

Test our HTTP input plugin using `curl` to send different events to our fluent-bit service:

```shell

#request allowed
curl -d '{"namespace":"product","message":"hello from product"}' -X POST -H "content-type: application/json" http://localhost:8888/product.log

# request dropped\filtered
curl -d '{"namespace":"search","message":"hello from search"}' -X POST -H "content-type: application/json" http://localhost:8888/search.log

```

---

### Overview (`monitoring/logging/readme.md`)

# Logging

## Logging Basics

<a href="https://youtu.be/MMVdkzeQ848" title="logging-intro"><img src="https://i.ytimg.com/vi/MMVdkzeQ848/hqdefault.jpg" width="20%" alt="logging-intro" /></a> 

* Standardised Logging
* Centralised Logging 

[Check it out](./fluentd/basic-demo/readme.md)

## Introduction to Fluentd

<a href="https://youtu.be/Gp0-7oVOtPw" title="fluentd-intro"><img src="https://i.ytimg.com/vi/Gp0-7oVOtPw/hqdefault.jpg" width="20%" alt="fluentd-intro" /></a> 

* What is fluentd
* Configuration
* Plugins
* Demos

[Check if out](./fluentd/introduction/readme.md)

## Introduction to OpenTelemetry

<a href="https://youtu.be/bIxt1b0GOU4" title="fluentd-intro"><img src="https://i.ytimg.com/vi/bIxt1b0GOU4/hqdefault.jpg" width="20%" alt="opentel-intro" /></a> 

* What is OpenTelemetry
* Configuration
* Demos

---

### Overview (`monitoring/opentelemetry/README.md`)

# OpenTelemetry

## Logs

<a href="https://youtu.be/bIxt1b0GOU4" title="intro"><img src="https://i.ytimg.com/vi/bIxt1b0GOU4/hqdefault.jpg" width="20%" alt="intro" /></a> 


## Tracing 

[Guide here](./tracing/README.md)

<a href="https://youtu.be/LQOeaxfiAt8" title="intro"><img src="https://i.ytimg.com/vi/LQOeaxfiAt8/hqdefault.jpg" width="20%" alt="intro" /></a> 


## Metrics

[Guide here](./metrics/README.md)

---

### Overview (`monitoring/prometheus/readme.md`)

# Prometheus Application Monitoring
## a Video reference guide

To run any of the commands, please ensure you open a terminal and navigate to the path where this readme is located.

## Start Prometheus, Grafana & Dashboards

```
docker compose up -d prometheus
docker compose up -d grafana
docker compose up -d grafana-dashboards
```


## Start the example app you prefer

```
docker compose up -d --build go-application
docker compose up -d --build python-application
docker compose up -d --build dotnet-application
docker compose up -d --build nodejs-application
```

## Generate some requests by opening the application in the browser

```
http://localhost:80 #Golang
http://localhost:81 #Python
http://localhost:82 #Dotnet
http://localhost:83 #NodeJS
```

## Check Dashboards
```
http://localhost:3000

```
## Prometheus Queries
### Golang Examples

Requests per Second over 2minutes
```
irate(go_request_operations_total[2m])
```
Request duration
```
rate(go_request_duration_seconds_sum[2m]) / rate(go_request_duration_seconds_total[2m])
```

# Prometheus Guide on Kubernetes

Checkout the prometheus guide [here](./kubernetes/readme.md)

---

### Overview (`python/introduction/README.md`)

# Introduction to Learning Python

Best place to start is the documentation [www.python.org](https:#www.python.org/) <br/>

Download [Python 3](https:#www.python.org/downloads/) <br/>

Docker images for Python on [Docker Hub](https:#hub.docker.com/_/python) <br/>


# Run Python in Docker

We can also run python in a docker container: <br/>

```
cd python\introduction

docker build --target dev . -t python
docker run -it -v ${PWD}:/work python sh

/work # python --version
Python 3.9.

```

# Our first Program

* Create a folder containing our application source code

```
mkdir src

```

* Create basic source code

In the `src` folder, create a program called `app.py`
Paste the following content into it.

```

def main():
	print("Hello, world.")

main()
```

* Run your application code

You can run your application 

```
/work # python src/app.py
Hello, world.
```

# The Code

In the video we cover writing functions. </br>
It allows us to execute a block of code <br/>
You want to give your function a single purpose <br/>
Functions can have an input and return an output <br/>
Well thought out functions makes it easier to write tests <br/>

Instead of doing a boring `x + y` function that adds two numbers, let's do something a little
more realistic but still basic: 

```
# This function returns some data 
# The data could be coming from a database, or a file.
# The person calling the function should not care
# how the data is retrieved.
# Since the function does not leak its Data provider

def getCustomer():
  return "Marcel Dempers"

print(getCustomer())

```

# Function inputs

As we saw, functions can return outputs, and also take inputs.
Let's accept a customer ID and return a customer record with that ID:

```
def getCustomer(customerID):
  return "CustomerID: " + customerID + ", Name: Marcel Dempers"

print(getCustomer("abc"))
```


## Variables

To hold data in programming languages, we use variables. <br/>
Variables take up space in memory, so we want to keep it minimal. <br/>
Let's declare variables in our function:

```
def getCustomer(customerID):
  firstName = "Marcel"
  lastName = "Dempers"
  fullName = firstName + " " + lastName

  return fullName

  #or we can return the computation instead of adding another variable!
  return firstName + " " + lastName

  #or we don't even need to declare variables :)
  return "Marcel Dempers"

customer = getCustomer("abc")
print(customer)

```

## Control Flows (if\else)

You can see we're not using the `customerId` input in our function. <br/>
Let's use it and showcase control flows to create logic! <br/>

Control flows allow us to add "rules" to our code. </br>
"If this is the case, then do that, else do something else".

So let's say we have a customer ID 1 coming in, we may only want to
return our customer if it matches the `customerID`

```

def getCustomer(customerID):
  if customerID == "abc":
    return "Marcel Dempers"
  elif customerID == "def":
    return "Bob Smith"
  else:
    return ""

customer = getCustomer("abc")
print(customer)

```

Let's invoke our function :

```
marcel = getCustomer("abc")
print(marcel)

bob = getCustomer("def")
print(bob)

```

## Arrays

Our function can only return one customer at a time. <br/>
What if we wanted to return more than one customer. <br/>
In real world systems, we may return database records. <br/>
This is where we start looking at arrays, lists, dictionaries etc. <br/>

Let's add another function to get an array of customers!

```
def getCustomers():
  customers = [ "Marcel Dempers", "Bob Smith" ]
  return customers

customers = getCustomers()
print(customers)
```

You can get array values by index starting at `0`:

```
customers = getCustomers()

marcel = customers[0]
bob = customers[1]

print(marcel + "\n" + bob)

```

We can set a value in our Array. <br/>
Arrays are fixed in size, notice we cannot just access a 
record : <br/>

```
customers[2] = "John Smith"
IndexError: list assignment index out of range

```

Instead we can use the `append()` function of the array to add items.

```
def getCustomers():
  customers = [ "Marcel Dempers", "Bob Smith" ]
  customers.append("James Baker")
  customers.append("Jonathan D")
  customers.append("Aleem Janmohamed")
  customers.append("Ivo Galic")
  customers.append("Joel Griffiths")
  customers.append("Michael Spinks")
  customers.append("Victor Savkov")
  return customers

```

We can remove items with the `remove()` function.

```
customers.remove("Bob Smith")
```

# Dictionaries

Dictionaries have an advantage whereby items can be found using a key. <br/>
With arrays, we have to know the index number where the item is located or else we cannot find items unless we write a loop. <br/>
With Dictionaries, we store key value pairs. So the key, may be our customer ID.<br/>

Let's replace our customer array with a dictionary
```
  customers = {
    "a": "James Baker",
    "b": "Jonathan D",
    "c": "Aleem Janmohamed",
    "d": "Ivo Galic",
    "e": "Joel Griffiths",
    "f": "Michael Spinks",
    "g": "Victor Savkov"
  }

```

We can also set and access an item by key

```
#update or create item
customers["h"] = "Marcel Dempers"

#get an item
marcel = customers["h"]
print(marcel)

```

Let's update our `getCustomer()` function to get customer from our dictionary

```
def getCustomer(customerID):
  customer = getCustomers()
  return customer[customerID]

def getCustomers():
  customers = {
    "a": "James Baker",
    "b": "Jonathan D",
    "c": "Aleem Janmohamed",
    "d": "Ivo Galic",
    "e": "Joel Griffiths",
    "f": "Michael Spinks",
    "g": "Victor Savkov",
    "h" : "Marcel Dempers"
  }
  
  return customers

customers = getCustomers()
customer = getCustomer("h")

print(customer)
print(customers)
```

## Loops 

Loops are used to iterate over collections, lists, arrays etc. <br/>
There are two primitive loops in Python </br>
Let's say we need to loop through our customers

### While loop

While loops are simple to write by condition:

```
i = 0
while i < 7:
  print(customers[i])
  i += 1
```

We can also use this condition to loop arrays:

```
customers = [ "Marcel Dempers", "Bob Smith" ]
customers.append("James Baker")
customers.append("Jonathan D")
customers.append("Aleem Janmohamed")
customers.append("Ivo Galic")
customers.append("Joel Griffiths")
customers.append("Michael Spinks")
customers.append("Victor Savkov")
i = 0
while i <= 8:
  print(customers[i])
  i += 1
```

### For loops

For looping a dictionary of list, `for` loops are a little easier:

```
customers = getCustomers()

for customerID in customers:
  print(customers[customerID])
```

## Classes and Objects

So far so good, however, customer data is not useful as strings. <br/>
Customers can have a firstname, lastname, and more properties. <br/>

For this purpose we'd like to group some variables into a single variable. <br/>
This is what `classes` allows us to do. <br/>
Let's create a `class` for our customer

```
class Customer:
  customerID = ""
  firstName  = ""
  lastName   = ""
  def fullName(self):
    return self.firstName + " " + self.lastName

marcel = Customer()
marcel.firstName = "Marcel"
marcel.lastName = "Dempers"
marcel.customerID = "h"

print(marcel.fullName())

```

Now how can we create an object with our values without having to set every
value individually ? 
Constructors allow us to initialise our object with some default values.

```
class Customer:
  def __init__(self, c="", f="", l=""):
    self.customerID = c
    self.firstName  = f
    self.lastName   = l
  def fullName(self):
    return self.firstName + " " + self.lastName

marcel = Customer("h", "Marcel", "Dempers")

print(marcel.fullName())

```

Let's plug our class into our current application and put it all together:
Firstly, we convert all our customers from strings to objects:

```
def getCustomers():
  customers = {
    "a": Customer("a","James", "Baker"),
    "b": Customer("b", "Jonathan", "D"),
    "c": Customer("c", "Aleem", "Janmohamed"),
    "d": Customer("d", "Ivo", "Galic"),
    "e": Customer("e", "Joel", "Griffiths"),
    "f": Customer("f", "Michael", "Spinks"),
    "g": Customer("g", "Victor", "Savkov"),
    "h" : Customer("h", "Marcel", "Dempers")
  }
  
  return customers
```

Then we can proceed to get our customer and access their fields:

```
customers = getCustomers()
for customerID in customers:
  print(customers[customerID].fullName())
```

# Docker

So far we have used our container as a development environment. <br/>
Docker allows us to run python in an isolated environment. <br/>
Docker also has a concept of stages, I.E Docker Multistage. <br/>

Therefore we may have a stage for development where we simply mount source code to access a python environment. In addition to this, we may add another layer for debugging where we can install debugger and extra development tools which we 
do not want in production. <br/>
Finally we can create a smaller stage which contains only python + our source which we can run as a runtime image in production.

Example: 

```
FROM python:3.9.6-alpine3.13 as dev

WORKDIR /work

FROM python:3.9.6-alpine3.13 as debugging

# add a debugger

FROM dev as runtime
COPY ./src/ /app 

ENTRYPOINT [ "python", "/app/app.py" ]

```

## Building the Container

```
docker build . -t customer-app

```

## Running the Container

```
docker run customer-app
```

---

### Overview (`storage/redis/readme.md`)

# Redis 

<a href="https://youtu.be/L3zp347cWNw" title="redis-intro"><img src="https://i.ytimg.com/vi/L3zp347cWNw/hqdefault.jpg" width="20%" alt="redis-intro" /></a> 

## Docker 

Docker image over [here](https://hub.docker.com/_/redis)

## Running redis

```
docker network create redis
docker run -it --rm --name redis --net redis -p 6379:6379 redis:6.0-alpine
```

## Configuration

Redis configuration documentation [here](https://redis.io/topics/config)

Starting Redis with a custom config

```
cd .\storage\redis\
docker run -it --rm --name redis --net redis -v ${PWD}/config:/etc/redis/ redis:6.0-alpine redis-server /etc/redis/redis.conf

```

## Security

Redis should not be exposed to public.
Always use a strong password in `redis.conf`

```
requirepass SuperSecretSecureStrongPass
```


## Persistence

Redis Persistence Documentation [here](https://redis.io/topics/persistence)

```
docker volume create redis
cd .\storage\redis\
docker run -it --rm --name redis --net redis -v ${PWD}/config:/etc/redis/ -v redis:/data/  redis:6.0-alpine redis-server /etc/redis/redis.conf

```


## Redis client application

An example application that reads a key from Redis, increments it and writes it back to Redis.

```
cd .\storage\redis\applications\client\

# start go dev environment
docker run -it -v ${PWD}:/go/src -w /go/src --net redis -p 80:80 golang:1.14-alpine

go build client.go
# start the app
./client

# build the container
docker build . -t aimvector/redis-client:v1.0.0

```

Run our application

```
cd .\storage\redis\applications\client\
docker build . -t aimvector/redis-client:v1.0.0

docker run -it --net redis `
-e REDIS_HOST=redis `
-e REDIS_PORT=6379 `
-e REDIS_PASSWORD="SuperSecretSecureStrongPass" `
-p 80:80 `
aimvector/redis-client:v1.0.0

```

## Redis Replication and High Availability

Lets move on to the [clustering](./clustering/readme.md) secion.

---

### Overview (`ai/agents/openclaw/README.md`)

# Introduction to OpenClaw

<a href="https://youtu.be/Xst5EPifJWM" title="openclaw-intro"><img src="https://i.ytimg.com/vi/Xst5EPifJWM/hqdefault.jpg" width="40%" alt="openclaw-intro" /></a>


<a href="https://youtu.be/l5sfnrQfgyk" title="openclaw-docker"><img src="https://i.ytimg.com/vi/l5sfnrQfgyk/hqdefault.jpg" width="40%" alt="openclaw-docker" /></a>



In this video, we will dive into the features of OpenClaw. </br>

[Documentation](https://openclaw.ai/)

## Source Code

```shell
git clone --depth 1 https://github.com/openclaw/openclaw.git /tmp/openclaw
```

## Docker 

View docker setup scripts:

```shell
tree /tmp/openclaw/scripts/docker/
/tmp/openclaw/scripts/docker/
├── cleanup-smoke
│   ├── Dockerfile
│   └── run.sh
├── install-sh-common
│   ├── cli-verify.sh
│   └── version-parse.sh
├── install-sh-e2e
│   ├── Dockerfile
│   └── run.sh
├── install-sh-nonroot
│   ├── Dockerfile
│   └── run.sh
├── install-sh-smoke
│   ├── Dockerfile
│   └── run.sh
├── sandbox
│   ├── Dockerfile
│   ├── Dockerfile.browser
│   └── Dockerfile.common
├── setup.sh
└── shared-image-artifact.sh
```

After cloning the code, we can run setup according to the docs. </br>
Use a pre-built image to avoid having to build it ourselves:

```shell
export OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:latest"
./scripts/docker/setup.sh
```

Build our own custom [dockerfile](./dockerfile): 

```shell
docker build . -t aimvector/openclaw
```

### Least privileges

Linux containers run with a default set of capabilities, which are fine-grained privileges that are subsets of what `root` can do. `--cap-drop ALL` removes every single one, leaving the container with zero elevated privileges.

| Capability | Allows |
|---|---|
| `CAP_NET_BIND_SERVICE` | Bind to ports < 1024 |
| `CAP_NET_RAW` | Raw sockets, packet sniffing |
| `CAP_SYS_ADMIN` | Mount filesystems and many other things |
| `CAP_CHOWN` | Change file ownership |
| `CAP_KILL` | Send signals to any process |

In our dockerfile, we also run our container as a non-root unprivileged user. </br>

### Secrets (environment)

We set our secrets in environment variables and inject those into the container :

```shell
export OPENCLAW_GATEWAY_TOKEN="$(openssl rand -hex 32)"
export GEMINI_API_KEY="xxxxxxx"
export DISCORD_BOT_TOKEN="xxxxxxx"
export GITHUB_TOKEN="xxxxxxx"
```

Run our container:

```shell
mkdir ~/.openclaw
cp ai/agents/openclaw/openclaw.json  ~/.openclaw/
docker run -it --rm \
  --name openclaw \
  -e OPENCLAW_GATEWAY_TOKEN=${OPENCLAW_GATEWAY_TOKEN} \
  -e GEMINI_API_KEY=${GEMINI_API_KEY} \
  -e DISCORD_BOT_TOKEN=${DISCORD_BOT_TOKEN} \
  -e GITHUB_TOKEN=${GITHUB_TOKEN} \
  --cap-drop ALL \
  -v $PWD:/work \
  -v ~/.openclaw:/home/openclaw/.openclaw \
  -w /work \
  --net host \
  -v /var/run/docker.sock:/var/run/docker.sock \
  aimvector/openclaw
```

### Configration

If we want to generate a new `openclaw.json`, we can run the onboarding process by overriding the entrypoint to bash </br>
We can tweak our `docker run` to add `--entrypoint bash` to get into the container and run the `openclaw onboard` process to get a configuration. </br>

We can run the onboard manually to generate an `openclaw.json` configuration file:

```shell
openclaw onboard --classic
```

## Kubernetes

View docker setup scripts:
```shell
tree /tmp/openclaw/scripts/k8s/
/tmp/openclaw/scripts/k8s/
├── create-kind.sh
├── deploy.sh
└── manifests
    ├── configmap.yaml
    ├── deployment.yaml
    ├── kustomization.yaml
    ├── pvc.yaml
    └── service.yaml

1 directories, 7 files
```

Lets create a Kubernetes cluster to play with using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/)

```
kind create cluster --name openclaw --image kindest/node:v1.36.1
```

Test our cluster and makes sure `kubectl` is configured for it:

```
kubectl get nodes
NAME                     STATUS   ROLES           AGE     VERSION
openclaw-control-plane   Ready    control-plane   2m30s   v1.36.1
```

View kubernetes deployment scripts:

```shell
tree /tmp/openclaw/scripts/k8s/

/tmp/openclaw/scripts/k8s/
├── create-kind.sh
├── deploy.sh
└── manifests
    ├── configmap.yaml
    ├── deployment.yaml
    ├── kustomization.yaml
    ├── pvc.yaml
    └── service.yaml

2 directories, 7 files
```

I've taken a copy of these manifests by using `cp`. </br>

```shell
cp /tmp/openclaw/scripts/k8s/manifests/* ai/agents/openclaw/kubernetes/
```

You can now use Helm, Kuztomize, ArgoCD, Flux or any mechanism of your choice to manage this deployment. </br>

## Deploy Secret

We can refer to the `deployment.yaml` and we see the secret references under `secretKeyRef` </br>

We will need a secret for our Gateway UI, a secret for our model provider as well as our channel. </br>

```shell
export OPENCLAW_GATEWAY_TOKEN="$(openssl rand -hex 32)"
export GEMINI_API_KEY="xxxxxxx"
export DISCORD_BOT_TOKEN="xxxxxxx"
```

I'll be modifying the `deployment.yaml` to add my channel token `DISCORD_BOT_TOKEN`. </br>
Note that we can reference this ENV variable in our `openclaw.json` configuration. </br>

```yaml
- name: DISCORD_BOT_TOKEN
  valueFrom:
    secretKeyRef:
      name: openclaw-secrets
      key: DISCORD_BOT_TOKEN
      optional: false
```

Create the secret:

```shell
kubectl create ns openclaw
kubectl create secret generic openclaw-secrets -n openclaw \
  --from-literal OPENCLAW_GATEWAY_TOKEN=${OPENCLAW_GATEWAY_TOKEN} \
  --from-literal GEMINI_API_KEY=${GEMINI_API_KEY} \
  --from-literal DISCORD_BOT_TOKEN=${DISCORD_BOT_TOKEN}

```

## Configure OpenClaw

We've configured our openclaw instance in our [Configmap.yaml](./kubernetes/configmap.yaml)

## Deploy Openclaw

Apply the manifests

```shell
kubectl -n openclaw apply -f ai/agents/openclaw/kubernetes/
```

## Install Plugins 

```shell
export NPM_CONFIG_CACHE=~/.openclaw/.npm
openclaw plugins install @openclaw/discord
kubectl -n openclaw delete pods --all
```

---

### Overview (`ai/kubernetes/llm-kube/README.md`)

# Introduction to LLMKube

<a href="https://youtu.be/xdMtc8jm88Q" title="llmkube"><img src="https://i.ytimg.com/vi/xdMtc8jm88Q/hqdefault.jpg" width="40%" alt="video" /></a>

Website: [LLMKube](https://llmkube.com) </br>

## What is LLMKube?

LLMKube is a Kubernetes operator for self-hosted LLM inference. </br>
Source Code: [defilantech/LLMKube](https://github.com/defilantech/LLMKube) </br>
Built on [llama.cpp](https://github.com/ggml-org/llama.cpp). </br>

## We need a Kubernetes cluster

Lets create a Kubernetes cluster to play with using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/)

```
kind create cluster --name llmkube --image kindest/node:v1.34.3
```

Test our cluster and makes sure `kubectl` is configured for it:

```
kubectl get nodes
NAME                       STATUS   ROLES           AGE   VERSION
llmkube-control-plane   Ready    control-plane   40s   v1.34.3
```

## Installing LLMKube with Helm

```shell
helm repo add llmkube https://defilantech.github.io/LLMKube
helm repo update

helm search repo llmkube --versions
CHART_VERSION="0.7.5"

helm install llmkube llmkube/llmkube \
  --namespace llmkube-system \
  --version ${CHART_VERSION} \
  --create-namespace
```

Check installation:

```shell
kubectl get pods -n llmkube-system
```

Two new CRDs `Model` and `InferenceService` are now available in the cluster: 

```shell
kubectl get crds | grep llmkube
inferenceservices.inference.llmkube.dev
models.inference.llmkube.dev
```

## The CRDs

### Model

```yaml

# Deploy the Model
kubectl apply -f ai/kubernetes/llm-kube/model.yaml

# Check the Model
kubectl get model
kubectl describe model

```

### InferenceService

```yaml
# Deploy the InferenceService
kubectl apply -f ai/kubernetes/llm-kube/inference.yaml

# Check the InferenceService
kubectl get inferenceservices
kubectl describe inferenceservices
```

## Testing the Inference API

Port-forward the service to reach it from your machine:

```shell
kubectl port-forward svc/gemma-e2b-service 8080:8080
```

Send a test request to the OpenAI-compatible endpoint:

```shell
curl -X POST http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gemma-e2b",
    "messages": [
      {"role": "user", "content": "What is Kubernetes in one sentence?"}
    ],
    "max_tokens": 80
  }'
```

## OpenAI-Compatible Integration

The example below connects [Opencode](https://opencode.ai) to the local service

```json
{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "llmkube": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "LLMKube (local)",
      "options": {
        "baseURL": "http://localhost:8080/v1"
      },
      "models": {
        "gemma-e2b": {
          "name": "Gemma 4 E2B (kind)",
          "limit": {
            "context": 32768,
            "output": 4096
          }
        }
      }
    }
  }
}
```

---

### Overview (`ai/models/llama-cpp/README.md`)

# Introduction to llama.cpp

<a href="https://youtu.be/ZR9S9zXm4ZU" title="llamacpp"><img src="https://i.ytimg.com/vi/ZR9S9zXm4ZU/hqdefault.jpg" width="40%" alt="llamacpp" /></a>

In this guide, we will learn how to run large language models locally using llama.cpp </br>

We will cover:
* What is llama.cpp
* What is quantization
* How to find and run models
* How to host a local model server 
* Interact with models using CLI like OpenCode.

## What is llama.cpp

[llama.cpp](https://llama-cpp.com/) is a lightweight, high-performance implementation designed to run large language models locally on your own machine. </br>
It has no external dependencies and no Python — just pure C++. </br>

Key highlights:

* Runs entirely on your own hardware — no cloud, no API keys required
* Supports CPU, NVIDIA GPU (CUDA), AMD GPU (ROCm), Intel GPU (SYCL), and Vulkan
* Natively supports quantized models (Q2 through Q8) for memory efficiency
* Ships with a CLI (`llama-cli`) and a local HTTP server (`llama-server`)

## Quantization

* AI models are massive
  * a 7B parameter model at full precision (F32) needs ~28GB of RAM
  * a 31B parameter model needs a really good top of the range GPU like a 4090 
* Quantization compresses the model's numbers (e.g. 32-bit down to 4-bit), cutting memory by up to 8x with minimal quality loss
* A 7B model can run in as little as 4GB of RAM on a laptop CPU
* llama.cpp natively supports Q2 through Q8, letting you tune the speed, memory, and quality trade-off to your hardware

Quantization levels:

| Quant | Size | Quality | Use when |
|---|---|---|---|
| `Q2_K` | Smallest | Lowest | Very limited RAM |
| `Q3_K_M` | Very small | Low | Tight on RAM |
| `Q4_K_M` | Small | Good | **Best balance — most popular** |
| `Q5_K_M` | Medium | Better | Have spare RAM |
| `Q6_K` | Large | Very good | Plenty of RAM |
| `Q8_0` | Largest | Near lossless | Maximum quality |
| `F16` | Huge | Lossless | Rarely practical |

## Where to Get Models

[Hugging Face](https://huggingface.co/models) is the primary hub for AI models — think of it as the Docker Hub of models. </br>

We can start with [Gemma 4](https://huggingface.co/collections/ggml-org/gemma-4) </br>

## Source Code

llama.cpp is fully open source and available on [GitHub](https://github.com/ggml-org/llama.cpp). </br>
Pre-built binary releases for all platforms are available at [github.com/ggml-org/llama.cpp/releases](https://github.com/ggml-org/llama.cpp/releases).

## Docker Images

The easiest way to get started is via the official Docker images. </br>
[Official Docker Guide](https://github.com/ggml-org/llama.cpp/blob/master/docs/docker.md) </br>

Three image types are available:

* `light`: includes CLI and completion tools
* `full`: includes light + tools for model conversion (safetensors → GGUF)
* `server`: only the `llama-server` executable

### Choosing the right image

| Image | Backend | Vendor |
|---|---|---|
| `ghcr.io/ggml-org/llama.cpp:full` | CPU | Any |
| `ghcr.io/ggml-org/llama.cpp:full-cuda` | CUDA 12 + cuBLAS | **NVIDIA** |
| `ghcr.io/ggml-org/llama.cpp:full-cuda13` | CUDA 13 + cuBLAS | **NVIDIA** |
| `ghcr.io/ggml-org/llama.cpp:full-rocm` | ROCm + hipBLAS | **AMD** |
| `ghcr.io/ggml-org/llama.cpp:full-intel` | SYCL | **Intel** Arc/Xe |
| `ghcr.io/ggml-org/llama.cpp:full-vulkan` | Vulkan | Cross-vendor |
| `ghcr.io/ggml-org/llama.cpp:full-openvino` | OpenVINO | **Intel** CPU/iGPU/NPU |

## Running llama.cpp

Example 1: Linux (CPU or integrated graphics): </br>
Using `--device /dev/dri` for integrated Graphics

```shell
docker run -it \
  -v ~/models:/models \
  --device /dev/dri \
  -p 8080:8080 \
  --entrypoint bash ghcr.io/ggml-org/llama.cpp:full-intel
```

Example 2:

WSL with NVIDIA GPU:

```shell
docker run -it \
  -v ~/models:/models \
  --gpus all \
  -p 8080:8080 \
  --entrypoint bash ghcr.io/ggml-org/llama.cpp:full-cuda
```

Once inside the container, verify the CLI works with `./llama-cli --help`

## Running a Model

### Download and run directly from Hugging Face

The `-hf` flag lets you pull a model directly from Hugging Face by repo and quant:

```shell
./llama-cli -hf ggml-org/gemma-4-E4B-it-GGUF:Q4_K_M
```

<b>Note:</b> This downloads and caches the model under `~/.cache/huggingface/` inside the container, not your `/models` mount. To persist models across container restarts, download them manually to `/models` first. </br>

### Run from a local GGUF file

```shell
./llama-cli -m /models/gemma-4-E4B-it-Q4_K_M.gguf -ngl 99
```

Use `-ngl 0` to disable GPU offload and run on CPU only:

```shell
./llama-cli -m /models/gemma-4-E4B-it-Q4_K_M.gguf -ngl 0
```

## Hosting a Model

To expose your model as an HTTP API, use `llama-server` on port `8080`. </br>
Once running, you can access the built-in web UI and API at [http://localhost:8080](http://localhost:8080). </br>

Linux (CPU):

```shell
export LLAMA_ARG_HOST=0.0.0.0
./llama-server -m /models/gemma-4-E4B-it-Q4_K_M.gguf \
  --port 8080 \
  -ngl 0 \
  --jinja \
  -c 8192 \
  --parallel 1 \
  --temperature 1.0 \
  --top-p 0.95 \
  --top-k 64
```

WSL with NVIDIA GPU:

```shell
export LLAMA_ARG_HOST=0.0.0.0

# gemma-4-31B-it-Q4_K_M.gguf
# gemma-4-26B-A4B-it-Q4_K_M.gguf
# gemma-4-E2B-it-Q8_0.gguf
# gemma-4-E4B-it-Q4_K_M.gguf

./llama-server -m /models/gemma-4-E2B-it-Q8_0.gguf \
  --port 8080 \
  -ngl 99 \
  --jinja \
  -c 131072 \
  --parallel 1 \
  --temperature 1.0 \
  --top-p  0.95 \
  --top-k 64
```

Key flags:

| Flag | Purpose |
|---|---|
| `-ngl 99` | Offload all layers to GPU (llama.cpp uses as many as fit in VRAM) |
| `-c 65536` | Context window size in tokens |
| `--parallel 1` | 1 concurrent request slot — correct for single-user use |
| `--jinja` | Enables Jinja2 chat templates for proper instruction-tuned model formatting |
| `--temperature` | Default sampling temperature (0.0 = deterministic, 1.0 = creative) |

## Model Interaction via OpenCode

You can interact with your locally hosted model using [OpenCode](https://opencode.ai/docs/providers/#llamacpp). </br>
OpenCode connects to the `llama-server` endpoint over its OpenAI-compatible API. </br>

Configure `~/.opencode/opencode.json`:

```json
{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "llama.cpp": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "llama-server (local)",
      "options": {
        "baseURL": "http://localhost:8080/v1"
      },
      "models": {
        "gemma-4-26B-A4B-it-Q4_K_M.gguf": {
          "name": "gemma-4-26B-A4B-it-Q4_K_M (local)",
          "limit": {
            "context": 65536,
            "output": 16384
          }
        }
      }
    }
  }
}
```

---

### Overview (`ai/openai/introduction/README.md`)

# Introduction to Open AI

## Overview 

What is [Open AI](https://openai.com/) ? 

* Research company on AI development
* Builds and provides models
* Builds and provides a standard protocol for using AI

What is a model ?

I see a model as a language super database. </br>
Instead of writing a query, that is slow to query a traditional database like SQL, you can throw a question at a model and it gives you an answer really fast </br>

Model examples:
* GPT 3.5 
* GPT 4

## Getting started 

The best way to get started and to understand OpenAI, is to learn hands on

* Create an OpenAI account [here](https://openai.com/) 

## Chat GPT 

Here you can find the link to [ChatGPT](https://chat.openai.com/)

## Open AI Playground 

Here you can find the link to the [OpenAI Playground](https://platform.openai.com/playground)

## Build an AI powered app

We can start with a `main.py` that reads a message

```
import sys

message = sys.argv[0]

```
Then we will need the code from the Open AI playground and add it to our `main.py`. </br>
Move the `import` statements to the top </br>

Once you have tidied up everything, you can get the response message from the AI:

```
responseMessage = response.choices[0].message.content
```

Let's build our app

```
cd ai\openai\introduction
docker build . -t ai-app
```

Set my OpenAI API key

```
$ENV:OPENAI_API_KEY=""
```

Run our AI App:

```
docker run -it -e OPENAI_API_KEY=$ENV:OPENAI_API_KEY ai-app
```

When we run the app, notice it has no concept of memory. </br>
The playground works because it keeps track of all the user and AI messages and keeps appending new messages to it </br>
So it can track the conversation.

Let's keep track of messages, by writing it to a local file </br>
We will also take the system message out and keep it as a constant in our code </br>

Full example:

```
import sys
import os
import json
import openai

openai.api_key = os.getenv("OPENAI_API_KEY")

#read the incoming message
message = sys.argv[1]
user_message = {
  "role" : "user",
  "content" : message
}

systemMessage = {
  "role": "system",
  "content": "You are a kubernetes exper that can assist developers with troubleshooting deployments\n\nTo help the developer you will need to know the namespaces as well as the pod name. Ask for missing information\n\nGenerate a command to help the developer surface logs or information\n"
}

# read the cached user messages if there are any
userMessages = []
if os.path.isfile("messages.json"):
  with open('messages.json', newline='') as messagesFile:
    data = messagesFile.read()
    userMessages = json.loads(data)

# add the new message to it and update the cached messages
userMessages.append(user_message)
with open('messages.json', 'w', newline='') as messagesFile:
  msgJSON = json.dumps(userMessages)
  messagesFile.write(msgJSON)
  print(msgJSON)

messages = []
messages.append(systemMessage)
messages.extend(userMessages)

response = openai.ChatCompletion.create(
  model="gpt-3.5-turbo",
  messages=messages,
  temperature=1,
  max_tokens=256,
  top_p=1,
  frequency_penalty=0,
  presence_penalty=0
)

responseMessage = response.choices[0].message.content
print(responseMessage)

```

Now we can mount our volume so we persist the cache of messages 

```
docker run -it -e OPENAI_API_KEY=$ENV:OPENAI_API_KEY -v ${PWD}:/app ai-app "can you help me with my deployment?"
Of course! I'd be happy to help with your deployment. Could you please provide me with the namespace and the name of the pod you're encountering issues with?

docker run -it -e OPENAI_API_KEY=$ENV:OPENAI_API_KEY -v ${PWD}:/app ai-app "my pod is pod-123" 
Sure, I can help you with your deployment. Can you please provide me with the namespace in which the pod is running?

docker run -it -e OPENAI_API_KEY=$ENV:OPENAI_API_KEY -v ${PWD}:/app ai-app "its in the products namespace"
Great! To surface the logs for the pod "pod-123" in the "products" namespace, you can use the following command:

```shell
kubectl logs -n products pod-123
```

This command will retrieve the logs for the specified pod in the given namespace. Make sure you have the necessary permissions to access the namespace.
```

---

### Overview (`automation/cicd/octopus-deploy/README.md`)

# Introduction to Octopus Deploy

<a href="https://youtu.be/xxxxx" title="octo-intro"><img src="https://i.ytimg.com/vi/xxxxx/hqdefault.jpg" width="20%" alt="octo-intro" /></a> 

Start here 👉🏽[https://octopus.com/](https://octopus.com/) </br>
Documentation 👉🏽[https://octopus.com/docs](https://octopus.com/docs)

In this video, I sign up with the Free version of Octopus deploy to get my license key. </br>

## Create Kubernetes clusters

In my video I use `kind` to act as my DEV and PROD environment where I run microservices </br>
Let's create that:
```shell
kind create cluster --name dev --image kindest/node:v1.34.0
kind create cluster --name prod --image kindest/node:v1.34.0
```

## Octopus Components & Installation

In this video, I will explore the architecture of Octopus and the required components, so I'll be using the self-hosted option for learning. </br>

I highly recommend you checkout the Cloud version. </br>

My settings:
```
cd automation/cicd/octopus-deploy

export MASTER_KEY=$(openssl rand -base64 32)
export OCTOPUS_SERVER_BASE64_LICENSE=xxxxx
export ADMIN_USERNAME="octo-admin"
export ADMIN_PASSWORD="octo-admin123"
export ADMIN_EMAIL="octo-admin@admin.com"
```

Spin up a database for Octopus to use:

```
docker run -d --name octopus-database \
--net kind \
-u root \
-e ACCEPT_EULA="Y" \
-e MSSQL_SA_PASSWORD="OurStrongPassword123" \
-v ${PWD}/.data/mssql/:/var/opt/mssql/data \
mcr.microsoft.com/mssql/server:2022-latest
```

Create our first Octopus server:

[notes: ports to expose](https://octopus.com/docs/installation/octopus-server-linux-container#exposed-container-ports)
```
docker run -d \
--name octopus \
--net kind \
-p 8080:8080 \
-p 8443:8443 \
-p 10943:10943 \
-e ACCEPT_EULA="Y" \
-e ADMIN_USERNAME=$ADMIN_USERNAME \
-e ADMIN_PASSWORD=$ADMIN_PASSWORD \
-e ADMIN_EMAIL=$ADMIN_EMAIL \
-e OCTOPUS_SERVER_BASE64_LICENSE=$OCTOPUS_SERVER_BASE64_LICENSE \
-e MASTER_KEY=$MASTER_KEY \
-e DB_CONNECTION_STRING="Server=octopus-database,1433;Database=OctopusDeploy;User=sa;Password=OurStrongPassword123" \
-v ${PWD}/.data/octopus/repository/:/repository \
-v ${PWD}/.data/octopus/artifacts/:/artifacts \
-v ${PWD}/.data/octopus/taskLogs/:/taskLogs \
-v ${PWD}/.data/octopus/cache/:/cache \
-v ${PWD}/.data/octopus/import/:/import \
-v ${PWD}/.data/octopus/eventExports/:/eventExports \
octopusdeploy/octopusdeploy:2025.3

```

## Access Octopus Dashboard

Let's get the IP of my machine so we can access Octopus:

```
ifconfig eth0 | grep 'inet' | awk '{print $2}' | cut -d: -f2
```

We can access the dashboard on `http://<IP>:8080`

## Setup Environments 

The Octopus UI helps us setup and configure environments. </br>
In this video, we add two Kubernetes environments, `k8s-dev` and `k8s-prod` </br>

We'll setup targets that will allow us to deploy to each environment. 

## Setup Targets

The Environment page allows us to configure deployment targets. </br>
The Octopus UI guides us to install the Kubernetes Agent using Helm. </br>
In this video, we will add targets for each Kubernetes environment so we can target those environments for deployment. </br>

## Script Modules

Octopus allows us to create script modules that we can use during deployments. 
Instead of placing raw scripts into deployment steps, we can create re-usable modules

Let's create a simple module called `create_namespace` that creates a kubernetes namespace: 

```bash
create_namespace() {
NAMESPACE_NAME=$1

# Check if the namespace exists.
# The 'get namespace' command will fail if the namespace is not found.
# The '>/dev/null 2>&1' part suppresses all output (success or failure) 
# so the script remains silent unless a new namespace is created.

kubectl get namespace $NAMESPACE_NAME >/dev/null 2>&1

# $? is the exit status of the last command (0 for success, non-zero for failure)
if [ $? -ne 0 ]; then
    echo "Namespace '$NAMESPACE_NAME' not found. Creating it now..."
    kubectl create namespace $NAMESPACE_NAME
    echo "Namespace '$NAMESPACE_NAME' created successfully."
else
    echo "Namespace '$NAMESPACE_NAME' already exists. Skipping creation."
fi
}

```

Now we'll be able to source that later in a deployment step. </br>

## Setup Project 

We'll setup a project to deploy our service. A project holds the process & steps as well as variables and features that allow us to deploy our software. </br>

## Setup Deployment Steps

We can now use a Script step template to run our script module 

```bash
source create_namespace.sh
create_namespace $(get_octopusvariable "NAMESPACE")
```

## Setup Variables

Variables allow us to configure almost anything about our steps, and scripts. They can be config values or even secrets. </br>
In this video we'll add a `NAMESPACE` variable for our `product` namespace. </br>

## Setup Kubernetes Step

Let's add a new step to "Deploy Kubernetes YAML"
We can refernce YAML in many ways, in this video we use Github:
```
https://github.com/marcel-dempers/docker-development-youtube-series.git
```

We also specify all our manifests path that octopus needs to deploy:

```
automation/cicd/octopus-deploy/example-application/deployment.yaml
automation/cicd/octopus-deploy/example-application/service.yaml
automation/cicd/octopus-deploy/example-application/ingress.yaml
```

### Variable injection

Octopus allows us to manipulate files, and flows and achieve different outcomes in many ways. One powerful feature is injecting values into files. </br>

We can use this for configuration value injection, secret injection etc. </br>

Let's update contents in our `configmap.yaml` based on our environment.
We can inject values into our JSON block based on the environment we are deploying too.

spec:rules:0:host

```
spec:
  ingressClassName: nginx
  rules:
  - host: marcel.local
```

## Create a step template

In this video, we also create a new step template which allows us to write modular templates that can be re-used throughout Octopus.
Since may microservices may use `ConfigMap` objects, we can create a step template for it </br>

Our step template name: `Deploy ConfigMap to Kubernetes` </br>
Parameters:
* `config_name` of the ConfigMap to create
* `config_namespace` name of the Namespace to deploy the ConfigMap to
* `config_body` the contents of the ConfigMap as Multi-line text

Inline Source Code:

```bash
NAMESPACE=$(get_octopusvariable "config_namespace")
CONFIGMAP_NAME=$(get_octopusvariable "config_name")
BODY_JSON=$(get_octopusvariable "config_body")

CONFIG_KEY=config.json
# Check if arguments are provided
if [ -z "$NAMESPACE" ] || [ -z "$CONFIGMAP_NAME" ] || [ -z "$BODY_JSON" ]; then
    echo "Error: Missing one or more required arguments."
    usage
fi

kubectl create configmap "${CONFIGMAP_NAME}" --namespace "${NAMESPACE}" \
  --from-literal="${CONFIG_KEY}=${BODY_JSON}" \
  --dry-run=client -o yaml | kubectl apply -f -
```

We can then proceed to add this step to our deployment process. </br>

We can do the same and create a Secret deployment template:

Our step template name: `Deploy Secret to Kubernetes` </br>
Parameters:
* `secret_name` of the Secret to create
* `secret_namespace` name of the Namespace to deploy the Secret to
* `secret_body` the contents of the Secret as Multi-line text

```bash
NAMESPACE=$(get_octopusvariable "secret_namespace")
SECRET_NAME=$(get_octopusvariable "secret_name")
BODY_JSON=$(get_octopusvariable "secret_body")

SECRET_KEY="secret.json"

# Check if arguments are provided
if [ -z "$NAMESPACE" ] || [ -z "$SECRET_NAME" ] || [ -z "$BODY_JSON" ]; then
    echo "Error: Missing one or more required arguments."
    usage
fi

ENCODED_BODY=$(echo -n "$BODY_JSON" | base64)

kubectl create secret generic "${SECRET_NAME}" --namespace "${NAMESPACE}" \
  --from-literal="${SECRET_KEY}=${ENCODED_BODY}" \
  --dry-run=client -o yaml | kubectl apply -f -

```

---

### Overview (`course/chapters/chapter-1-source-control-git/README.md`)

# Chapter 1 : Source Control and GIT

Back to [Course Introduction](../../README.md)

<h3>Introduction</h3>

<p>In this chapter we will cover the fundamental tools you need to know to start a DevOps, SRE or platform engineering career from the beginning.</p>
<p> This starts with knowing how to work with files. 
  Source control allows us to work with files, collaborate with others and track changes and histrory of source code.
</p>

<p>We will also take a look at popular tools that software engineers use to work with files.</p> 
<p>What your will learn:</p>

<ul>
  <li> What source control is</li>
  <li> How to use GIT to manage source code</li>
  <li> Learn about the basics of hosted source control systems</li>
  <li> What an IDE is</li>
  <li> Developer Tools</li>
  <li> How to use an IDE as an engineer</li>
</ul>

### 🎬 [Module 1: Introduction to GIT: Source Control](../../content/source-control/git/README.md)

<p>
<a href="https://en.wikipedia.org/wiki/Git">GIT</a> is the foundation of DevOps, Programming & Software Engineering </br> 
This is because it is a source code distributed version control system. </br>
In DevOps, you will deal with code, command line, documentation, scripts, automation, infrastructure-as-code and more. </br>
Generally, all of this involves writing files and storing them.
GIT is the system you use to store these files. </br>
</p>

<p>
In this module we'll take a read through <a href="https://en.wikipedia.org/wiki/Git">GIT</a> to understand where it started and why it is so successful and widely used in software engineering. </br>
</p>

<h4>In this module</h4>
<ul>
  <li>What is GIT</li>
  <li>Installing GIT</li>
  <li>Command line</li>
  <li>Create your first GIT repository</li>
  <li>Branching</li>
  <li>Commits</li>
  <li>Merging branches</li>
  <li>GUI Tools for GIT</li>
  <li>VSCode</li>
</ul>

### 🎬 [Module 2: Introduction to IDEs: Visual Studio Code](../../content/editors/vscode/introduction/README.md)

<p>
When working on source code using GIT, you will want to work on files and directories. </br>
This includes configuration files, code files, scripts and so on. </br>
To get a nice overview of all our files and folders, as well as have access to GIT and command line, we'll use an IDE. </br>
An <a href="https://en.wikipedia.org/wiki/Integrated_development_environment">IDE</a> is an integrated development environment. </br>
It is software that helps us deal with software development and engineering. </br>
</p>

<h4>In this module:</h4>
<ul>
  <li>What is VSCode</li>
  <li>Directory and File management</li>
  <li>Editing files</li>
  <li>Command line terminals</li>
  <li>Extensions</li>
  <li>Themes</li>
  <li>GIT features</li>
</ul>

### 🎬 [Module 3: Introduction to Github: Hosted Source Control](../../content/source-control/github/README.md)

<p>
Storing source code in a Git repository on a local machine carries a risk of data loss. </br>
If your harddrive crashes, or you lose your laptop, all your work would be lost. </br>
Therefore it's always recommended to use a hosted source control provider like Github, BitBucket, Gitlab and others. </br>
</p>
<p>
It also makes collaboration possible, when you have a team of people working on the same project </br>
</p>

<h4>In this module:</h4>
<ul>
  <li>We create a Github account</li>
  <li>Overview of Github source control</li>
  <li>Integrating our repository with Github</li>
  <li>Working with our repository</li>
</ul>

---

### Overview (`course/chapters/chapter-2-operating-systems/README.md`)

# Chapter 2 : Operating Systems & Virtualization

Back to [Course Introduction](../../README.md#-table-of-contents)


<h3>Introduction</h3>

<p>In this Chapter, we will cover the fundamentals around servers and what role they play in I.T. Servers make up the entire cloud and it's what's responsible for all compute.</p>

<p>We'll cover the different types of servers, technologies used to create and provision servers, as well as the skills and basic fundamental knowledge required to manage servers.</p>

<p>This includes the Operating system running on the server, the role of the Kernel and the different type of operating system resources that we will need to know about.</p>

<p>What your will learn:</p>

<ul>
  <li> What servers are</li>
  <li> Server technology & Virtualization</li>
  <li> Operating System functions & resources</li>
  <li> Creating our first virtual server</li>
  <li> Introduction to Linux</li>
  <li> Introduction to Command-line</li>
  <li> Introduction to scripting </li>
</u>

### 🎬 [Module 1: Introduction Operating Systems for DevOps](../../content/operating-systems/introduction/README.md)

<h3> In this module </h3>
<ul>
<li> What is an Operating system </li>
<li> Why Operating Systems are important for DevOps </li>
<li> Operating System Key Functions  </li>
<li> Operating systems resources  </li>
  <ul>
    <li> user management  </li>
    <li> permission management  </li>
    <li> hardware management & devices </li>
    <li> resource management </li>
      <ul>
        <li> CPU</li>
        <li> Memory</li>
        <li> Disk</li>
      </ul>
  </ul>
<li> The Kernel </li>
<li> Terminal & Command Line </li>
<ul>

### 🎬 [Module 2: Introduction to Servers & Virtualisation](../../content/operating-systems/virtualization/README.md)

<h3> In this module </h3>
<ul>
  <li> What are servers </li>
  <li> Introduction to Virtualisation </li>
  <li> Virtualisation software </li>
  <li> Creating our first server </li>
</ul>

### 🎬 [Module 3: Introduction to Linux for beginners](../../content/operating-systems/linux/introduction/README.md)

<h3> In this module </h3>
<ul>
  <li> What is Linux </li>
  <li> Installation and setup of a linux server </li>
  <li> Accessing servers </li>
  <li> Command line basics </li>
  <li> User accounts  </li>
  <li> File management </li>
</ul>

### 🎬 [Module 4: Understanding the command line](../../content/operating-systems/commandline/introduction/README.md)

<h3> In this module </h3>
<ul>
  <li> Benefits of using command line </li>
  <li> Why Command line is important for DevOps </li>
  <li> Command line structure </li>
  <li> Command line examples </li>
  <li> Navigating command usage </li>
  <li> Command line tools </li>
</ul>

### 🎬 [Module 5: Bash scripting for beginners](../../content/operating-systems/scripting/bash/README.md)

<h3> In this module </h3>
<ul>
  <li> Benefits of writing bash scripts </li>
  <li> Basics of Automation</li>
  <li> Introduction to bash scripting</li>
  <li> Pro's and Con's of scripting</li>
  <li> Write our first bash script</li>
</ul>

---

### Overview (`course/chapters/chapter-3-linux-monitoring/README.md`)

# Chapter 3 : Linux Monitoring

Back to [Course Introduction](../../README.md)

<p>
  This the previous chapters, we have spent quite a bit of time understanding operating systems and command line and why it's so important.  </br>
  Modern cloud and IT tends to hide or abstract a lot of how servers and operating systems function as well as how they are monitored. </br>
  This means engineers often lack the knowledge and expertise on how to dismantle a system and monitor it with bare essential command line tools. </br>
</p>

<p>
  In this chapter we'll take a look at monitoring concepts and introduce a few more command line tools that help us monitor our server. </br>
</p>

### 🎬 [Module 1: Introduction to Linux monitoring](../../content/operating-systems/linux/monitoring/README.md)

<h3> In this module </h3>

<ul>
  <li> Importance of Monitoring </li>
  <li> Monitoring vs Observability </li>
  <li> Resources to monitor: CPU, Memory, Network, Disk </li>
  <li> Monitoring Tools Overview </li>
  <li> Basic Monitoring Commands </li>
  <li> Logs vs Metrics vs Traces & tooling </li>
</ul>

### 🎬 [Module 2: Linux CPU Monitoring](../../content/operating-systems//linux/monitoring/cpu/README.md)

<h3> In this module </h3>
<ul>
  <li> Understanding CPU Metrics </li>
  <li> Tools for CPU Monitoring (e.g., top, htop, mpstat) </li>
  <li> Analyzing CPU Usage </li>
  <li> Practical Examples </li>
</ul>

### 🎬 [Module 3: Linux Memory Monitoring](../../content/operating-systems//linux/monitoring/memory/README.md)

<h3> In this module </h3>
  <li> Understanding Memory Metrics </li>
  <li> Tools for Memory Monitoring (e.g., free, vmstat) </li>
  <li> Analyzing Memory Usage </li>
  <li> Practical Examples </li>
</ul>

### 🎬 [Module 4: Linux Disk Usage Monitoring](../../content/operating-systems//linux/monitoring/disk/README.md)

<h3> In this module </h3>
<ul>
  <li> Understanding Disk Metrics </li>
  <li> Tools for Disk Monitoring (e.g., df, du, iostat) </li>
  <li> Analyzing Disk Usage </li>
  <li> Practical Examples </li>
</ul>

### 🎬 [Module 5: Linux Network Monitoring](../../content/operating-systems//linux/monitoring/network/README.md)

<h3> In this module </h3>
<ul>
  <li> Understanding Network Metrics </li>
  <li> Tools for Network Monitoring (e.g., iftop, netstat, ss) </li>
  <li> Analyzing Network Usage </li>
  <li> Practical Examples </li>
</ul>

### ⌛ Module 6: Introduction to Prometheus

<h3> In this module </h3>
<ul>
  <li>  What is Prometheus </li>
  <li> Concepts of monitoring </li>
  <li> Logs vs Metrics vs Traces  </li>
  <li> Pull vs Push approach </li>
  <li> Installation </li>
  <li> Monitor our Linux server </li>
  <li> Grafana Preview (next module) </li>
</ul>

### ⌛ Module 7: Introduction to Grafana

<h3> In this module </h3>
<ul>
  <li> What is Grafana </li>
  <li> Data sources and concepts  </li>
  <li> Installation & Setup </li>
  <li> Basic queries </li>
  <li> Installing node-exporter dashboard </li>
</ul>

---

### Overview (`course/chapters/chapter-4-web-and-http/README.md`)

# Chapter 4 : The Web & HTTP

Back to [Course Introduction](../../README.md)

<p>
  Web servers are an importand foundation of the IT ecosystem. </br>
  The internet is fundamentally made up of web servers. </br>
  In this chapter we'll learn about the basics of web servers and get our first web server up and running. </br>
</p>

### 🎬 [Module 1: Introduction to Web](../../content/web/README.md)

<h3> In this module </h3>
<ul>
  <li> What is the Web </li>
  <li> What is a web server </li>
  <li> Run a very basic web server </li>
  <li> Basics of Web </li>
    <ul>
      <li> Client & Server </li>
      <li> HTTP  </li>
      <li> DNS </li>
      <li> Status codes </li>
      <li> Request methods </li>
      <li> TCP\UDP and Ports </li>
      <li> Network testing tools </li>
    </ul>
</ul>

### 🎬 [Module 2: The Basics of HTTP](../../content/web/http/README.md)

<h3> In this module </h3>
<ul>
  <li> What is the HTTP protocol </li>
  <li> Roles of clients and servers </li>
  <li> Understanding requests and responses </li>
  <li> Interpreting responses & status codes </li>
</ul>

### 🎬 [Module 3: The Basics of HTML Websites and Web Services or APIs ](../../content/web/html/README.md)

<p>
  DevOps engineers are often tasked to troubleshoot and assist developers who may have Web applications deployed. </br>
  Perhaps there are network failures, resources may not be correctly loading or incorrectly deployed. </br>
</p>
<p>
  In this chapter we will learn about the basics of HTML and web assets, how they render and how to use developer tools to troubleshoot networking and other issues with Web applications.  It's a high level module designed to give you a glimpse in the shoes of a UI or front-end developer
</p>
<h3> In this module </h3>
<ul>
  <li> HTML files, syntax and usage </li>
  <li> Basics of Stylesheets & Scripts </li>
  <li> Troubleshooting HTML in Browsers </li>
  <li> Understanding different HTTP responses: API vs WEB </li>
  <li> Monitoring and Debugging </li>
</ul>

### 🎬 [Module 4: Creating web servers with NGINX](../../content/web/servers/README.md)

<h3> In this module </h3>
<ul>
  <li> Web Server overview </li>
  <li> Installing NGINX Web server </li>
  <li> Understand the NGINX configuration </li>
  <li> Setup a basic web site with a few pages </li>
  <li> Make our website public </li>
  <li> Understanding SSL\TLS security for Web servers </li>
</ul>

### 🎬[Module 5: HTTP Troubleshooting](../../content/web/http/troubleshooting/README.md)

<h3> In this module </h3>
<ul>
  <li> Troubleshooting client and servers  </li>
  <li> Understanding HTTP status codes and meaning of errors </li>
  <li> Interpret & Understand web server error and access logs </li>
  <li> Tools </li>
</ul>

### 🚧🎬 [Module 6: The Basics of DNS & Domains](../../content/web/dns/README.md)

<h3> In this module </h3>
<ul>
  <li> What are domains </li>
  <li> What are host names </li>
  <li> What is DNS </li>
  <li> DNS tooling </li>
  <li> Types of DNS </li>
  <li> Importance of TLS/SSL for secure communication   </li>
</ul>

### 🚧🎬 [Module 7: The Basics of SSL & TLS](../../content/web/tls/README.md)

<h3> In this module </h3>
<ul>
  <li> What is SSL & TLS </li>
  <li> HTTPS connections </li>
  <li> Importance of encryption on the Web </li>
  <li> TLS Tools </li>
  <li> Setting up TLS in Web servers for HTTPS </li>
</ul>

### ⏳(Coming Soon)🎬 Module 7: Creating our own personal website

<p>
  During this entire course we will touch on a wide spectrum of DevOps tooling, concepts and technology. </br>
  The outcome of this course is to have real-world hands-on experience with all these topics, all the way from setup, scripting, coding to building, CI/CD and deployment including monitoring and observability. </br>
</p>
<h3> In this module </h3>
<ul>
  <li> Put together a DevOps CV personal website using HTML, CSS and Javascript </li>
  <li> Understanding website deployment and changes </li>
</ul>

### ⏳(Coming Soon)🎬 Module 8: Get free SSL certificates

To make our website public on the web, we should introduce a domain and SSL certificate

<h3> In this module </h3>
<ul>
  <li> Setup domain for your website </li>
  <li> Understanding SSL\TLS </li>
  <li> Introduction to Let's encrypt </li>
</ul>

---

### Overview (`course/chapters/chapter-4.1-web-server-monitoring/README.md`)

# Chapter 4.1 : Web server: Monitoring

Back to [Course Introduction](../../README.md)

In our chapter on monitoring, we've covered some basics around dashboards and metrics. </br>
In this chapter we will expand on that to include application logs and metrics that are crucial in understanding our web server application health </br>

We'll cover the key concepts of log collection and visualisation of those logs using various open source tools </br>

### 🎬 Module 1: Introduction to Fluentd

#### In this module

* What is Fluentd 
* Benefits of using a log collector
* Benefits of sending logs to external systems
* Installation and Setup
* Capture using configuration
* Plugins to send logs externally  

### 🎬 Module 2: Monitoring Logs with Grafana Loki

#### In this module

*  What is Grafana Loki
* Log capture from external sources
* Get our application logs into Grafana Loki
* Visualise our web server logs in Grafana

---

### Overview (`course/chapters/chapter-5-ci-cd/README.md`)

# Chapter 5 : CI/CD & Automation

Back to [Course Introduction](../../README.md)

<p>
  In previous chapters we've setup our own web server and website.
</p>

### 🎬 [Module 1: Introduction to CI/CD](../../content/automation/cicd/introduction/README.md)

<h3> In this module </h3>

<ul>
  <li> What is Continuous Integration (CI) </li>
  <li> What is Continuous Delivery & Deployments (CD) </li>
  <li> CI/CD phases from A to Z </li>
  <li> Popular CI/CD Tools </li>
</ul>

### 🎬 [Module 2: Introduction to Automation](../../content/automation/introduction/README.md)

<h3> In this module </h3>
<ul>
  <li> What is automation </li>
  <li> Our automation journey so far </li>
  <li> Key Focusses of Automation: </li>
    <ul>
      <li> Scripting & Coding </li>
      <li> CI/CD: Automated build & deployments </li>
      <li> Infrastructure as Code </li>
      <li> Configuration Management </li>
      <li> GitOps </li>
    </ul>
</ul>

### 🎬 [Module 3: CI Example: Creating a CI pipeline with Github Actions](../../content/automation/cicd/ci/examples/github/README.md)

<h3> In this module </h3>
<ul>
  <li> Continuous integration overview </li>
  <li> Networking challenges & Security concerns </li>
  <li> Architecture review </li>
  <li> Scripting: Automate CI system install </li>
  <li> Github Actions workflow file overview </li>
  <li> Building and packaging software (artifacts) </li>
  <li> Create a CI pipeline using Github Actions  </li>
</ul>

### 🎬 [Module 4: CD Example: Creating a CD pipeline with Github Actions](../../content/automation/cicd/cd/examples/github/README.md)

<h3> In this module </h3>
<ul>
  <li> Continuous delivery\deployment overview </li>
  <li> Github Actions workflow file review </li>
  <li> Graceful and Rolling deployments </li>
  <li> Create CD steps in our CICD pipeline using Github Actions </li>
</ul>

### 🎬 [Module 5: Scripting Example: Automate our Infrastructure ](../../content/automation/scripting/examples/README.md)

<h3> In this module </h3>
<ul>
  <li> Scripting Requirements </li>
  <li> Scripting our Server Infrastructure </li>
  <li> Using the Command line </li>
  <li> Process of building a script: iteration and testing </li>
  <li> Challenges of Scripting </li>
</ul>

### 🎬 [Module 6: Declarative Tools Example: Automate our Infrastructure (Vagrant)](../../content/automation/config-management/examples/vagrant/README.md)

<h3> In this module </h3>
<ul>
  <li> Declarative vs Imperative Automation </li>
  <li> Desired State </li>
  <li> Infrastructure as Code </li>
  <li> Declarative IaC with Vagrant </li>
</ul>

### 🎬 [Module 7: Configuration Management Example: Automate our Infrastructure (Ansible)](../../content/automation/config-management/examples/ansible/README.md)

<h3> In this module </h3>
<ul>
  <li> What is Configuration Management </li>
  <li> Challenges with Scripting & Idempotence </li>
  <li> Declarative configuration </li>
  <li> Introduction to Ansible </li>
  <li> Migrating scripts to declarative configuration </li>
</ul>

---

### Overview (`course/chapters/chapter-6-docker/README.md`)

# Chapter 6 : Docker & Linux Containers

Back to [Course Introduction](../../README.md)


### 🎬 [Module 1: Introduction to Linux containers](../../content/containers/introduction/README.md)

<h3> In this module </h3>
<ul>
  <li> What are containers </li>
  <li> Processes vs Containers </li>
  <li> How processes work inside containers </li>
  <li> What problems do they solve </li>
  <li> Virtual Machines vs Containers </li>
</ul>

### 🎬 [Module 2: Running and Configuring Containers](../../content/containers/running/README.md)

<h3> In this module </h3>
<ul>
  <li> The Container runtime </li>
  <li> How containers & processes get inputs </li>
  <li> Popular container runtimes </li>
  <li> Installing a container runtime </li>
  <li> Running our first container </li>
</ul>

### 🎬 [Module 3: Building Container Images](../../content/containers/building/README.md)

<h3> In this module </h3>
<ul>
  <li> What are container images </li>
  <li> Filesystem layers </li>
  <li> Writing our first `Dockerfile` </li>
  <li> Common `Dockerfile` instructions </li>
  <li> Layer caching and optimizing builds </li>
  <li> Tagging and pushing images </li>
  <li> Running a custom built container </li>
</ul>

### 🎬 [Module 4: CI/CD Pipelines for Containers](../../content/containers/cicd/README.md)

<h3> In this module </h3>
<ul>
  <li> CI/CD fundamentals overview </li>
  <li> Learn about container lifecycle </li>
  <li> Building containers </li>
  <li> Publishing containers </li>
  <li> Deploying containers </li>
  <li> </li>
</ul>

### 🎬 [Module 5: Container Networking](../../content/containers/networking/README.md)

<h3> In this module </h3>
<ul>
  <li> Default container networks </li>
  <li> Creating custom networks </li>
  <li> Secure container communication </li>
  <li> Internal DNS and service discovery </li>
</ul>

### 🎬 [Module 6: Introduction to Container Orchestration](../../content/containers/orchestration/README.md)

<h3> In this module </h3>
<ul>
  <li> Limitations of standalone containers </li>
  <li> Declarative vs Imperative management </li>
  <li> Desired State </li>
  <li> Networking and Service Discovery challenges </li>
  <li> Overview of orchestration technologies </li>
</ul>

---

### Overview (`course/content/web/README.md`)

# 🎬 Introduction to the Web

## 💡 Preface

This module is part of a course on DevOps. </br>
Checkout the [course introduction](../../README.md) for more information </br>
This module is part of [chapter 4](../../chapters/chapter-4-web-and-http/README.md)

This module draws from my extensive experience in the world of Web. Building and managing Web servers, building web sites, web services, monitoring , security, networking etc.  

It focuses on my personal approach to understanding the Web as well as how I troubleshoot.

## What is the Web 

The Web is just a bunch of computers and devices that are connected to a network. This is one giant network made up of public and private networks </br>

In our module on servers and virtualization, we created a small private network and we learned that this network is connected to the internet via our gateway (which in my case, was my home router) </br>

In our module on Network monitoring, we covered all of this in a diagram. I will link it here for a recap </br>

The internet is technically private networks that are connected by public networks, similar to our diagram below



We'll need to ensure we understand the diagram, because the network components form the core architecture of the internet. </br>

### Understanding the big picture

This entire chapter will break down the following diagram and deep dive into each component. </br> It is critically important as an engineer to understand these fundamentals as it plays a role in building , testing , troubleshooting and working with almost every aspect of DevOps </br>
This diagram puts together all important aspects and components that play a part in Web and HTTP </br>



## What is HTTP

In our previous Chapters on servers and network monitoring, we covered the first diagram showcasing TCP connections between a client process and a server process. </br>
We learned that TCP is the lowest level and smallest form of network connection. </br> 
We learned that a TCP connection goes through various states and we even saw this in a hands-on practical using tools like `netstat` and `ss` to monitor network connections </br>
TCP connections allow clients and servers to transport network packets between each other. This is how "data exchange" happens. </br>

Now in the world of Web, we need a standard of how these network packets need to be structured, so that devices on the web can communicate effectively and securely </br>

Just like human languages have structures, rules and standards which allow us to communicate in ways we can all understand. </br>

When you type a website address into your browser, your browser creates a TCP packet in a "special format" that a web server can accept, understand and it can respond with requested content which your browser can then understand and display to you. </br>

This "special format" of a TCP packet is called "HTTP" </br>
So in a nutshell, an HTTP packet is just a TCP packet, but it has more information that has to conform to certain standards </br>

In order to create an HTTP packet, we can use scripts, tools or programming languages. But before we go there, lets understand more overall components

<i>Note: We will have an entire module dedicated to HTTP</i>

## Client & Server

### What is a Client 

Zooming into our above diagram on the Web, a client is a process that initiates a TCP connection. In the world of Web, we talk about HTTP. </br>

An HTTP client will create an HTTP packet that conforms to the HTTP specification. This HTTP packet is referred to as an HTTP Request.

An HTTP request has information and structure that a "Server" will understand. </br>

### What is a Server 

Looking at our diagram once again, a server is a process that accepts a TCP connection. In the world of Web, we talk about HTTP </br>

An HTTP server will listen on a port and accept a connection. It understands HTTP and will generally only respond to TCP network requests that conforms to HTTP specifications </br>

An HTTP server (or a Web server) will generally respond with an HTTP response

In our diagram above, we can visualize the incoming connection to a server and see the HTTP request as well as the HTTP response. </br>

When it comes to development of HTTP based services like microservices, it is crucial to understand HTTP requests and responses. DevOps engineers spend a lot of time analyzing HTTP traffic to understand performance, health , and security of an overall system. </br>

The HTTP requests and responses make up a large portion of monitoring, so understanding these will help you make sense of web server logs to understand what is happening between clients and servers in your environment. </br>

## HTTP Requests & Responses

### HTTP Requests

In our networking diagram, we can visualize a pure raw TCP connection flow. We can see that in order to satisfy the connection, a TCP packet would need header details such as source and destination IP addresses & ports to know where to connect to. </br>

In order for clients and servers to send each other "data" this data forms part of the TCP payload which is just arbitrary raw binary data.

An HTTP request is simply extra metadata that is sent in a TCP network packet as part of the TCP payload. 

In the world of Web, we require extra fields, for example a web browser needs a URL of the website you need to access. </br>
It needs to know if the website supports HTTPS or HTTP. When a user needs to log into a website, the browser needs to pass this login information to the server. </br>
As users interact with websites, data is passed back and forth between the browser (acting as client) and a web server (acting as server).

These all form part of the "metadata" I described, which needs to be added to a TCP network packet. Remember that under the hood of all this, there are still pure raw TCP connections.

Let's refer back to the diagram, and I will briefly walk through these fields that are required and supported by HTTP </br>

### HTTP Responses

A client is responsible for creating the HTTP request. Once sent and received by a server, the server will process it and generally respond with an HTTP response.
An HTTP response also has certain fields that it both requires and supports. 

Let's refer back to the diagram, and I will briefly walk through these fields that are required and supported by the HTTP response </br>

As this is just an overview module, we will deep dive this protocol further in a later module. </br>

[mozilla.org](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Overview) is a great resource for exploring the world of HTTP and its specifications.

#### Creating an HTTP request

I use `curl` on a daily basis to test and troubleshoot web servers. It's essential for every DevOps, SRE, Platform or Cloud engineer to master. </br>
The `curl` and `wget` tools are available in almost all Linux distributions and may even be available on many of your production servers. </br>
I often use `curl` on production servers to test if web servers are working correctly or if they are responding with the correct HTTP responses. </br>

We can make a simple HTTP request with curl like so:

```
curl https://marceldempers.dev
```
We can pass `-v` to indicate `curl` to give us more verbose data on the HTTP request and response. Doing so allows us to see request and response headers and other fields we talked about in HTTP:

```
curl -v https://marceldempers.dev
```

We can construct a HTTP request ourselves by passing some of the fields we talked about

```
curl -X GET  \
-H "Host: marceldempers.dev" \
 https://marceldempers.dev
```

### Troubleshooting HTTP requests and responses

1. Command Line

We can use `curl` alongside `-v` to troubleshoot HTTP requests responses using the command line. </br>

2. Browser

It's also important to get used to doing a similar thing in the browser. Browsers support developer tools which have network tooling built-in. We can use this to inspect HTTP requests and HTTP responses for websites to analyze web traffic </br> 

When analyzing HTTP traffic , it's important to pay attention to HTTP responses and understand HTTP status codes in depth. In my experience this is the simplest field with the largest oversight when troubleshooting HTTP traffic. </br>
It's important to study and understand the meaning of each HTTP status code. 

3. Logs

We can also gather HTTP details like URLS, Status Codes, IP addresses, Latency details etc from Web server logs. 
All web servers have configuration we can enable that tells the server to output HTTP request and response logs. 

We will use steps 1 - 3 extensively when troubleshooting HTTP requests and responses.
We'll further explore web monitoring in more detail in a future module. </br>

## What is DNS

Looking back at our network diagram, notice that I only have IP addresses for client to server communication. </br>
This is because TCP uses only source and destination IP addresses and fundamentally uses IP's to forward traffic. 

For HTTP, you may have noticed we have an HTTP header in our request that indicates `Host: marceldempers.dev` and there is no IP address there. </br>
It is up to the client to resolve the host name and work out what the IP address is, so that the client can construct a TCP packet. </br>

This means the client takes the URL portion which is the domain, and performs a DNS lookup to get the IP address. </br>

We can do this ourselves with command line tools like `nslookup` or `dig`. </br> 

<i>Note: `dig` needs to be installed with ` sudo apt-get install -y dnsutils`</i>

```
nslookup google.com
```

DNS servers will resolve domains that we purchase, to IP addresses. </br>
Important to know that IP addresses can be dynamically issued by your Internet service provider, so your public IP address of your server could change depending on where you host it. </br>

All cloud providers generally provide static IP addresses which do not change. This helps us point our DNS names to these static IP addresses when running web servers. </br>
Hosting a web server on a public IP address that is dynamic is a little more complicated, but it is possible. </br>

We will further explore the world of Domains, DNS and SSL\TLS in an upcoming module </br>

## What is a web server

A Web server is a type of server that serves HTTP requests and produces HTTP responses. </br>
Web servers listen on ports for incoming TCP connections, just as illustrated in our above network and web diagrams. </br>
Web servers basically serve files, which can be made up of different types of content from text files, to HTML, javascript, CSS stylesheets and more. A web server could host a website or web application such as a microservice. 

### Examples of Web servers 

Popular Web servers that I have used:

* NGINX
* Apache
* Caddy 
* IIS (for Microsoft Windows)

Popular programming languages also allow you to write your own or host web application code on the programming language runtime. <//br>

In this course, we will explore the most popular web servers, but that will require its own module to cover it. For simplicity of this module, I have written a [small standalone web server](.test/server) we can run. 

<i><b>Important Note:</b> Please note that links to these files can change without notice and may be different from what you read on screen in the video at the time of recording. You can always click the hyperlink in the document to get the latest path</i>

We use `cd` to change directory to the above web server and run it: 

```
./server
```

### Web server logs

Web servers have the capability to write logs. Logs are basically just a sequence of events and diagnostic information that helps with troubleshooting and analysis. </br>
Web servers can have two types of logs.

* Error logs: These logs are errors thrown by the web server. This could indicate problems with the web server or with its configuration
* Access logs: These logs contain a combination of important HTTP request and response fields, such as the time the request came in, the IP of the client making the request, the URL and PATH of the request, its response status code and other important data. </br> 
Each line in the access log represent important fields of a request and response

I've mentioned in previous modules that in Linux, our commands write output into `stdout` which is a [Standard Stream](https://en.wikipedia.org/wiki/Standard_streams) in Linux that writes output to a terminal that we can view. </br>

Let's send some HTTP requests to our web server using `curl`: 
```
curl -X GET http://localhost:8080/home.html
curl -X GET http://localhost:8080/home-notexist.html
```

Now in the terminal where we started the web server, we can see our logs being output to `stdout`.
Let's analyze the HTTP logs:

```
./server 
2025/04/01 14:20:50 Starting server on :8080
2025/04/01 14:21:00 GET /home.html 127.0.0.1:37836 HTTP/1.1 200
2025/04/01 14:21:05 GET /home-notexist.html 127.0.0.1:37840 HTTP/1.1 404

```

We can also open our page in the browser and see how the browser requests additional content like the `.js` and `.css` and `.ico` files. </br>
This is because the browser will load the `home.html` page and try to render it. </br>
While rendering, if we take a look at the `home/html` file, we will see `.css` and `.js` script file references. The browser knows to get these and use them to apply styles and rendering to the site. </br>
This is why we see these additional requests: 

```
./server 
2025/04/01 16:19:44 Starting server on :8080
2025/04/01 16:19:54 GET /home.html [::1]:54736 HTTP/1.1 200
2025/04/01 16:19:54 GET /style.css [::1]:54736 HTTP/1.1 200
2025/04/01 16:19:54 GET /script.js [::1]:54736 HTTP/1.1 200
2025/04/01 16:19:54 GET /favicon.ico [::1]:54736 HTTP/1.1 404
```

Notice that when refreshing, we may stop seeing the `.js` and `.css` request. This is because the browser may cache these resources to prevent making repeat requests to the server </br>

We'll learn more about HTML, JS, and CSS in an upcoming module

---

### Overview (`github/actions/self-hosted-runner/README.md`)

<a href="https://youtu.be/RcHGqCBofvw" title="githubactions"><img src="https://i.ytimg.com/vi/RcHGqCBofvw/hqdefault.jpg" width="20%" alt="introduction to github actions runners" /></a> 

# Introduction to GitHub Actions: Self hosted runners

## Create a kubernetes cluster

In this guide we we''ll need a Kubernetes cluster for testing. Let's create one using [kind](https://kind.sigs.k8s.io/) </br>

```
kind create cluster --name githubactions --image kindest/node:v1.28.0@sha256:b7a4cad12c197af3ba43202d3efe03246b3f0793f162afb40a33c923952d5b31
```

Let's test our cluster:
```
kubectl get nodes
NAME                          STATUS   ROLES           AGE     VERSION
githubactions-control-plane   Ready    control-plane   2m53s   v1.28.0
```

## Running the Runner in Docker 

We can simply install this directly on to virtual machines , but for this demo, I'd like to run it in Kubernetes inside a container. </br>

### Security notes

* Running in Docker needs high priviledges.
* Would not recommend to use these on public repositories.
* Would recommend to always run your CI systems in seperate Kubernetes clusters.

### Creating a Dockerfile

* Installing Docker CLI 
For this to work we need a `dockerfile` and follow instructions to [Install Docker](https://docs.docker.com/engine/install/debian/).
I would grab the content and create statements for my `dockerfile` </br>

Now notice that we only install the `docker` CLI. </br> 
This is because we want our running to be able to run docker commands , but the actual docker server runs elsewhere </br>
This gives you flexibility to tighten security by running docker on the host itself and potentially run the container runtime in a non-root environment </br>

* Installing Github Actions Runner 

Next up we will need to install the [GitHub actions runner](https://github.com/actions/runner) in our `dockerfile`
Now to give you a "behind the scenes" of how I usually build my `dockerfile`s, I run a container to test my installs: 

```
docker build . -t github-runner:latest 
docker run -it github-runner bash
```

Next steps:

* Now we can see `docker` is installed 
* To see how a runner is installed, lets go to our repo | runner and click "New self-hosted runner"
* Try these steps in the container
* We will needfew dependencies
* We download the runner


Finally lets test the runner in `docker` 

```
docker run -it -e GITHUB_PERSONAL_TOKEN="" -e GITHUB_OWNER=marcel-dempers -e GITHUB_REPOSITORY=docker-development-youtube-series github-runner
```

## Deploy to Kubernetes 

Load our github runner image so we dont need to push it to a registry:

```
kind load docker-image github-runner:latest --name githubactions
```

Create a kubernetes secret with our github details 

```
kubectl create ns github
kubectl -n github create secret generic github-secret `
  --from-literal GITHUB_OWNER=marcel-dempers `
  --from-literal GITHUB_REPOSITORY=docker-development-youtube-series `
  --from-literal GITHUB_PERSONAL_TOKEN=""

kubectl -n github apply -f kubernetes.yaml
```

---

### Overview (`golang/introduction/part-2.json/readme.md`)

# Introduction to Go: JSON

<a href="https://youtu.be/_ok29xwZ11k" title="golang-part-2"><img src="https://i.ytimg.com/vi/_ok29xwZ11k/hqdefault.jpg" width="20%" alt="introduction to Go part 2" /></a>

In programming languages, you will very often deal with data structures internally. <br/>
Sometimes, you need to pass data outside of your application or read data from another application, or even a file. <br/>

API, for example often expose data in `json`, `xml`, `grpc` and all sorts of formats.
Before we can really take a look at building APIs and even storing data in databases,
we need some fundamental knowledge about how to convert these data structures, into structures
that our application can understand. <br/>

In part 1, we dealt with [Variables]("https://tour.golang.org/basics/8") and more importantly, <br/> we dealt with `struct` data type, which made us build a `customer` object.

If we wanted to pass the `customer` to a database, or an external system, it's often required that we convert this to `json` format.


## Dev Environment

The same as Part 1, we start with a [dockerfile](./dockerfile) where we declare our version of `go`.

```
cd golang\introduction\part-2.json

docker build --target dev . -t go
docker run -it -v ${PWD}:/work go sh
go version
```

## Create our App

Create a new directory that holds defines our `repository` and holds our `module`

```
mkdir videos

```

* Define a module path

```
# change directory to your application source code

cd videos

# create a go module file

go mod init videos

```

## Create our base code

We start out all our applications with a `main.go` defining our `package`, declaring our `import` dependencies <br/>
and our entrypoint `main()` function

```
package main

import "fmt"

func main() {
	fmt.Println("Hello, world.")
}
```

## Create our Videos app

Firstly, we create a seperate code file `videos.go` that deals with our YouTube videos <br/>

Similar to Part 1, we define the file, we define what a video looks like using a `struct` <br/>
and a function for returning a slice of videos.


```
package main

import ()

type video struct {
	Id string  
	Title string  
	Description string 
	Imageurl string 
	Url string 
}

func getVideos()(videos []video){
	//Get our videos here,
	//and return them
	return videos
}


```

## Populating our Video Struct

Similar to our customers app, we created a `struct` and populated it with data: 
Let's add the following to our `getVideos()` function:

```
video1 := video{
  Id : "eyvLwK5C2dw",
  Title : "Kubernetes on Azure",
  Imageurl : "https://i.ytimg.com/vi/eyvLwK5C2dw/mqdefault.jpg?sqp=CISC_PoF&rs=AOn4CLDo7kizrJozB0pxBhxL9JbyiW_EPw",
  Url : "https://youtu.be/eyvLwK5C2dw",
  Description : "",
}

video2 := video{
  Id : "QThadS3Soig",
  Title : "Kubernetes on Amazon",
  Imageurl : "https://i.ytimg.com/vi/QThadS3Soig/sddefault.jpg",
  Url : "https://youtu.be/QThadS3Soig",
  Description : "",
}

return []video{ video1, video2}

```

We need to also invoke our function that will return the videos
Don't worry about where the videos will come from. <br/>
We always start with the building blocks and move on from there <br/>

In our `main()` function:  <br/>

```
func main() {
	videos := getVideos()
	fmt.Println(videos)
}
```

## Files

Now, Ideally, we do not want to "hard code" our videos like this. <br/>
Currently, our videos are embedded into the code and we can only return 2 videos. <br/>
If we want to introduce a new video, we have to rebuild the application. <br/>

In the real world, videos are our data. <br/>
Data lives outside of the application. Like in a database.

Technically, we can use a database, but that is too big of a step for now. <br/>
So let's start with a file instead.

### Introducing ioutil

It's important to learn how to navigate and read go documentation.
Let's go to https://golang.org/pkg/io/ioutil/

We can import this library since its part of the Go standard library set.

```
import (
	"io/ioutil"
)
```

Let's move our videos into a `json` file called `videos.json` :

```
[
  {
		"id" : "QThadS3Soig",
		"title" : "Kubernetes on Amazon",
		"imageurl" : "https://i.ytimg.com/vi/QThadS3Soig/sddefault.jpg",
		"url" : "https://youtu.be/QThadS3Soig",
		"description" : ""
	},
  {
		"id" : "eyvLwK5C2dw",
		"title" : "Kubernetes on Azure",
		"imageurl" : "https://i.ytimg.com/vi/eyvLwK5C2dw/mqdefault.jpg?sqp=CISC_PoF&rs=AOn4CLDo7kizrJozB0pxBhxL9JbyiW_EPw",
		"url" : "https://youtu.be/eyvLwK5C2dw",
		"description" : ""
	}
]

```

## Reading a file

Let's take another look at https://golang.org/pkg/io/ioutil/ <br/>
Notice the `ReadFile` function
We can read a file from disk:

```
	fileBytes, err := ioutil.ReadFile("./videos.json")

	if err != nil {
		panic(err)
	}

  fileContent := string(fileBytes)
  fmt.Println(fileContent)
  
```

## Panic

Also notice the function `panic`. <br/>

"A panic typically means something went unexpectedly wrong. Mostly we use it to fail fast on errors that shouldn’t occur during normal operation, or that we aren’t prepared to handle gracefully." 
- https://gobyexample.com/panic

For now, we will panic on every potential error. <br/>
In a future video, we'll cover Error handling in more depth

## JSON 

Working with JSON is pretty straightforward in go. <br/>
`struct` objects are pretty well synergized with `json`

Let's take a look at the `json` package that is part of go.
https://golang.org/pkg/encoding/json/

```
"encoding/json"
```

This package allows us to marshal and unmarshal JSON. <br/>
This allows conversion between a `struct` or a go type, and `json` 


## Unmarshal

From JSON(bytes) ==> Struct\Go type

```
  err = json.Unmarshal(fileBytes, &videos)

	if err != nil {
		panic(err)
	}

	return videos
```
## Using our Data

Now in the real world, you would use your data to do something. <br/>
Let's say we'd like to update some common terms and conditions to the video description <br/>

In our `main` function, let's loop the videos & update the description.
Feel free to checkout Part 1 of the series on `loops`!

```
  for _, video := range videos {
  }
```

Now we dont want to override the description, we want to inject the terms and conditions on a new line

```
  video.Description = video.Description + "\n Remember to Like & Subscribe!"
```

Note that when we run this, it does not print our new description field </br>
This is because the loop `range` is giving us a copy of the video object. </br> 
This means we are updating a copy, but printing out the original. </br>
We need to use the loop indexer and update the original object in the slice. </br>


```
  for i, _ := range videos {
		videos[i].Description = videos[i].Description + "\n Remember to Like & Subscribe!"
	}
```

Run our app again and now it updates the original video!

## Marshal

https://golang.org/pkg/encoding/json/#Marshal
From Struct\Go type ==> JSON(bytes) 

Let's create a new function for saving our video back to file

```
func saveVideos(videos []video)(){

  videoBytes, err  := json.Marshal(videos)
  if err != nil {
		panic(err)
	}

}

```

## Writing to File

https://golang.org/pkg/io/ioutil/

```

  err = ioutil.WriteFile("./videos-updated.json", videoBytes, 0644)
  if err != nil {
		panic(err)
	}
```

Then in our `main` function we can save our videos after the update:

```
  saveVideos(videos)
```

## Mapping fields


In our example our `json` and our struct fields match exactly.
Sometimes this is not possible to maintain.
Sometimes we need to tell go, to map certain `json` fields to certain fields in our `struct`
We can do it like so:

```
type video struct {
	Id string `json:"id"`
	Title string `json:"title"`
	Description string `json:"description"`
	Imageurl string `json:"imageurl"`
	Url string `json:"url"`
}
```

---

### Overview (`golang/introduction/part-3.http/readme.md`)

# Introduction to Go: HTTP

<a href="https://youtu.be/MKkokYpGyTU" title="golang-part-3"><img src="https://i.ytimg.com/vi/MKkokYpGyTU/hqdefault.jpg" width="20%" alt="introduction to Go part 3" /></a>

HTTP is a fundamental part of Microservices and Web distributed systems <br/>

Go has a built in HTTP web server package. The package can be found [here](https://golang.org/pkg/net/http/) <br/>
We simply have to import the `http` package: 

```
import (
  "net/http"
)
```

[In part 1](../readme.md), we covered the fundamentals of writing basic Go <br/>
[In part 2](../part-2.json/readme.md), we've learn how to use basic data structures like `json` so we can send\receive data. <br/>

We'll be combining both these techniques so we can serve our `videos` data over a web endpoint.

As always, let's start with our `dockerfile` , `main.go` and `videos.go` we created in Part 2


## Dev Environment

The same as Part 1+2, we start with a [dockerfile](./dockerfile) where we declare our version of `go`.

```
cd golang\introduction\part-3.http

docker build --target dev . -t go
docker run -it -v ${PWD}:/work go sh
go version
```

## Create our App

Create a new directory that holds defines our `repository` and holds our `module`

```
mkdir videos

```

* Define a module path

```
# change directory to your application source code

cd videos

# create a go module file

go mod init videos

```

## Create our base code

We start out all our applications with a `main.go` defining our `package`, declaring our `import` dependencies <br/>
and our entrypoint `main()` function

```
package main

import "fmt"

func main() {
	fmt.Println("Hello, world.")
}
```

## Create our Videos app

Firstly, we create a seperate code file `videos.go` that deals with our YouTube videos <br/>

The `videos.go` file defines what a video `struct` looks like, a `getVideos()` function to retrieve <br/>
videos list as a slice and a `saveVideos()` function to save videos to a file locally. <br/>

Let's copy the following content from Part 2 and create `videos.go` :

We want `videos.go` to be part of package main:

```
package main

```

We import 2 packages, 1 for reading and writing files, and another for dealing with `json`

```
import (
	"io/ioutil"
	"encoding/json"
)

```

Then we define what a video `struct` looks like:

```

type video struct {
	Id string  
	Title string  
	Description string 
	Imageurl string 
	Url string 
}

```

We have a function for retrieving `video` objects as a list of type `slice` :

```

func getVideos()(videos []video){
	
	fileBytes, err := ioutil.ReadFile("./videos.json")

	if err != nil {
		panic(err)
	}

	err = json.Unmarshal(fileBytes, &videos)

	if err != nil {
		panic(err)
	}

	return videos
}

```

We also need to copy our `videos.json` file which contains our video data. <br/>

And finally, we have a function that accepts a list of type `slice` and stores the videos to a local file

```
func saveVideos(videos []video)(){

  videoBytes, err  := json.Marshal(videos)
  if err != nil {
		panic(err)
	}

	err = ioutil.WriteFile("./videos-updated.json", videoBytes, 0644)
  if err != nil {
		panic(err)
	}

}
```

## HTTP Package 

https://golang.org/pkg/net/http/

The HTTP package allows us to implement an HTTP client and a server.
A client is a component that makes HTTP calls.
A server is a component that receives or serves HTTP.

The HTTP package is capable of sending HTTP requests as well as defining a server
for receiving HTTP requests. 

We can use this to run an HTTP server to serve files, or serve data, like an API.

Let's define a server in `main.go` :

```
# just one line :)

http.ListenAndServe(":8080", nil)

# ListenAndServe starts an HTTP server with a given address and handler. 
# The handler is usually nil, which means to use DefaultServeMux. 
# Handle and HandleFunc add handlers to DefaultServeMux

```

Now before we run this, since we're running in Docker, we want to exit the container <br/>
and rerun it, but this time open port `8080`

```
docker run -it -p 8080:8080 -v ${PWD}:/work go sh
cd videos

go run main.go 
# you will notice the application pausing
```

We should see our server with a 404 on http://localhost:8080/

## Handle HTTP requests

In order to handle requests, we can tell the HTTP service that we want it to run a function </br>
for the request coming in.

We can see the `http` package has a `HandleFunc` function: https://golang.org/pkg/net/http/

To see this in action, lets create a `Hello()` function:

```
func Hello(){
}
```

And tell our `http` service to run it:

```
http.HandleFunc("/", Hello)
```

We cannot run this yet. As per `http` documentation, our `Hello` function needs to take in some inputs.
`func HandleFunc(pattern string, handler func(ResponseWriter, *Request))`

Therefore we need to add inputs to our function: 

```
func Hello(w http.ResponseWriter, r *http.Request){
}
```

This allows us to get the request, its `body`, `headers` and a write where we can send a response.
Run this in the browser and you will notice the 404 goes away, but we now get an empty response.

## HTTP Response

Let's write a reponse to the incoming request. </br>
The response write has a `Write()` function that takes a bunch of bytes. <br/>
We can convert string to bytes by casting a `string` to a `[]byte` <br/> like:
`[]byte("Hello!")`. Let's convert it and write "Hello" to the response:

```
w.Write([]byte("Hello!"))
```

IF we run this code, we can see "Hello!" in the response body

## HTTP Headers

Headers play an important role in HTTP communication. </br>
Lets access all the headers of the incoming request!
If we look at the Header definition [here](https://golang.org/pkg/net/http), we can see how to access it.
Let's use the `for` loop we learnt in [part 1](../readme.md)

```
for i, value := range r.Header {
}
```

We learn't from our loop, we have in indexer and a value.
For `i`, we can rename it to header since it represents the header key in the dictionary.
And the `value` is the value of type `[]string`, containing the value of the header:

```
for header, value := range r.Header {
  fmt.Printf("Key: %v \t Value: %v \n", header, value)
}
```

We can use `fmt` to print out the values and look at the headers.

We can also set headers on our response. <br/>
If we take a look at the `http` docs, we can see header is also a dictionary or strings.

```
w.Header().Add("TestHeader", "TestValue")
```

You can now see the headers in the response value if you use `curl` or your browser development tools

## HTTP Methods | GET

Web servers can serve data in a number of ways and support multiple type of HTTP methods.
`GET` is used to request data from a specified resource.
So far, our HTTP route for our Hello function is using the `GET` method.

Let's make our `GET` method more useful by serving our video data </br>
Let's rename our `Hello()` function to `HandleGetVideos()`. </br>
Our `/` route will return all videos:

```
videos := getVideos()
```

In [part 2](../part-2.json/readme.md) we covered `JSON`.
We need to convert our video `slice` of `struct`'s to `JSON` in order to return it to the client.

For this we learnt about the Marshall function: 

Import the `JSON` package: 
```
"encoding/json"
```

Convert our videos to `JSON` :

```
videoBytes, err  := json.Marshal(videos)

if err != nil {
  panic(err)
}

w.Write(videoBytes)

```

If we run this code and hit our `/` endpoint, we can now see `JSON` data being returned. <br/>
This is a core part of building an API in Go. <br/>

## HTTP Methods | POST

A `POST` method is used to send data to a server to create/update a resource. <br/>
Since we built a `saveVideos` function, lets use that so a client can update videos! <br/>

We need to define a new route, we can all it `/update` :

```
http.HandleFunc("/update", HandleUpdateVideos)
```

And we need to define an `HandleUpdateVideos()` function:

```
func HandleUpdateVideos(w http.ResponseWriter, r *http.Request){
}
```

Let's validate the request method to ensure its `POST`
We need to also ensure we send a status code to inform the user of method not allowed.

https://golang.org/pkg/net/http/#ResponseWriter

```
	if r.Method == "POST" {
		//update our videos here!
	} else {
	  w.WriteHeader(405)
		fmt.Fprintf(w, "Method not Supported!")
	}
```

Now we need to accept `JSON` from the `POST` request body
https://golang.org/pkg/net/http/#Request

In the docs above, we can see the request Body is of type `Body io.ReadCloser`
To read that, we can use the `ioutil` package 
https://golang.org/pkg/io/ioutil/

```
import "io/ioutil"
```

Then we can read the body into a `slice` of `bytes`:

```
body, err := ioutil.ReadAll(r.Body)

if err != nil {
  panic(err)
}
```

Now that we have the body in a `[]byte`, we need to use our knowledge from [part 2](../part-2.json/readme.md) where we <br/>
convert `[]byte` to a `slice` of `video` items.

```

var videos []video
err = json.Unmarshal(body, &videos)
if err != nil {
  panic(err)
}
```

Creating our video objects allows us to do some validation if we wanted to. <br/>
We can ensure the request body adheres to our API contract for this videos API. <br/>
So instead of calling `panic`, lets return a `400` Bad request status code if we cannot <br/>
Unmarshal the `JSON` data. This might help with some basic validation.

```
w.WriteHeader(400)
fmt.Fprintf(w, "Bad request")
```

And Finally, let's update our videos file! :

```
saveVideos(videos)
```

# Build our Docker container

Let's uncomment all the build lines in the `dockerfile`
Full `dockerfile` : 

```
FROM golang:1.15-alpine as dev

WORKDIR /work

FROM golang:1.15-alpine as build

WORKDIR /videos
COPY ./videos/* /videos/
RUN go build -o videos


FROM alpine as runtime 
COPY --from=build /videos/videos /
COPY ./videos/videos.json /
CMD ./videos

```

Build :
```
cd golang\introduction\part-3.http
docker build . -t videos
```

Run :
```
docker run -it -p 8080:8080 videos
```

## Things to know

* SSL for secure web connection
* Authentication
* Good API validation
* Support a backwards compatible contract (Inputs remain consistent)

---

### Overview (`golang/introduction/part-4.commandline/readme.md`)

# Introduction to Go: Command Line

<a href="https://youtu.be/CODqM_rzwtk" title="golang-part-4"><img src="https://i.ytimg.com/vi/CODqM_rzwtk/hqdefault.jpg" width="20%" alt="introduction to Go part 4" /></a>

Command line apps are a fundamental part of software development <br/>

Go has a built in Commandline parser package. The package can be found [here](https://golang.org/pkg/flag/) <br/>
We simply have to import the `flag` package: 

```
import (
  "flag"
)
```

[In part 1](../readme.md), we covered the fundamentals of writing basic Go <br/>
[In part 2](../part-2.json/readme.md), we've learn how to use basic data structures like `json` so we can send\receive data. <br/>
[Part 3](../part-3.http/readme.md) was about exposing data via a Web server.

We'll be combining these techniques so we can serve our `videos` data over a commandline application.

As always, let's start with our `dockerfile` , `main.go` and `videos.go` we created in Part 2

## Dev Environment

The same as Part 1+2+3, we start with a [dockerfile](./dockerfile) where we declare our version of `go`.

```
cd golang\introduction\part-4.commandline

docker build --target dev . -t go
docker run -it -v ${PWD}:/work go sh
go version
```

## Create our App

Create a new directory that holds defines our `repository` and holds our `module`

```
mkdir videos
cd videos
```

* Define a module path

```
# create a go module file

go mod init videos

```

## Create our base code

We start out all our applications with a `main.go` defining our `package`, declaring our `import` dependencies <br/>
and our entrypoint `main()` function

```
package main

import "fmt"

func main() {
	fmt.Println("Hello, world.")
}
```

## Create our Videos app

Firstly, we create a seperate code file `videos.go` that deals with our YouTube videos <br/>

The `videos.go` file defines what a video `struct` looks like, a `getVideos()` function to retrieve <br/>
videos list as a slice and a `saveVideos()` function to save videos to a file locally. <br/>

Let's copy the following content from Part 2 and create `videos.go` :

We want `videos.go` to be part of package main:

```
package main

```

We import 2 packages, 1 for reading and writing files, and another for dealing with `json`

```
import (
	"io/ioutil"
	"encoding/json"
)

```

Then we define what a video `struct` looks like:

```

type video struct {
	Id string  
	Title string  
	Description string 
	Imageurl string 
	Url string 
}

```

We have a function for retrieving `video` objects as a list of type `slice` :

```

func getVideos()(videos []video){
	
	fileBytes, err := ioutil.ReadFile("./videos.json")

	if err != nil {
		panic(err)
	}

	err = json.Unmarshal(fileBytes, &videos)

	if err != nil {
		panic(err)
	}

	return videos
}

```

We also need to copy our `videos.json` file which contains our video data. <br/>

And finally, we have a function that accepts a list of type `slice` and stores the videos to a local file

```
func saveVideos(videos []video)(){

  videoBytes, err  := json.Marshal(videos)
  if err != nil {
		panic(err)
	}

	err = ioutil.WriteFile("./videos.json", videoBytes, 0644)
  if err != nil {
		panic(err)
	}

}
```

## Flag Package 

https://golang.org/pkg/flag/

Package flag implements command-line flag parsing.
So we can run our videos app and pass it inputs like any other CLI.

Let's build a CLI tool that users our `getVideos()` and `saveVideos` functions.

To get all videos, perhaps we'd like a command
```
# get all videos
videos get --all

# get video by ID
videos get -id <video-id>

# add a video to our list
videos add -id -title -url -imageurl -desc 


```
To start, we import package `flag`

```
import (
	"flag"
	"fmt"
)
```


Let's define our subcommands in `main.go` :

```
    //'videos get' subcommand
    getCmd := flag.NewFlagSet("get", flag.ExitOnError)
```

`videos get` command will need two inputs, `--all` if the user wants to return all videos, or `--id` if the user only wants a specific video

```
  // inputs for `videos get` command
  getAll := getCmd.Bool("all", false, "Get all videos")
  getID := getCmd.String("id", "", "YouTube video ID")
```

`videos add` command will take a bit more inputs to be able to add a video to our storage.
```
  addCmd := flag.NewFlagSet("add", flag.ExitOnError)

  addID := addCmd.String("id", "", "YouTube video ID")
  addTitle := addCmd.String("title", "", "YouTube video Title")
  addUrl := addCmd.String("url", "", "YouTube video URL")
  addImageUrl := addCmd.String("imageurl", "", "YouTube video Image URL")
  addDesc := addCmd.String("desc", "", "YouTube video description")


```

When a user runs our videos CLI tool, we may need to validate that 
our application receives the right subcommands. So lets ensure a simple validation to check if the user has passed a subcommand

To check the arguments passed to our CLI, we use the ["os"](https://golang.org/pkg/os/) package. Check the Args variable, it holds usefull information passed to our application including its name.
`var Args []string`

We can do a simple check by ensuring the length of `os.Args` is atleast 2.

We firstly need to add `os` to our import section </br>
Followed by our check:

```
if len(os.Args) < 2 {
        fmt.Println("expected 'get' or 'add' subcommands")
        os.Exit(1)
  }
```

## Handling our subcommands

So to handle each sub command like `get` and `add`, we add a simple
`switch` statement that can branch into different pathways of execution,
based on a variables content.

Let's take a look at this simple `switch` statement:

```
//look at the 2nd argument's value
switch os.Args[1] {
  case "get": // if its the 'get' command
    //hande get here
  case "add": // if its the 'add' command
    //hande add here
  default: // if we don't understand the input
}
```

Let's create seperate handler functions for each sub command to keep our code tidy:

```
func HandleGet(getCmd *flag.FlagSet, all *bool, id *string){
}

func HandleAdd(addCmd *flag.FlagSet,id *string, title *string, url *string, imageUrl *string, description *string ){
}
```

Now that we have seperate functions for each subcommand, we can take appropriate actions in each. Let's firstly parse the command flags for each subcommand:

This allows us to parse everything after the `videos <subcommand>` arguments:

```
getCmd.Parse(os.Args[2:])
```

## Input Validation

For our `HandleGet` function, let's validate input to ensure its correct.

```
  if *all == false && *id == "" {
    fmt.Print("id is required or specify --all for all videos")
    getCmd.PrintDefaults()
    os.Exit(1)
  }
```

Let's handle the scenario if user passing `--all` flag:

```
if *all {
		//return all videos
		videos := getVideos()
		
		fmt.Printf("ID \t Title \t URL \t ImageURL \t Description \n")
		for _, video := range videos {
			fmt.Printf("%v \t %v \t %v \t %v \t %v \n",video.Id, video.Title, video.Url, video.Imageurl,video.Description)
		}

		return
}
```

Let's handle when user is searching for a video by ID

```
if *id != "" {
		videos := getVideos()
		id := *id
		for _, video := range videos {
			if id == video.Id {
				fmt.Printf("ID \t Title \t URL \t ImageURL \t Description \n")
				fmt.Printf("%v \t %v \t %v \t %v \t %v \n",video.Id, video.Title, video.Url, video.Imageurl,video.Description)

			}
		}
} 
```

## Parsing multiple fields

For our `HandleAdd` function, we need to validate multiple inputs, create a `video` struct, append it to the existing video list and save it back to file

Let's create a `ValidateVideo()` function with similar inputs to our `HandleAdd()`:

```
func ValidateVideo(addCmd *flag.FlagSet,id *string, title *string, url *string, imageUrl *string, description *string ){
}

```

Let's simply validate all fields since they are all required:

```
if *id == "" || *title == "" || *url == "" || *imageUrl == "" || *description == "" {
		fmt.Print("all fields are required for adding a video")
		addCmd.PrintDefaults()
		os.Exit(1)
	}
```

And we can now call this function in our add function:

```
ValidateVideo(addCmd, id,title,url, imageUrl, description)
```

## Adding our video

Now that we have some basic validation, not perfect, but good enough to get started, let's add our video to the existing file.

Define a video struct with the CLI input:

```
	video := video{
		Id: *id,
		Title: *title,
		Description: *description,
		Imageurl: *imageUrl, 
		Url: *url, 
	}
```

Get the existing videos:

```
videos := getVideos()
```

Append our video to the list:

```
videos = append(videos,video)
```

Save the new updated video list:

```
saveVideos(videos)
```

# Build our Docker container

Let's uncomment all the build lines in the `dockerfile`
Full `dockerfile` : 

```
FROM golang:1.15-alpine as dev

WORKDIR /work

FROM golang:1.15-alpine as build

WORKDIR /videos
COPY ./videos/* /videos/
RUN go build -o videos


FROM alpine as runtime 
COPY --from=build /videos/videos /usr/local/bin/videos
COPY ./videos/videos.json /
COPY run.sh /
RUN chmod +x /run.sh
ENTRYPOINT [ "./run.sh" ]
```

For our entrypoint, we need to create a shell script to accept all the arguments:

Let's create a script called `run.sh`

```
#!/bin/sh

videos $@

```

Build :
```
cd golang\introduction\part-4.commandline
docker build . -t videos
```

Run :
```
docker run -it videos get --help
```

---

### Overview (`golang/introduction/part-5.database.redis/readme.md`)

# Introduction to Go: Storing data in Redis Database

<a href="https://youtu.be/6lJCyKwoQaQ" title="golang-part-5"><img src="https://i.ytimg.com/vi/6lJCyKwoQaQ/hqdefault.jpg" width="20%" alt="introduction to Go part 5" /></a>

Up until now, we've learned the fundamentals of Go and built a small web microservice that handles our video data.
Our service has a `/` `GET` endpoint for returning all videos, as well as a simple `/update` endpoint for updating our list of videos.

We've learnt how to read and write from files and learn how to work with `json` data. </br>
This is important for learning Go, however there are a few challenges for using a file as storage. <br/>

* It can be problematic if we have more than one instance of our service writing to the same file
* It's important to keep state out of our application, so if our application crashes, we don't lose our data

[In part 1](../readme.md), we covered the fundamentals of writing basic Go <br/>
[In part 2](../part-2.json/readme.md), we've learnt how to use basic data structures like `json` so we can send\receive data. <br/>
[In part 3](../part-3.http/readme.md), we've learnt how to write a HTTP service to expose our videos data <br/>

## Start up a Redis Cluster

Follow my Redis clustering Tutorial </br>

<a href="https://youtube.com/playlist?list=PLHq1uqvAteVtlgFkmOlIqWro3XP26y_oW" title="Redis"><img src="https://i.ytimg.com/vi/L3zp347cWNw/hqdefault.jpg" width="30%" alt="Redis Guide" /></a>

Code is over [here](../../../storage/redis/clustering/readme.md)

## Go Dev Environment

The same as Part 1+2+3+4, we start with a [dockerfile](./dockerfile) where we declare our version of `go`.

The `dockerfile`:

```
FROM golang:1.15-alpine as dev

WORKDIR /work
```

Let's build and start our container: 

```
cd golang\introduction\part-5.database.redis

docker build --target dev . -t go
docker run -it -p 80:80 -v ${PWD}:/work go sh
go version
```

## Our application

Create a new directory that holds defines our `repository` and holds our `module`

```
mkdir videos

```

* Define a module path

```
# change directory to your application source code

cd videos

# create a go module file

go mod init videos

```

## Create our base code

We start out all our applications with a `main.go` defining our `package`, declaring our `import` dependencies <br/>
and our entrypoint `main()` function

```
package main

import (
	"net/http"
	"encoding/json"
	"io/ioutil"
	"fmt"
)

func main() {
	
	http.HandleFunc("/", HandleGetVideos)
	http.HandleFunc("/update", HandleUpdateVideos)

	http.ListenAndServe(":80", nil)
}
```

Now before we write these handler functions, we need our videos application

## Create our Videos app

Firstly, we create a separate code file `videos.go` that deals with our YouTube videos <br/>

The `videos.go` file defines what a video `struct` looks like, a `getVideos()` function to retrieve <br/>
videos list as a slice and a `saveVideos()` function to save videos to a file locally. <br/>

We start with our dependencies. <br> 
We import 2 packages, 1 for reading and writing files, and another for dealing with `json`

```
package main

import (
	"io/ioutil"
	"encoding/json"
)

```

Then we define what a video `struct` looks like:

```
type video struct {
	Id string  
	Title string  
	Description string 
	Imageurl string 
	Url string 
}
```

We have a function for retrieving `video` objects as a list of type `slice` :

```
func getVideos()(videos []video){
	
	fileBytes, err := ioutil.ReadFile("./videos.json")

	if err != nil {
		panic(err)
	}

	err = json.Unmarshal(fileBytes, &videos)

	if err != nil {
		panic(err)
	}

	return videos
}

```

We also need to copy our `videos.json` file which contains our video data. <br/>

And finally, we have a function that accepts a list of type `slice` and stores the videos to a local file

```
func saveVideos(videos []video)(){

  videoBytes, err  := json.Marshal(videos)
  if err != nil {
		panic(err)
	}

	err = ioutil.WriteFile("./videos.json", videoBytes, 0644)
  if err != nil {
		panic(err)
	}

}
```

## HTTP Handlers

Now we have to define our handler functions `HandleGetVideos` and `HandleUpdateVideos`, 

```
func HandleGetVideos(w http.ResponseWriter, r *http.Request){
	
	videos := getVideos()
	videoBytes, err  := json.Marshal(videos)

	if err != nil {
  	panic(err)
	}

	w.Write(videoBytes)
}

func HandleUpdateVideos(w http.ResponseWriter, r *http.Request){

	if r.Method == "POST" {

			body, err := ioutil.ReadAll(r.Body)
			if err != nil {
				panic(err)
			}

			var videos []video
			err = json.Unmarshal(body, &videos)
			if err != nil {
				w.WriteHeader(400)
				fmt.Fprintf(w, "Bad request")
			}

			saveVideos(videos)

		} else {
			w.WriteHeader(405)
			fmt.Fprintf(w, "Method not Supported!")
		}
}

```

Now so far, we have a web HTTP application that can list and update our youtube videos. <br/>
Let's build and run it 

```
go build

./videos

```

If we head over to `http://localhost` in the browser we can see our application returns our 2 videos. </br>.
We can use tools like PostMan to generate a `POST` request to update our videos too.


## Redis Go Package 

Now instead of reading and writing our records to a `json` file, we are going to read and write records to Redis.

https://github.com/go-redis/redis/

```
go get github.com/go-redis/redis/v8

```

And to use the library, we have to import it

```
import (
  "github.com/go-redis/redis/v8"
)
```

Now to connect to any database, you're going to need a bit of information:

* Database Address
* Database Port
* Database Username\Password

We can define these as environment variables and read those in our code

```
import (
  "os"
)
//main.go (global variables)

var redis_sentinels = os.Getenv("REDIS_SENTINELS")
var redis_master = os.Getenv("REDIS_MASTER_NAME")
var redis_password = os.Getenv("REDIS_PASSWORD")

```

We define an empty context and Redis client
Context can be used to control timeouts and deadlines for our application. We can set up an empty context for now.

```
import (
  "context"
)

var ctx = context.Background() 
var redisClient *redis.Client

```

## Redis Sentinel Client

https://redis.uptrace.dev/guide/sentinel.html#redis-server-client


```
import (
  "strings"
)

sentinelAddrs := strings.Split(redis_sentinels, ",")
rdb := redis.NewFailoverClient(&redis.FailoverOptions{
  MasterName:     redis_master,
  SentinelAddrs:  sentinelAddrs,
  Password: redis_password,
  DB: 0,
})

redisClient = rdb

rdb.Ping(ctx)
```

We can also add the `Ping()` to our handler functions to ensure it can connect using the global redis client variable

## Networking

Now we need to remember our go container may not be able to talk to the redis containers because they are running on different networks.

If you took note, we started our Redis containers on a `redis` network by passing `--net redis` as a flag to our `docker run` commands.  </br>

Let's restart our Go container on the same network

We also need to set our `ENV` variables to point our container to the redis sentinels. </br>

If we look at our sentinel configuration, our master alias is set to `mymaster`

```
docker run -it -p 80:80 `
  --net redis `
  -e REDIS_SENTINELS="sentinel-0:5000,sentinel-1:5000,sentinel-2:5000" `
  -e REDIS_MASTER_NAME="mymaster" `
  -e REDIS_PASSWORD="a-very-complex-password-here" `
  -v ${PWD}:/work go sh
```

We can now observe our container is connected to Redis. </br>
Our application: [http://localhost](http://localhost)

# Store our Data

So now we can store our video data in Redis instead of a local `json` file. </br>
We'll write the `json` document of a video (struct) to Redis. </br>

Let's create a `saveVideo()` function that stores a single record.

```
func saveVideo(video video)(){

  videoBytes, err  := json.Marshal(video)
  if err != nil {
		panic(err)
	}

	err = redisClient.Set(ctx, video.Id, videoBytes, 0).Err()
	if err != nil {
		panic(err)
	}
}
```

Now since we have one endpoint that saves all videos, we need to update it to save each video it has.

```
func saveVideos(videos []video)(){
	for _, video := range videos {
		saveVideo(video)
	}
}
```

To get the videos, let's create a function to retrieve a single record:

```
func getVideo(id string)(video video) {
	
	value, err := redisClient.Get(ctx, id).Result()

	if err != nil {
		panic(err)
	}

	if err != redis.Nil {
		err = json.Unmarshal([]byte(value), &video)
	}
	
	return video
}

```

And finally we need to update our `GetVideos()` function to loop all the video keys and return all videos

```
func getVideos()(videos []video){
	
	keys, err := redisClient.Keys(ctx,"*").Result()

	if err != nil {
		panic(err)
	}

	for _, key := range keys {
		video := getVideo(key)
		videos = append(videos, video)
	}

	return videos
}

```

And we need all our imports

```
import (
	"encoding/json"
	"github.com/go-redis/redis/v8"
)
```

Now if we rebuild our all and access it, we get `null` as there are no videos in Redis. Let's add two!

Let's `POST` the following using PostMan to url `http://localhost/update`
```
[
	{
		"id" : "QThadS3Soig",
		"title" : "Kubernetes on Amazon",
		"imageurl" : "https://i.ytimg.com/vi/QThadS3Soig/sddefault.jpg",
		"url" : "https://youtu.be/QThadS3Soig",
		"description" : "TEST"
	},
	{
		"id" : "eyvLwK5C2dw",
		"title" : "Kubernetes on Azure",
		"imageurl" : "https://i.ytimg.com/vi/eyvLwK5C2dw/mqdefault.jpg?sqp=CISC_PoF&rs=AOn4CLDo7kizrJozB0pxBhxL9JbyiW_EPw",
		"url" : "https://youtu.be/eyvLwK5C2dw",
		"description" : "TEST"
	}
]

```

If we refresh our page, we can now see two records!

# Improvements

Now that you have the fundamental knowledge of HTTP and Redis, </br>
you can update the code to retrieve 1 video by ID, or delete a video by ID. </br>
You can add search functionality and more! </br>

Let's update our `/ GET` handler to be able to return a single video

```
func HandleGetVideos(w http.ResponseWriter, r *http.Request){
	
	id, ok := r.URL.Query()["id"]
  
	if ok {

    videoID := id[0]
    video := getVideo(videoID)
    
		if video.Id == "" { //video not found, or empty ID
			w.WriteHeader(http.StatusNotFound)
			w.Write([]byte("{}"))
			return 
		}

		videoBytes, err  := json.Marshal(video)
		if err != nil {
  		panic(err)
		}

		w.Write(videoBytes)
    return
		
	}

	videos := getVideos()
	videoBytes, err  := json.Marshal(videos)

	if err != nil {
  	panic(err)
	}

	w.Write(videoBytes)
}
```

We can also update our `/update POST` endpoint to be able to update a single video

```
func HandleUpdateVideos(w http.ResponseWriter, r *http.Request){

	if r.Method == "POST" {

			body, err := ioutil.ReadAll(r.Body)
			if err != nil {
				panic(err)
			}

			_, ok := r.URL.Query()["id"]
			if ok {
				
				var video video
				err = json.Unmarshal(body, &video)
				if err != nil {
					w.WriteHeader(400)
					fmt.Fprintf(w, "Bad request")
				}

				saveVideo(video)
				return

			}
			
			var videos []video
			err = json.Unmarshal(body, &videos)
			if err != nil {
				w.WriteHeader(400)
				fmt.Fprintf(w, "Bad request")
			}

			saveVideos(videos)
			return

		} else {
			w.WriteHeader(405)
			fmt.Fprintf(w, "Method not Supported!")
		}
}
```

# Build our Docker container

Full `dockerfile` : 

```
FROM golang:1.15-alpine as dev

WORKDIR /work

FROM golang:1.15-alpine as build

WORKDIR /videos
COPY ./videos/* /videos/
RUN go build -o videos

FROM alpine as runtime 
COPY --from=build /videos/videos /
CMD ./videos

```

Build :
```
cd golang\introduction\part-5.database.redis
docker build . -t videos
```

Run :
```
docker run -it -p 80:80 `
  --net redis `
  -e REDIS_SENTINELS="sentinel-0:5000,sentinel-1:5000,sentinel-2:5000" `
  -e REDIS_MASTER_NAME="mymaster" `
  -e REDIS_PASSWORD="a-very-complex-password-here" `
  videos
```

---

### Overview (`kubernetes/admissioncontrollers/introduction/README.md`)

# Introduction to Admission controllers

[Admission Webhook](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#what-are-admission-webhooks)

<a href="https://youtu.be/1mNYSn2KMZk" title="Kubernetes"><img src="https://i.ytimg.com/vi/1mNYSn2KMZk/hqdefault.jpg" width="20%" alt="Kubernetes Admission Controllers" /></a> 

<hr/>

## Installation (local)
<hr/>

Create a kind cluster:

```
kind create cluster --name webhook --image kindest/node:v1.20.2
```

## TLS certificate notes for Webhook

<hr/>

In order for our webhook to be invoked by Kubernetes, we need a TLS certificate.<br/>
In this demo I'll be using a self signed cert. <br/>
It's ok for development, but for production I would recommend using a real certificate instead. <br/>

We'll use a very handy CloudFlare SSL tool in a docker container to get this done. <br/>

Follow [Use CFSSL to generate certificates](./tls/ssl_generate_self_signed.md)

After the above, we should have: <br/>
* a Webhook YAML file
* CA Bundle for signing new TLS certificates
* a TLS certificate (Kubernetes secret)
<br/>

## Local Development

<hr/>

We always start with a `dockerfile` since we need a Go dev environment.

```
FROM golang:1.15-alpine as dev-env

WORKDIR /app

```

Build and run the controller

```
# get dev environment: webhook

cd sourcecode
docker build . -t webhook
docker run -it --rm -p 80:80 -v ${PWD}:/app webhook sh

```

We always start with Hello world! <br/>
Let's define our basic main module and a web server

```
go mod init example-webhook
```

New file : `main.go`
```
package main

import (
  "net/http"
	"log"
)

func main() {
  http.HandleFunc("/", HandleRoot)
	http.HandleFunc("/mutate", HandleMutate)
  log.Fatal(http.ListenAndServe(":80", nil))
}

func HandleRoot(w http.ResponseWriter, r *http.Request){
	w.Write([]byte("HandleRoot!"))
}

func HandleMutate(w http.ResponseWriter, r *http.Request){
	w.Write([]byte("HandleMutate!"))
}

```

Build our code and run it

```
export CGO_ENABLED=0
go build -o webhook
./webhook
```

We'll be able to hit the `http://localhost/mutate` endpoint in the browser <br/>

NOTE: In Windows, container networking is not fully supported. Our container exposes port 80, but to access our Kubernetes cluster which runs in another container, we need to enable `--net host` flag. This means exposing port 80 will stop working from here on <br/>

Let's exit the container and start with `--net host` so our container can access our kubernetes `kind` cluster 

```
docker run -it --rm --net host -v ${HOME}/.kube/:/root/.kube/ -v ${PWD}:/app webhook sh
```

We can also test our access to our kubernetes cluster with the config that is mounted in:

```
apk add --no-cache curl
curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl
chmod +x ./kubectl
mv ./kubectl /usr/local/bin/kubectl
```

# Kubernetes 

How do we interact with Kubernetes ? </br>
Kubernetes provides many libraries and we'll interact with some of these today

Since we'll receive webhook events from Kubernetes, we'll need to translate these
requests into objects or structs that we understand.

For this, the serializer is important:

```
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"

var (
	universalDeserializer = serializer.NewCodecFactory(runtime.NewScheme()).UniversalDeserializer()
)

```

To access Kubernetes, we need to define a config and a client using our config. <br/>
We can authenticate with K8s in a number of ways. <br/>

First way is good for local development and thats using a kubeconfig file. <br/>
For production, we'll use a Kubernetes service account with RBAC permissions. <br/>
We'll do both methods today. <br/>

```
# define our config and client
var config *rest.Config
var clientSet *kubernetes.Clientset

# in main()

useKubeConfig := os.Getenv("USE_KUBECONFIG")
kubeConfigFilePath := os.Getenv("KUBECONFIG")

if len(useKubeConfig) == 0 {
		// default to service account in cluster token
		c, err := rest.InClusterConfig()
		if err != nil {
			panic(err.Error())
		}
		config = c
	} else {
		//load from a kube config
		var kubeconfig string

		if kubeConfigFilePath == "" {
			if home := homedir.HomeDir(); home != "" {
				kubeconfig = filepath.Join(home, ".kube", "config")
			} 
		} else {
			kubeconfig = kubeConfigFilePath
		}

    fmt.Println("kubeconfig: " + kubeconfig)

		c, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
		if err != nil {
			panic(err.Error())
		}
		config = c
	}

```

Once we built our kubeconfig, we can instantiate a client to use in our app: 

```
	cs, err := kubernetes.NewForConfig(config)
  if err != nil {
    panic(err.Error())
  }
  clientSet = cs
```

And we'll need to import the dependencies for this:

```
	"os"
	"fmt"
	"path/filepath"
	"k8s.io/client-go/kubernetes"
	rest "k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/util/homedir"
```

Since we're also using the client-go library, we need to install the same version as 
the other libraries as we can see in the `go.mod` file, we're using `v0.21.0`

```
go get k8s.io/client-go@v0.21.0
```

Rebuild to ensure no errors:

```
go build -o webhook
```

Test with a kubeconfig

```
export USE_KUBECONFIG=true
./webhook
```

To test our access, let's create a `test.go` and return pods from the kube-system namespace

```
#test.go
package main 

import ()

func test(){
}
```

Use our global clientset defined in main() and get all pods 

```
	pods, err := clientSet.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{})

	if err != nil {
		panic(err.Error())
	}

	fmt.Printf("There are %d pods in the cluster\n", len(pods.Items))

```

Define dependencies:

```
	"context"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"fmt"
```

And finally invoking it in main() calling `test()` <br/>

Run and test our Kubernetes access:

```
bash-5.0# ./webhook
kubeconfig: /root/.kube/config
There are 11 pods in the cluster
```

## Mutating Webhook

Now that we have a working app that can talk to Kubernetes, lets implement our webhook endpoint and deploy it to kubernetes to see what type of message the API server sends us when events happen

Firstly, we need to enable a TLS endpoint </br>
Let's take some parameters where we can set the path to the TLS certificate and port number to run on. </br>

Import flag dependency:

```
"flag"
"strconv"
```

Define our parameters for cert configuration

```
type ServerParameters struct {
	port           int    // webhook server port
	certFile       string // path to the x509 certificate for https
	keyFile        string // path to the x509 private key matching `CertFile`
}

var parameters ServerParameters

# in main()

  flag.IntVar(&parameters.port, "port", 8443, "Webhook server port.")
  flag.StringVar(&parameters.certFile, "tlsCertFile", "/etc/webhook/certs/tls.crt", "File containing the x509 Certificate for HTTPS.")
  flag.StringVar(&parameters.keyFile, "tlsKeyFile", "/etc/webhook/certs/tls.key", "File containing the x509 private key to --tlsCertFile.")
  flag.Parse()

# start our web server exposing TLS endpoint 

	log.Fatal(http.ListenAndServeTLS(":" + strconv.Itoa(parameters.port), parameters.certFile, parameters.keyFile, nil))

```

Let's capture the request coming from Kubernetes and write it to local file for analysis

```
# dependencies
"io/ioutil"

# HandleMutate
body, err := ioutil.ReadAll(r.Body)
err = ioutil.WriteFile("/tmp/request", body, 0644)
if err != nil {
  panic(err.Error())
}
```

## Deployment
<hr/>

Let's built what we have and deploy it to our kubernetes cluster
We will firstly need to add a build step to our `dockerfile` to build the code </br>
And we'll also need to create a smaller runtime layer in our `dockerfile`

Full `dockerfile` :

```
FROM golang:1.15-alpine as dev-env

WORKDIR /app

FROM dev-env as build-env
COPY go.mod /go.sum /app/
RUN go mod download

COPY . /app/

RUN CGO_ENABLED=0 go build -o /webhook

FROM alpine:3.10 as runtime

COPY --from=build-env /webhook /usr/local/bin/webhook
RUN chmod +x /usr/local/bin/webhook

ENTRYPOINT ["webhook"]

```

Let's build the container and push it to a registry:

```
docker build . -t aimvector/example-webhook:v1
docker push aimvector/example-webhook:v1
```

```

# apply generated secret
kubectl -n default apply -f ./tls/example-webhook-tls.yaml


kubectl -n default apply -f rbac.yaml
kubectl -n default apply -f deployment.yaml
kubectl -n default get pods

# ensure above pods are running first

kubectl -n default apply -f webhook.yaml

```

# Deploy a demo that needs mutation

```
kubectl -n default  apply -f ./demo-pod.yaml
```

We should now be able to see an example request from Kubernetes sitting in our `tmp/request` location. This request is called an "AdmissionReview" <br/>

Kubernetes sends us an `AdmissionReview` and expects an AdmissionResponse back. <br/>

We can copy this review locally and use it for development so we dont need to deploy to 
kubernetes constantly. For example:

```
kubectl cp example-webhook-756bcb566b-9kxjp:/tmp/request ./mock-request.json
```

So lets grab the info from the admission request, so we can do something with it


```
  //dependencies 
  "k8s.io/api/admission/v1beta1"
  "errors"

  //HandleMutate()

  var admissionReviewReq v1beta1.AdmissionReview

	if _, _, err := universalDeserializer.Decode(body, nil, &admissionReviewReq); err != nil {
		w.WriteHeader(http.StatusBadRequest)
		fmt.Errorf("could not deserialize request: %v", err)
	} else if admissionReviewReq.Request == nil {
		w.WriteHeader(http.StatusBadRequest)
		errors.New("malformed admission review: request is nil")
	}

	fmt.Printf("Type: %v \t Event: %v \t Name: %v \n",
		admissionReviewReq.Request.Kind,
		admissionReviewReq.Request.Operation,
		admissionReviewReq.Request.Name,
	)

```

# Mutation

Firstly we need to grab the Pod object from the admission request


```
//dependencies 
apiv1 "k8s.io/api/core/v1"

var pod apiv1.Pod

err = json.Unmarshal(admissionReviewReq.Request.Object.Raw, &pod)

if err != nil {
  fmt.Errorf("could not unmarshal pod on admission request: %v", err)
}

```

To perform a simple mutation on the object before the Kubernetes API sees the object, we can apply a patch to the operation.

```
//global

type patchOperation struct {
	Op    string      `json:"op"`
	Path  string      `json:"path"`
	Value interface{} `json:"value,omitempty"`
}

//HandleMutate()
var patches []patchOperation
```

Add a label that we can inject on the pod
We have to craft the kubernetes object we want to patch. <br/>
For example, a label is part of the Metadata API on the Pod spec

https://pkg.go.dev/k8s.io/api/core/v1#Pod

```
// Get existing Metadata labels

labels := pod.ObjectMeta.Labels
labels["example-webhook"] = "it-worked"

patches = append(patches, patchOperation{
				Op:    "add",
				Path:  "/metadata/labels",
				Value: labels,
		})
```

Once you have completed all your patching, convert the patches to byte slice:

```
	patchBytes, err := json.Marshal(patches)
	if err != nil {
		fmt.Errorf("could not marshal JSON patch: %v", err)
	}
```

Add it to the admission response

```
  admissionReviewResponse := v1beta1.AdmissionReview{
      Response: &v1beta1.AdmissionResponse{
        UID: admissionReviewReq.Request.UID,
        Allowed: true,
      },
    }

  admissionReviewResponse.Response.Patch = patchBytes

  bytes, err := json.Marshal(&admissionReviewResponse)
    if err != nil {
      fmt.Errorf("marshaling response: %v", err)
    }
  
  w.Write(bytes)

  //dependencies 
  "encoding/json"
```

# Build and push the updates

```
docker build . -t aimvector/example-webhook:v1
docker push aimvector/example-webhook:v1
```

# Delete all pods to get latest image

```
kubectl delete pods --all
```

# Redeploy our demo pod and see the mutations

```
kubectl -n default  apply -f ./demo-pod.yaml
```

See the injected label

```
kubectl get pods --show-labels
```

---

### Overview (`kubernetes/autoscaling/vertical-pod-autoscaling/readme.md`)

# Vertical Pod Autoscaling

<a href="https://youtu.be/jcHQ5SKKTLM" title="Kubernetes"><img src="https://i.ytimg.com/vi/jcHQ5SKKTLM/hqdefault.jpg" width="20%" alt="vertical auto scaling" /></a> 

## We need a Kubernetes cluster

Lets create a Kubernetes cluster to play with using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/)

```
kind create cluster --name vpa --image kindest/node:v1.30.4
```
<hr/>

## Metric Server

<br/>

* For `Cluster Autoscaler` - On cloud-based clusters, Metric server may already be installed. <br/>
* For `HPA` - We're using kind

[Metric Server](https://github.com/kubernetes-sigs/metrics-server) provides container resource metrics for use in autoscaling pipelines <br/>

Because I run K8s `1.30` in `kind`, the Metric Server version i need is `0.7.2` <br/>
We will need to deploy Metric Server [0.7.2](https://github.com/kubernetes-sigs/metrics-server/releases/tag/v0.7.2) <br/>
I used `components.yaml`from the release page link above. <br/>

<b>Important Note</b> : For Demo clusters (like `kind`), you will need to disable TLS <br/>
You can disable TLS by adding the following to the metrics-server container args <br/>

<b>For production, make sure you remove the following :</b> <br/>

```
- --kubelet-insecure-tls
```

Deployment: <br/>

```
cd kubernetes\autoscaling
kubectl -n kube-system apply -f .\components\metric-server\components.yaml

#test 
kubectl -n kube-system get pods

#note: wait for metrics to populate!
kubectl top nodes

```

## VPA

VPA docs [here](https://github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler#install-command) <br/>
Let's install the VPA from a container that can access our cluster

```
cd kubernetes/autoscaling/vertical-pod-autoscaling
docker run -it --rm -v ${HOME}:/root/ -v ${PWD}:/work -w /work --net host debian:bookworm bash

# install git
apt-get update && apt-get install -y git curl nano

# install kubectl 
curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl
chmod +x ./kubectl
mv ./kubectl /usr/local/bin/kubectl


cd /tmp
git clone https://github.com/kubernetes/autoscaler.git
cd autoscaler/vertical-pod-autoscaler/

# you may need to generate VPA certificates 
bash ./pkg/admission-controller/gencerts.sh

# deploy the VPA
./hack/vpa-up.sh

# after few seconds, we can see the VPA components in:

kubectl -n kube-system get pods
```

## Build and deploy example app

```
# build

cd kubernetes\autoscaling\components\application
docker build . -t aimvector/application-cpu:v1.0.0

# push
docker push aimvector/application-cpu:v1.0.0

# deploy 
kubectl apply -f deployment.yaml

# metrics
kubectl top pods

```

## Generate some traffic

Let's deploy a simple traffic generator pod

```
cd kubernetes\autoscaling\components\application
kubectl apply -f .\traffic-generator.yaml

# get a terminal to the traffic-generator
kubectl exec -it traffic-generator -- sh

# install wrk
apk add --no-cache wrk

# simulate some load
wrk -c 5 -t 5 -d 99999 -H "Connection: Close" http://application-cpu

```

# Deploy an example VPA

```

kubectl apply -f .\vertical-pod-autoscaling\vpa.yaml

kubectl describe vpa application-cpu

```

# Deploy Goldilocks

```
cd /tmp
git clone https://github.com/FairwindsOps/goldilocks.git
cd goldilocks/hack/manifests/

kubectl create namespace goldilocks
kubectl -n goldilocks apply -f ./controller
kubectl -n goldilocks apply -f ./dashboard


kubectl label ns default goldilocks.fairwinds.com/enabled=true
kubectl label ns default goldilocks.fairwinds.com/vpa-update-mode="off"

kubectl -n goldilocks port-forward svc/goldilocks-dashboard 80

```

---

### Overview (`kubernetes/gateway-api/agentgateway/README.md`)

# Introduction to Agent Gateway

|   |   |
|---|---|
|<a href="https://youtu.be/QZUZCz1Q1Vs" title="llmgateway"><img src="https://i.ytimg.com/vi/QZUZCz1Q1Vs/hqdefault.jpg" width="40%" alt="llmgateway" /></a>   | <a href="https://youtu.be/_EfxYSpo2mo" title="mcpgateway"><img src="https://i.ytimg.com/vi/_EfxYSpo2mo/hqdefault.jpg" width="40%" alt="mcpgateway" /></a>  |

[Official Site](https://agentgateway.dev) | [GitHub](https://github.com/agentgateway/agentgateway) | [Documentation](https://agentgateway.dev/docs/kubernetes/latest/)

## Prerequisites

To get started, you will need to follow the [Introduction to Gateway API](../README.md) first. </br>
You'll need an understanding of the Gateway API. </br>

<b>In the introduction guide, you will:</b>
* Create a local Kubernetes cluster
* Install the Gateway API CRDs
* Example models, either in cluster or external (OpenAI, Anthropic, Gemini)

This will allow us access to the Gateway API so we can go ahead and deploy a Gateway API controller to use. </br

Let's start with [Official Site](https://agentgateway.dev) | [GitHub](https://github.com/agentgateway/agentgateway) | [Documentation](https://agentgateway.dev/docs/kubernetes/latest/)

## Install AgentGateway

Agent Gateway is distributed as two Helm charts: one for its CRDs, one for the control plane. </br>


To get the version you want, you can use the agent gateway [Github Release page](https://github.com/agentgateway/agentgateway/releases). </br>
For this guide, we will use 1.1.0. </br>

You can list helm chart versions too:

```shell
helm show chart oci://cr.agentgateway.dev/charts/agentgateway-crds
helm show chart oci://cr.agentgateway.dev/charts/agentgateway
```

```shell
CHART_VERSION="v1.1.0"

# Install CRDs

helm upgrade -i agentgateway-crds oci://cr.agentgateway.dev/charts/agentgateway-crds \
  --create-namespace \
  --namespace agentgateway-system \
  --version $CHART_VERSION

# Install ControlPlane
helm upgrade -i agentgateway oci://cr.agentgateway.dev/charts/agentgateway \
  --namespace agentgateway-system \
  --version $CHART_VERSION \
  --set controller.image.pullPolicy=Always \
  --wait
```

Check our installation

```shell
# check pods are running
kubectl -n agentgateway-system get pods

# check logs
kubectl -n agentgateway-system logs -l app.kubernetes.io/name=agentgateway
```


## Check the AgentGateway Class

Verify the `GatewayClass` was registered:

```shell
kubectl get gatewayclass
```

## Install an AgentGateway Gateway

```shell
kubectl apply -f kubernetes/gateway-api/agentgateway/01-gateway.yaml

# we can see our gateway dataplane pods
kubectl get pods
kubectl get svc

# check gateway 
kubectl get gateway

# port forward for access
kubectl -n default port-forward svc/agentgateway 8080:80
```

## Features

* Route to [LLM Models](#feature-llm-routing)
* Route to [MCP Servers](#feature-mcp-routing)

### Feature: LLM Routing

AgentGateway introduces CRD `AgentgatewayBackend`. </br>
`AgentgatewayBackend` is an LLM provider as a routable backend.</br>


#### Create an LLM Backend: Gemini

If your LLM Provider needs an API key, we need a Kubernetes secret for it:


```shell

export GEMINI_API_KEY=""

kubectl apply -f- <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: gemini-secret
type: Opaque
stringData:
  Authorization: $GEMINI_API_KEY
EOF
```

Create the LLM backend: </br>

Instead of an HTTPRoute routing to an upstream backend `service`, it uses an `AgentgatewayBackend`

```shell
kubectl apply -f kubernetes/gateway-api/agentgateway/llm/gemini/02-backend-gemini.yaml
```

Route traffic from the Gateway to the backend using a standard `HTTPRoute`:

```shell
kubectl apply -f kubernetes/gateway-api/agentgateway/llm/03-httproute.yaml
```

Test our HTTPRoute:

```shell
curl "localhost:8080/v1/chat/completions" \
  -H "Content-Type: application/json" \
  -d '{
      "messages": [{"role": "user", 
      "content": "What is Kubernetes in one sentence?"}]
  }' | jq
```

We can route by path as well. This means one domain and route to provider by path

```shell
kubectl apply -f kubernetes/gateway-api/agentgateway/llm/04-httproute-gemini.yaml
```

```shell
curl "localhost:8080/ai/gemini/v1/chat/completions" \
  -H "Content-Type: application/json" \
  -d '{
      "messages": [{"role": "user", 
      "content": "What is Kubernetes in one sentence?"}]
  }' | jq
```

#### Create an LLM Backend: Anthropic

The same pattern works for any supported provider.</br>

```shell
export ANTHROPIC_API_KEY=""

kubectl apply -f- <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: anthropic-secret
type: Opaque
stringData:
  Authorization: $ANTHROPIC_API_KEY
EOF
```

```shell
kubectl apply -f kubernetes/gateway-api/agentgateway/llm/anthropic/05-backend-anthropic.yaml
```

```shell
kubectl apply -f kubernetes/gateway-api/agentgateway/llm/06-httproute-anthropic.yaml
```

Route to Anthropic Claude:

```shell
curl "localhost:8080/ai/claude/v1/chat/completions" \
  -H "Content-Type: application/json" \
  -d '{
      "messages": [{"role": "user", 
      "content": "What is Kubernetes in one sentence?"}]
  }' | jq
```

Now we have implemented LLM routing by path using `HTTPRoute`:

`localhost:8080/ai/gemini` --> `gemini flash` </br>
`localhost:8080/ai/claude` --> `claude sonnet` </br>

### Feature: MCP Routing

To route to MCP servers, we will need some example MCP servers setup. </br>
In this example we will use a Kubernetes MCP and setup a read-only `ServiceAccount` so it can read our cluster. </br>

```shell
kubectl apply -f kubernetes/gateway-api/agentgateway/mcp/kubernetes/02-mcp.yaml

# check mcp server
kubectl get pods
kubectl get sa
kubectl get service
```

This MCP server will serve traffic on `/mcp` </br>
Let's create a backend and an `HTTPRoute` for that. </br>

#### Create an MCP Backend: Kubernetes

Instead of an HTTPRoute routing to an upstream backend `service`, it uses an `AgentgatewayBackend` </br>
Create the MCP backend: </br>
```shell
kubectl apply -f kubernetes/gateway-api/agentgateway/mcp/kubernetes/03-mcp-backend.yaml

# check the backend
kubectl get agentgatewaybackend
kubectl describe agentgatewaybackend
```

Route traffic from the Gateway to the backend using a standard `HTTPRoute`:

```shell
kubectl apply -f kubernetes/gateway-api/agentgateway/mcp/04-httproute.yaml
```

#### Test our MCP Backend: Kubernetes

Initialize session to get a `mcp-session-id` from the response:

```shell
curl -s -i http://localhost:8080/ai/mcps/kubernetes/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1}'


```

MCP Tool call to list namespaces in our cluster: 
```shell
MCP_SESSION_ID=''

curl -s http://localhost:8080/ai/mcps/kubernetes/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Mcp-Session-Id: ${MCP_SESSION_ID}" \
  -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"namespaces_list","arguments":{}},"id":3}'
```

#### Create an MCP Backend: Github

To showcase routing to and federating different MCPs under different or the same endpoints, we will create another MCP server. We have use Github MCP

```shell

kubectl apply -f kubernetes/gateway-api/agentgateway/mcp/github/05-mcp.yaml

# check mcp server
kubectl get pods
kubectl get sa
kubectl get service
```

Then we create a backend to route to:

```shell
kubectl apply -f kubernetes/gateway-api/agentgateway/mcp/github/06-mcp-backend.yaml

# check the backend
kubectl get agentgatewaybackend
kubectl describe agentgatewaybackend
```

Route traffic from the Gateway to the backend using a standard `HTTPRoute`:

```shell
kubectl apply -f kubernetes/gateway-api/agentgateway/mcp/07-httproute.yaml
```

#### Test our MCP Backend: Github

Initialize session to get a `mcp-session-id` from the response:

```shell
curl -s -i http://localhost:8080/ai/mcps/github/mcp \
       -H "Content-Type: application/json" \
       -H "Accept: application/json, text/event-stream" \
       -H "Authorization: Bearer ${GITHUB_PERSONAL_ACCESS_TOKEN}" \
       -d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1}'
```

MCP Tool call to search repositories: 
```shell
MCP_SESSION_ID=''

```shell
curl -s http://localhost:8080/ai/mcps/github/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer ${GITHUB_PERSONAL_ACCESS_TOKEN}" \
  -H "Mcp-Session-Id: ${MCP_SESSION_ID}" \
  -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"search_repositories","arguments":{"query":"kubernetes gateway-api","sort":"stars","perPage":5}},"id":3}'
```

Now we have implemented MCP routing by path using `HTTPRoute`:

`localhost:8080/ai/mcps/kubernetes` --> `kubernetes MCP service` </br>
`localhost:8080/ai/mcps/github` --> `Github MCP service` </br>

#### Federate our MCPs under one endpoint 

To federate endpoints we have to define an `AgentGatewayBackend` that combines our MCPs 

```shell
kubectl apply -f kubernetes/gateway-api/agentgateway/mcp/08-federatedbackend.yaml
```

Deploy an `HTTPRoute` to our single federated endpoint: 

```shell
kubectl apply -f kubernetes/gateway-api/agentgateway/mcp/09-httproute.yaml
```

Get session ID: 
```shell
curl -s -i http://localhost:8080/ai/mcps/mcp \
       -H "Content-Type: application/json" \
       -H "Accept: application/json, text/event-stream" \
       -H "Authorization: Bearer ${GITHUB_PERSONAL_ACCESS_TOKEN}" \
       -d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1}'
```

Now we can proceed to call tools under the single endpoint for both Kubernetes and Github MCP.

---

### Overview (`kubernetes/gateway-api/cilium/README.md`)

# Introduction to Cilium: Gateway API

<a href="https://youtu.be/q4bucRkDOm8" title="cilium"><img src="https://i.ytimg.com/vi/q4bucRkDOm8/hqdefault.jpg" width="40%" alt="cilium" /></a>

## Prerequisites

This guide will assume you have a basic understanding of the Gateway API. </br>
If it's your first time using Gateway API, please see the [Introduction to Gateway API guide](../README.md) first </br>

This guide has a slight twist from the other Gateway APIs. </br>
It's because Cilium is a network CNI for Kubernetes.</br>
This means we need to disable the default kubernetes CNI networking in our cluster, so we will be creating it slightly differently. </br>

We also need to install the Gateway API CRDs followed by Cilium, before we deploy all our example applications. </br>

## What is Cilium

[Cilium](https://cilium.io/) leverages eBPF technology to provide high-performance networking, security and observability for containerised applications, especially in Kubernetes. </br>

eBPF (Extended Berkeley Packet Filter) is a technology that allows us to run custom programs safely in the Linux Kernel without the need for modules, changing source code. </br>

These programs allow programs like Cilium to add security, performance and observability to the network. </br>

### Create a kubernetes cluster

```shell
kind create cluster --name gatewayapi --image kindest/node:v1.34.0 --config kubernetes/gateway-api/cilium/kind.yaml
```

Test our cluster and makes sure `kubectl` is configured for it:

```shell
kubectl get nodes
NAME                       STATUS     ROLES           AGE   VERSION
gatewayapi-control-plane   NotReady   control-plane   93s   v1.34.0
gatewayapi-worker          NotReady   <none>          83s   v1.34.0
```

The nodes will remain in a `NotReady` state until we unstall our network plugin, which we will do with Cilium. </br>

### Install Gateway API CRDs

Now we can deploy the Gateway API CRDs following our [Introduction to Gateway API guide](../README.md#gateway-api-crds). </br>
The CRDs need to be in place when we install Cilium. </br>

### Configuration

It's always good to get a grip on the [default helm values](./default-values.yaml) to see what we can do with the chart. </br>

In this demo we'll only focus on the `gatewayAPI` section of the `helm` values file. </br>
We created our own [values.yaml](./values.yaml) which allows us to enable Gateway API, and use Cilium as the kube proxy replacement so it takes over our networking. </br>

### Installation 

Install:

```shell
CHART_VERSION="1.19.4"
helm repo add cilium https://helm.cilium.io
helm repo update
helm search repo cilium --versions

# default configuration
helm show values cilium/cilium > kubernetes/gateway-api/cilium/default-values.yaml

helm upgrade --install cilium cilium/cilium \
  --version $CHART_VERSION \
  --namespace kube-system \
  --values kubernetes/gateway-api/cilium/values.yaml
```

Check our installation

```shell
# check the pods
kubectl -n kube-system get pods

# nodes should be Ready once above is Running
kubectl get nodes
NAME                       STATUS   ROLES           AGE   VERSION
gatewayapi-control-plane   Ready    control-plane   18m   v1.34.0
gatewayapi-worker          Ready    <none>          18m   v1.34.0
```

We can also check the Cilium Operator logs to ensure we are smooth sailing:
```shell
kubectl -n kube-system logs -l app.kubernetes.io/name=cilium-operator
kubectl -n kube-system logs -l app.kubernetes.io/name=cilium-envoy
kubectl -n kube-system logs -l app.kubernetes.io/name=cilium-agent
```

<b>In the [introduction guide](../README.md), you will:</b>

* Deploy example apps to our cluster
* Have Domains for our traffic
* Have TLS certificates

This will allow us access to the Gateway API so we can go ahead and deploy a Gateway API controller to use. </br

## Install a Cilium Gateway Class

```shell
kubectl apply -f kubernetes/gateway-api/cilium/01-gatewayclass.yaml
```

The `CiliumGatewayClassConfig` CRD for Cilium allows us to customize the Gateway class. </br>

This allows us to change the deployment and services for gateways that use this class. </br> In our case, we can set the `service.type = NodePort`


```shell
kubectl apply -f kubernetes/gateway-api/cilium/01.1-gatewayclass-config.yaml
```

## Install a Cilium Gateway

```shell
kubectl apply -f kubernetes/gateway-api/cilium/02-gateway.yaml
```

### Demo notes on NodePort service

#### Patch the NodePort service 

In `kind`, we don't have a load balancer service, so we'll use a `NodePort` service for demo purpose. </br>
Cilium creates a NodePort service for the gateway automatically. 
In Kubernetes this NodePort service will have automatically generated ports which won't match our configuration. </br>
Therefore, we need to pin the NodePorts to match the `extraPortMappings` in `kind.yaml` (30080 for HTTP, 30443 for HTTPS):

```shell
SVC=$(kubectl get svc -n default -l gateway.networking.k8s.io/gateway-name=gateway-api -o name)
kubectl patch -n default $SVC --type='json' -p='[
  {"op":"replace","path":"/spec/ports/0/nodePort","value":30080},
  {"op":"replace","path":"/spec/ports/1/nodePort","value":30443}
]'
```

## Install the Cilium CLI

```
CILIUM_CLI_VERSION=v0.19.0
CLI_ARCH=amd64
curl -L --fail --remote-name-all https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}
sha256sum --check cilium-linux-${CLI_ARCH}.tar.gz.sha256sum
tar xzvf cilium-linux-${CLI_ARCH}.tar.gz -C /usr/local/bin
rm cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}
```

## HTTP Traffic management

Feel free to quickly run through the basic [traffic management table](../README.md#traffic-management-features--http-routes) for using `HTTPRoute` routing for traffic. </br>

<i>Note: HTTPRoute features are not specific to this controller and should be available to any other gateway API controller that you choose.</i>

---

### Overview (`kubernetes/gateway-api/envoy/README.md`)

# Introduction to Envoy: Gateway API

<a href="https://youtu.be/me_5W_Q4ZWg" title="envoy"><img src="https://i.ytimg.com/vi/me_5W_Q4ZWg/hqdefault.jpg" width="40%" alt="envoy" /></a>

## Prerequisites

To get started, you will need to follow the [Introduction to Gateway API](../README.md) first. </br>
You'll need an understanding of the Gateway API. </br>

<b>In the introduction guide, you will:</b>
* Create a local Kubernetes cluster
* Install the Gateway API CRDs
* Deploy example apps to our cluster
* Have Domains for our traffic
* Have TLS certificates

This will allow us access to the Gateway API so we can go ahead and deploy a Gateway API controller to use. </br

## What is Envoy

It's important to take a step back and fully understand and appreciate what Envoy is. </br>
Envoy is not only a Gateway API, that's just one of its features. </br>
Let's take a look at the [Official Site](https://www.envoyproxy.io/) and jump to the documentation. </br>
Envoy has a separate web page for the Gateway API feature. </br>

## Envoy: Gateway API controller

Envoy has a `helm` chart specifically for the gateway product. </br>
The `helm` chart relies on the Gateway API CRD's we installed in our cluster already as well as Envoy Proxy. </br>

### Installation 

We can install the helm chart with `helm install` or upgrade it with `helm upgrade`:

```shell

CHART_VERSION="v1.6.0"
helm show chart oci://docker.io/envoyproxy/gateway-helm
helm show values oci://docker.io/envoyproxy/gateway-helm > kubernetes/gateway-api/envoy/default-values.yaml

helm install envoy-gateway oci://docker.io/envoyproxy/gateway-helm \
  --version $CHART_VERSION \
  --values kubernetes/gateway-api/envoy/values.yaml \
  --namespace envoy-gateway-system \
  --create-namespace
```

Upgrade:

```shell
helm upgrade envoy-gateway oci://docker.io/envoyproxy/gateway-helm \
  --values kubernetes/gateway-api/envoy/values.yaml \
  --version $CHART_VERSION \
  --namespace envoy-gateway-system
```

### Configuration

Most of the Gateway API controllers are installed using `helm`. </br>
Before we install it, let's take a look at the [default-values.yaml](./default-values.yaml)

### Check Installation

Now we should have the Envoy gateway API controller up and running. </br>
This is not the gateway itself, but the controller that will manage the CRDs we get access to and implement some gateway API CRDs. </br>

```shell
# check the controller pods
kubectl -n envoy-gateway-system get pods

# check the controller pod logs 
kubectl -n envoy-gateway-system logs -l app.kubernetes.io/instance=envoy-gateway

```

## Install an Envoy Gateway Class

```shell
kubectl apply -f kubernetes/gateway-api/envoy/01-gatewayclass.yaml
```

## Install an Envoy Gateway

```shell
kubectl apply -f kubernetes/gateway-api/envoy/02-gateway.yaml
```

When we apply the gateway, we get a new gateway api pod. 

```shell
# check the new gateway-api pod
kubectl -n envoy-gateway-system get pods

# we also have a new service
kubectl -n envoy-gateway-system get svc
```

### Gateway Configuration

`EnvoyProxy` is the CRD that allows us to configure each gateway API instance. </br>
For example, we can change the `deployment` or `service` of the instance like so:

```shell
kubectl apply -f kubernetes/gateway-api/envoy/02.1-gateway-config.yaml

# we should see 2 replicas 
kubectl -n envoy-gateway-system get deploy
kubectl -n envoy-gateway-system get pods

# port forward for access (get the correct service)
kubectl -n envoy-gateway-system get svc
kubectl -n envoy-gateway-system port-forward svc/envoy-gateway-default 80
```

## HTTP Traffic management

Feel free to quickly run through the basic [traffic management table](../README.md#traffic-management-features--http-routes) for using `HTTPRoute` routing for traffic. </br>

<i>Note: HTTPRoute features are not specific to this controller and should be available to any other gateway API controller that you choose.</i>

## Gateway API Extensions

Envoy Gateway has its own extensions on top of the native Gateway API features we've seen so far, using custom CRD's.

### Client Traffic Policies

#### Connection Limit

Envoy allows us to limit connections to gateways & listeners. </br>
We can use tools like `hey` to demonstrate connection limits.

```shell

hey -c 10 -q 1 -z 10s -host "example-app.com" http://localhost:80/api/go/status

kubectl apply -f kubernetes/gateway-api/envoy/08-clientpolicy-connectionlimit.yaml

hey -c 10 -q 1 -z 10s -host "example-app.com" http://localhost:80/api/go/status
```

#### Mutual TLS

Currently we have a listener for TLS (HTTPs). For normal TLS, the client does not have to pass a private key. </br>
Envoy supports mutual TLS

```shell

# tell Envoy about the CA cert 
kubectl create secret generic secret-tls-ca --from-file=rootCA.pem=kubernetes/gateway-api/tls/rootCA.pem

# generate client certs for curl
mkcert -client client-user.com

kubectl apply -f kubernetes/gateway-api/envoy/09-clientpolicy-mtls.yaml

# browser TLS should now be broken "Access to example-app.com was denied"
# This is because a client needs to pass certificates to trust 

curl -v -H "Host: example-app.com" \
--cert kubernetes/gateway-api/tls/client-user.com-client.pem \
--key kubernetes/gateway-api/tls/client-user.com-client-key.pem \
--cacert kubernetes/gateway-api/tls/rootCA.pem \
https://example-app.com/api/go/status

```

### Backend Traffic Policies

#### Local and Global Rate limits

We can rate limit each HTTP route using Local Rate Limit:

```shell

kubectl apply -f kubernetes/gateway-api/envoy/10-backendpolicy-ratelimit.yaml

for i in {1..4}; do
curl -I --header "x-user-id: one" http://example-app.com/api/go/status ; sleep 1;
done

```

### Security Policies

### CORS 

```shell

# port-forward to set pre-cors
kubectl port-forward svc/web-app 8000:80

kubectl apply -f kubernetes/gateway-api/envoy/11-securitypolicy-cors.yaml

```

### Basic Auth

```shell

#cleanup client policies to avoid conflict
kubectl delete clienttrafficpolicies mtls-policy
kubectl delete SecurityPolicy go-route-cors

# port-forward to 443 
kubectl -n envoy-gateway-system port-forward svc/envoy-gateway-default 443

#install htpasswd (alpine)
apk add apache2-utils
htpasswd -cbs .htpasswd bob password123

kubectl create secret generic basic-auth --from-file=.htpasswd

kubectl apply -f kubernetes/gateway-api/envoy/12-securitypolicy-basicauth.yaml

```

### API Auth 

```shell

#cleanup previous basic auth policy
kubectl delete SecurityPolicy go-route-basicauth

kubectl apply -f kubernetes/gateway-api/envoy/13-securitypolicy-apiauth.yaml

# port-forward
kubectl -n envoy-gateway-system port-forward svc/envoy-gateway-default 443

```

You can see `Client authentication failed.` in the browser. 
We need to pass the header 

```
curl -v -H "Host: example-app.com" \
-H 'x-api-key: secret123' \
--cert kubernetes/gateway-api/tls/client-user.com-client.pem \
--key kubernetes/gateway-api/tls/client-user.com-client-key.pem \
--cacert kubernetes/gateway-api/tls/rootCA.pem \
https://example-app.com/api/go/status
```

Logs:

```shell
kubectl -n envoy-gateway-system logs -l app.kubernetes.io/managed-by=envoy-gateway
```

---

### Overview (`kubernetes/gateway-api/haproxy/README.md`)

# Introduction to HAProxy: Gateway API

<a href="https://youtu.be/5clOvqDh-zA" title="haproxy"><img src="https://i.ytimg.com/vi/5clOvqDh-zA/hqdefault.jpg" width="40%" alt="haproxy" /></a>

## Prerequisites

To get started, you will need to follow the [Introduction to Gateway API](../README.md) first. </br>
You'll need an understanding of the Gateway API. </br>

<i><b>Important Note: According to testing and [HAProxy documentation](https://www.haproxy.com/documentation/kubernetes-ingress/gateway-api/enable-gateway-api/#deploy-gateway-api-resources), it currently only supports version v0.5.1</b></i>
<i>Tried `GatewayClass` with `gateway.networking.k8s.io/v1` and does not work</i>

<b>In the introduction guide, you will:</b>
* Create a local Kubernetes cluster
* Install the Gateway API CRDs

```shell
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v0.5.1/experimental-install.yaml
```

* Deploy example apps to our cluster
* Have Domains for our traffic
* Have TLS certificates

This will allow us access to the Gateway API so we can go ahead and deploy a Gateway API controller to use. </br

## Introduction

Let's take a look at the [Official Documentation](https://www.haproxy.com/documentation/). </br>

At the time of this guide the HAProxy Gateway API solution is built alongside the HAProxy Kubernetes Ingress controller. So its one solution that can achieve both Ingress and Gateway functionality. </br>

The HAProxy Unified Gateway is still under development at the time of this guide. </br>

### Configuration

Most of the Gateway API controllers are installed using `helm`. </br>
Before we install it, let's take a look at the [default-values.yaml](./default-values.yaml)

### Installation 

We can install the helm chart with `helm install` or with `kubectl` as stated in the documentation. Important to know that the below Helm chart installs the ingress controller. In order to enable Gateway API functionality, we need to turn it on using our values file. </br>

```shell

CHART_VERSION="1.48.0"
helm repo add haproxytech https://haproxytech.github.io/helm-charts
helm repo update
helm search repo haproxytech --versions

# see the default configuration
helm show values haproxytech/kubernetes-ingress \
> kubernetes/gateway-api/haproxy/default-values.yaml

#apply the cluster roles and bindings (RBAC) to give HAProxy access to Gateway resources
kubectl apply -f https://raw.githubusercontent.com/haproxytech/kubernetes-ingress/master/deploy/tests/config/experimental/gwapi-rbac.yaml

helm install haproxy-kubernetes-ingress haproxytech/kubernetes-ingress \
  --version $CHART_VERSION \
  --create-namespace \
  --namespace haproxy-controller \
  --values kubernetes/gateway-api/haproxy/values.yaml

```

### Check Installation

Now we should have the HAProxy controller up and running. </br>
This is not the gateway itself, but the controller that will manage the CRDs we get access to and implement some gateway API CRDs. </br>

```shell
# check the controller pods
kubectl -n haproxy-controller get pods

# check the controller pod logs 
kubectl -n haproxy-controller logs -l app.kubernetes.io/name=kubernetes-ingress

```

## Install an HAProxy Gateway Class

Please note `apiVersion: gateway.networking.k8s.io/v1alpha2` is used and `v1` does not work. :

```shell
kubectl apply -f kubernetes/gateway-api/haproxy/01-gatewayclass.yaml
```

## Install an HAProxy Gateway

```shell
kubectl apply -f kubernetes/gateway-api/haproxy/02-gateway.yaml
```

## Traffic Routing 

At the time of this guide, HAProxy only supports TCPRoute, and not HTTPRoute like the other controllers we covered thus far. </br>

```shell
kubectl apply -f kubernetes/gateway-api/haproxy/03-tcproute.yaml
```

```shell
kubectl -n haproxy-controller port-forward svc/haproxy-kubernetes-ingress 80
```

---

### Overview (`kubernetes/gateway-api/istio/README.md`)

# Introduction to Istio: Gateway API

<a href="https://youtu.be/du9h71WYToo" title="istio"><img src="https://i.ytimg.com/vi/du9h71WYToo/hqdefault.jpg" width="40%" alt="istio" /></a>

## Prerequisites

To get started, you will need to follow the the [Introduction to Gateway API](../README.md) first. </br>
You'll need an understanding of the Gateway API. </br>

<b>In the introduction guide, you will:</b>
* Create a local Kubernetes cluster
* Install the Gateway API CRDs
* Deploy example apps to our cluster
* Have Domains for our traffic
* Have TLS certificates

This will allow us access to the Gateway API so we can go ahead and deploy a Gateway API controller to use. </br

## What is Istio

Before diving into installing Istio, its very important to understand what Istio is. </br>
<b>Istio is not only a Gateway API</b>, its a service mesh. </br>
In a distributed system with service-to-service communication, a service mesh places a proxy next to each service. </br>
This proxy intercepts all inbound and outbound communication for it's neighboring service. </br>
Doing so, a service mesh provides many features
* <b>observability</b>:  monitor network calls (metrics \ traces)
* <b>traffic</b> management: traffic routing features
* <b>resillience</b>: automatic retry, circuit breaker, rate limiting etc
* <b>security</b>: mutual TLS, authentication, policies etc

Let's take a look at the [Official Site](https://istio.io/latest/docs/overview/what-is-istio/) and jump to the documentation. </br>

## Istio: Gateway API controller

With Istio, we have a few more steps to install the gateway API. We fistly need to install the base `helm` chart which includes Istio specific CRD components required. </br>

### Installation 

We can install the helm chart with `helm install` or upgrade it with `helm upgrade`:

```shell

BASE_CHART_VERSION="1.28.1"
ISTIOD_CHART_VERSION="1.28.1"

helm repo add istio https://istio-release.storage.googleapis.com/charts
helm repo update

# get versions for the charts
helm search repo istio/base --versions | head
helm search repo istio/istiod --versions | head

helm install istio-base istio/base \
  --version $BASE_CHART_VERSION \
  --namespace istio-system \
  --create-namespace \
  --set defaultRevision=default 

helm install istiod istio/istiod \
  --version $ISTIOD_CHART_VERSION \
  --namespace istio-system \
  --wait

```

### Configuration

```
helm show values istio/istiod \
  --version $ISTIOD_CHART_VERSION > kubernetes/gateway-api/istio/default-values.yaml
```

### Check Installation

Now we should have the Istio control plane / controller up and running. </br>
This is not the gateway itself, but the controller that will manage the CRDs we get access to and implements some gateway API CRD's. </br>

```shell
# check deployed status
helm ls -n istio-system

# check the controller pods
kubectl get pods -n istio-system --output wide

# check the controller pod logs 
kubectl -n istio-system logs -l app.kubernetes.io/instance=istiod

```

## Install an Istio Gateway Class

```shell
kubectl apply -f kubernetes/gateway-api/istio/01-gatewayclass.yaml
```

## Install an Istio Gateway

```shell
kubectl apply -f kubernetes/gateway-api/istio/02-gateway.yaml
```

When we apply the gateway, we get a new gateway api pod. </br>
Unlike Traefik and Envoy, in Istio, the gateway pod is created in the same namespace as the gateway

```shell
# check the new gateway-api pod - this time its in the same namespace as the gateway object!
kubectl -n default get pods

# we also have a new service
kubectl -n default get svc
```

### Gateway Configuration

For global Gateway class defaults, we can use a `ConfigMap` with a special label in the `istio-system` namespace. </br>
This allows us to set global defaults for all our gatways. </br>

```shell
kubectl apply -f kubernetes/gateway-api/istio/01.1-gatewayclass-config.yaml
```

For Gateways, we can use the `parametersRef` field under `infrastructure` and use a `ConfigMap` to configure it further. </br>
For example, we can change the `deployment` or `service` of the instance like so:

```shell
kubectl apply -f kubernetes/gateway-api/istio/02.1-gateway-config.yaml

# we should see 2 replicas 
kubectl -n default get pods

# port forward for access (get the correct service)
kubectl -n default port-forward svc/gateway-api-istio 80
```

## HTTP Traffic management

Feel free to quickly run through the basic [traffic management table](../README.md#traffic-management-features--http-routes) for using `HTTPRoute` routing for traffic. </br>

<i>Note: HTTPRoute features are not specific to this controller and should be available to any other gateway API controller that you choose.</i>

## Istio Gateway VS Istio Gateway API

Istio has been around for a lot longer than Gateway API has. Therefore Istio has solved much of the traffic management challenges using it's own CRDs, such as `VirtualService`. </br>

There is a very important difference between Istio and the other Gateway APIs we've covered so far. </br>

Istio has made a strict separation between it's own Gateway (`networking.istio.io/v1`) and the Gateway of Gateway API (`gateway.networking.k8s.io/v1`) </br>

This means, their CRDs such as `VirtualService` does not attach to the new Kubernetes Gateway API like `Gateway` and `HTTPRoute`. </br>

Therefore, to use Gateway API features, one should use Gateway API and not Istio Gateways. </br>

### Installing Classic Istio Gateway

To install the Istio gateway, we also use `helm`

```shell

ISTIOGATEWAY_CHART_VERSION="1.28.1"

helm repo add istio https://istio-release.storage.googleapis.com/charts
helm repo update

# get versions for the charts
helm search repo istio/gateway --versions | head

helm install istio-ingress istio/gateway \
  --version $ISTIOGATEWAY_CHART_VERSION \
  --namespace istio-system

kubectl -n istio-system port-forward svc/istio-ingress 80
```

### Enable Application Service Mesh

To use virtual services we need to enable the mesh in our cluster. </br>
I will be using the classic side-car mode, so we'll label our `default` namespace with the correct labels so that the Istio control plane will automatically inject sidecar containers into all pods in the namespace 

[Virtual Services](https://istio.io/latest/docs/reference/config/networking/virtual-service/) are used throughout Istio to manage traffic. </br>

```shell
kubectl label namespace default istio-injection=enabled

# delete all pods to get sidecars added
kubectl delete pods --all
```

We will also need the Gateway controller that will be managing our Istio Gateways. 

### Istio Gateway Ingress

Let's deploy the Istio Gateway

```shell
kubectl apply -f kubernetes/gateway-api/istio/08-istiogateway.yaml
```

### Traffic management using Istio Gateways

Technically, as this is a Gateway API guide, this following bit would be out of scope. </br> 
However I wanted to show you the difference by running your thorugh a quick demo of `VirtualService` using Isio Gateway. </br>

As an example, `VirtualService` can be used to deal Traffic in the same way as Gateway API and Ingress </br>

Let's deploy a Virtual service to perform basic traffic routing:

```shell
kubectl apply -f kubernetes/gateway-api/istio/09-virtualservice.yaml
```

---

### Overview (`kubernetes/gateway-api/kgateway/README.md`)

# Introduction to kgateway: Gateway API

<a href="https://youtu.be/4VTD5Z9YUu0" title="kgateway"><img src="https://i.ytimg.com/vi/4VTD5Z9YUu0/hqdefault.jpg" width="40%" alt="kgateway" /></a>

## Prerequisites

To get started, you will need to follow the [Introduction to Gateway API](../README.md) first. </br>
You'll need an understanding of the Gateway API. </br>

<b>In the introduction guide, you will:</b>
* Create a local Kubernetes cluster
* Install the Gateway API CRDs
* Deploy example apps to our cluster
* Have Domains for our traffic
* Have TLS certificates

This will allow us access to the Gateway API so we can go ahead and deploy a Gateway API controller to use. </br

Let's start with the [Official Documentation](https://kgateway.dev/docs/envoy/latest/quickstart/)

## kgateway: Gateway API controller

kgateway is feature rich when it comes to L7 HTTP routing. </br>
In this guide we will turn on the Gateway API feature in kgateway and use that specifically. </br>

### Installation 

To get the version you want, you can use the kgateway [Github Release page](https://github.com/kgateway-dev/kgateway/releases/tag/v2.1.2). </br>
For this guide, we will use 2.1.2. </br>

Install:

```shell
CHART_VERSION="v2.3.3"

helm show chart oci://cr.kgateway.dev/kgateway-dev/charts/kgateway --version $CHART_VERSION
helm show chart oci://cr.kgateway.dev/kgateway-dev/charts/kgateway-crds --version $CHART_VERSION

helm show values oci://cr.kgateway.dev/kgateway-dev/charts/kgateway --version $CHART_VERSION > kubernetes/gateway-api/kgateway/default-values.yaml

# deploy CRDs
helm upgrade kgateway-crds oci://cr.kgateway.dev/kgateway-dev/charts/kgateway-crds \
  --version ${CHART_VERSION} \
  --namespace kgateway-system \
  --create-namespace \
  --install

# Deploy kgateway control plane
helm upgrade kgateway oci://cr.kgateway.dev/kgateway-dev/charts/kgateway \
  --version $CHART_VERSION \
  --values kubernetes/gateway-api/kgateway/values.yaml \
  --namespace kgateway-system \
  --install \
  --create-namespace
  
```

Check our installation

```shell
# check the pods
kubectl -n kgateway-system get pods

# check the logs 
kubectl -n kgateway-system logs -l app.kubernetes.io/name=kgateway
```

## Install a kgateway Gateway Class

The `helm` chart will create a `GatewayClass` for us as per [docs](https://kgateway.dev/docs/envoy/latest/setup/default/#gatewayclass). </br>

```shell
kubectl get gc kgateway -o yaml
```

## Install a kgateway Gateway

```shell
kubectl apply -f kubernetes/gateway-api/kgateway/01-gateway.yaml

# we can see our gateway dataplane pods
kubectl get pods
kubectl get svc

# port forward for access
kubectl -n default port-forward svc/gateway-api 80
```

### Configuration

Most of the Gateway API controllers are installed using `helm`. </br>
Before we install it, let's take a look at the [values.yaml](./values.yaml)

kgateway has a section on [Gateway Customization](https://kgateway.dev/docs/envoy/main/setup/) where we can adjust gateway parameters. </br>

Gateway API allows us to inject parameters using the `infrastructure.parametersRef` field </br>

We can customize our gateway:

```shell
kubectl apply -f kubernetes/gateway-api/kgateway/02-gateway-config.yaml
```

## HTTP Traffic management

Feel free to quickly run through the basic [traffic management table](../README.md#traffic-management-features--http-routes) for using `HTTPRoute` routing for traffic. </br>

<i>Note: HTTPRoute features are not specific to this controller and should be available to any other gateway API controller that you choose.</i>

## Policies: TrafficPolicy

kgateway uses `TrafficPolicy` as their custom CRD to apply traffic policies to certain HTTPRoutes or all HTTPRoutes that a Gateway serves. </br>

There are many [TrafficPolicy Features](https://kgateway.dev/docs/envoy/main/reference/api/#trafficpolicy) available. 
The cool thing here is that these TrafficPolicies are now pluggable into Gateway API HTTPRoutes. </br>
In `HTTPRoute` objects, under `filters`, we can add extensions using something like:

```yaml
filters:
- type: ExtensionRef
  extensionRef:
    group: gateway.kgateway.dev
    kind: TrafficPolicy
    name: cors-feature
```

### TrafficPolicy Example

Example of a Traffic Policy supporting CORS </br>

```shell
kubectl apply -f kubernetes/gateway-api/kgateway/03-trafficpolicy-cors.yaml
```

## Policies: HTTPListenerPolicy

This is a special CRD for attaching policies to listeners on gateways. </br>
We can use this to set custom access log formats and various features of the gateway listener.

The following changes our log format to JSON
```shell
kubectl apply -f kubernetes/gateway-api/kgateway/04-gateway-accesslogs.yaml

# see log format
kubectl logs -l app.kubernetes.io/name=gateway-api
```

## Policies: BackendConfigPolicy

[BackendConfigPolicy](https://kgateway.dev/docs/envoy/latest/about/policies/backendconfigpolicy/) allows us to configure connection settings for a backend like a Kubernetes Service

## AgentGateway

One thing that sets kgateway apart from the other gateway API's is its integration with [Agent Gateway](https://kgateway.dev/docs/agentgateway/latest/)

---

### Overview (`kubernetes/gateway-api/linkerd/README.md`)

# Introduction to Linkerd: Gateway API

<a href="https://youtu.be/B9gKwJdWW7A" title="linkerd"><img src="https://i.ytimg.com/vi/B9gKwJdWW7A/hqdefault.jpg" width="40%" alt="linkerd" /></a>

## Prerequisites

To get started, you will need to follow the [Introduction to Gateway API](../README.md) first. </br>
You'll need an understanding of the Gateway API. </br>

<b>In the introduction guide, you will:</b>
* Create a local Kubernetes cluster
* Install the Gateway API CRDs
* Deploy example apps to our cluster (below)

In this guide, we will deploy our videos application which has both north\south and east\west traffic: 

```shell
kubectl apply -f monitoring/opentelemetry/applications/playlists-api/
kubectl apply -f monitoring/opentelemetry/applications/playlists-db/
kubectl apply -f monitoring/opentelemetry/applications/videos-web/
kubectl apply -f monitoring/opentelemetry/applications/videos-api/
kubectl apply -f monitoring/opentelemetry/applications/videos-db/

```
* Have Domains for our traffic

This will allow us access to the Gateway API so we can go ahead and deploy a Gateway API controller to use. </br

## What is Linkerd

[Linkerd](https://linkerd.io) is a service mesh technology for Kubernetes that has [gateway api support](https://linkerd.io/2.19/features/gateway-api/) </br>
Linkerd is the first service mesh to achieve CNCF graduation status and is highly matured. </br>

In a distributed system with service-to-service communication, a service mesh uses various methods to intercept all inbound and outbound communication to and from a given service. </br>

So when a service makes an outbound call or has an inbound network call, the mesh is aware of it. </br>

Doing so, a service mesh provides many features:

* <b>observability</b>:  monitor network calls (metrics \ traces)
* <b>traffic</b> management: traffic routing features
* <b>resillience</b>: automatic retry, circuit breaker, rate limiting etc
* <b>security</b>: mutual TLS, authentication, policies etc

Each service mesh is different in terms of how it achieves this. Some meshes use side-cars to intercept and proxy traffic that go in and out of pods. Others use node level proxies and even eBPF to proxy at kernel level. </br>

Linkerd uses the sidecar approach with their proxy written in Rust. </br>

There are a few things that make Linkerd very different to other Gateway APIs we looked at. </br>
Linkerd is a big player in the [The GAMMA Initiative](https://gateway-api.sigs.k8s.io/mesh/gamma/) for Gateway API. </br>
Linkerd does not focus on north/south traffic and therefore we will use an alternative gateway API implementation for that. </br>
Linkerd's focus is more about mesh traffic, which is east\west. </br>

## Linkerd: Gateway API

Linkerd provides Gateway API support included within its single installation. </br>
Unlike some other gateways, that provide Gateway API support through a separate `helm` chart, Linkerd is all one single simple install. </br>

The `helm` chart relies on the Gateway API CRD's we installed in our cluster already </br>

We can checkout the official [install via helm](https://linkerd.io/2.19/tasks/install-helm/) for more instructions. </br>

### Installation 

We firstly need to create mTLS certificates that Linkerd will use for secure internal communication in its service mesh

```shell
apk add step-cli #alpine linux

#use the linkerd CLI to check the install
export LINKERD2_VERSION=edge-25.12.3
curl --proto '=https' --tlsv1.2 -sSfL https://run.linkerd.io/install-edge | sh
export PATH=$PATH:/root/.linkerd2/bin

linkerd check --pre

#generate CA
step certificate create root.linkerd.cluster.local /tmp/ca.crt /tmp/ca.key \
--profile root-ca --no-password --insecure

#generate intermediate cert for csr's
step certificate create identity.linkerd.cluster.local /tmp/issuer.crt /tmp/issuer.key \
--profile intermediate-ca --not-after 8760h --no-password --insecure \
--ca /tmp/ca.crt --ca-key /tmp/ca.key


```
We can install the helm chart with `helm install` or upgrade it with `helm upgrade`
Please note that I will be using the linkerd edge release in this demo to ensure I have the latest functionality. For production, you may want to use the stable release instead. </br>

```shell

CRD_CHART_VERSION="2025.12.3"
CHART_VERSION="2025.12.3"

helm repo add linkerd-edge https://helm.linkerd.io/edge
helm repo update
helm search repo linkerd-edge --versions | grep linkerd-edge/linkerd-crds | head
helm search repo linkerd-edge --versions | grep linkerd-edge/linkerd-control-plane | head

# get values files
helm show values linkerd-edge/linkerd-control-plane > kubernetes/gateway-api/linkerd/default-values.yaml

helm install linkerd-crds linkerd-edge/linkerd-crds \
  --version $CRD_CHART_VERSION \
  --namespace linkerd \
  --create-namespace

helm install linkerd-control-plane \
  --version $CHART_VERSION \
  --namespace linkerd \
  --create-namespace \
  --set-file identityTrustAnchorsPEM=/tmp/ca.crt \
  --set-file identity.issuer.tls.crtPEM=/tmp/issuer.crt \
  --set-file identity.issuer.tls.keyPEM=/tmp/issuer.key \
  --values kubernetes/gateway-api/linkerd/values.yaml \
  linkerd-edge/linkerd-control-plane

```

### Check Installation

Now we should have the Envoy gateway API controller up and running. </br>
This is not the gateway itself, but the controller that will manage the CRDs we get access to and implement some gateway API CRDs. </br>

```shell
# check the control plane pods
kubectl -n linkerd get pods

linkerd check

```

### Configuration

Most of the Gateway API controllers are installed using `helm`. </br>
Before we install it, let's take a look at the [default-values.yaml](./default-values.yaml)

Linkerd also provide a High Availability YAML file for HA deployments which I found [here on Github](https://github.com/linkerd/linkerd2/blob/main/charts/linkerd-control-plane/values-ha.yaml). </br>
So in this guide I will base my demo off that with the `controllerReplicas` set to 1 for demo purpose as I am running a single node cluster. </br>


## Install a Gateway Class & Gateway

Since Linderd does not have a gateway, we will be using Envoy from our [Envoy Gateway Guide](../envoy/README.md#envoy-gateway-api-controller)

```shell
# deploy gateway class
kubectl apply -f kubernetes/gateway-api/envoy/02.1-gateway-config.yaml

# deploy a gateway
kubectl apply -f kubernetes/gateway-api/envoy/02-gateway.yaml

```

When we apply the gateway, we get a new gateway api pod. 

```shell
# check the new gateway-api pod
kubectl -n envoy-gateway-system get pods

# we also have a new service
kubectl -n envoy-gateway-system get svc

# port forward for access to the new gateway
kubectl -n envoy-gateway-system port-forward svc/envoy-gateway-default 80
```

## Mesh our Services

To add our services to the Linkerd mesh, we need to annotate each deployment

```shell
kubectl get deploy playlists-api -o yaml | linkerd inject - | kubectl apply -f -
kubectl get deploy playlists-db -o yaml | linkerd inject - | kubectl apply -f -
kubectl get deploy videos-api -o yaml | linkerd inject - | kubectl apply -f -
kubectl get deploy videos-db -o yaml | linkerd inject - | kubectl apply -f -
kubectl get deploy videos-web -o yaml | linkerd inject - | kubectl apply -f -

```

## Linkerd Dashboard

To access the `linkerd` dashboard, we need to install the viz extension

```
linkerd viz install | kubectl apply -f -
kubectl -n linkerd-viz port-forward svc/web 8084
```

## HTTPRoutes: North \ South Traffic

For north\south traffic, we will use Gateway API and setup a HTTP route

```shell
kubectl apply -f kubernetes/gateway-api/linkerd/03-httproute.yaml
```
## HTTPRoutes - East \ West Traffic 

For east\west traffic features:

Let's deploy a V2 of our videos-api that only returns a video about the Gateway API. </br>

```shell
# new V2
kubectl apply -f kubernetes/gateway-api/linkerd/04-east-west-app.yaml

# mesh the new app
kubectl get deploy videos-api-v2 -o yaml | linkerd inject - | kubectl apply -f -

# HTTPRoute that routes traffic east \ west
kubectl apply -f kubernetes/gateway-api/linkerd/04-httproute-east-west.yaml
```

You could use this for a number of reasons:

* Inject latency
* Inject faults to test applications
* Canary deployments
* Dynamic routing

---

### Overview (`kubernetes/gateway-api/nginx-fabric/README.md`)

# Introduction to NGINX Gateway Fabric: Gateway API

<a href="https://youtu.be/zbAb2e_Q_Y0" title="nginx-fabric"><img src="https://i.ytimg.com/vi/zbAb2e_Q_Y0/hqdefault.jpg" width="40%" alt="nginx-fabric" /></a>

## Prerequisites

To get started, you will need to follow the [Introduction to Gateway API](../README.md) first. </br>
You'll need an understanding of the Gateway API. </br>

<b>In the introduction guide, you will:</b>
* Create a local Kubernetes cluster
* Install the Gateway API CRDs
* Deploy example apps to our cluster
* Have Domains for our traffic
* Have TLS certificates

This will allow us access to the Gateway API so we can go ahead and deploy a Gateway API controller to use. </br

## What is NGINX Gateway Fabric

NGINX Gateway Fabric is an [open-source project](https://github.com/nginx/nginx-gateway-fabric) that provides an implementation of the Gateway API using NGINX as the data plane. 

Let's checkout the [Official Documentation](https://docs.nginx.com/nginx-gateway-fabric/overview/gateway-architecture/)

## Install a NGINX Gateway Class

In our other guides, we deployed and managed a Gateway Class outside of the controller installation. </br>
NGINX Fabric is a little different as their `GatewayClass` is managed by helm and there are extra configurations managed by the chart that gets injected into the `GatewayClass` under the `parametersRef` field. </br>

If you've been following this series you will know the `parametersRef` field is used to extend the class. NGINX uses a custom CRD called `NginxProxy` to customize the class, but these settings come from their `helm` chart. </br>

For example:

```yaml
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: nginx-gateway
  annotations:
    meta.helm.sh/release-name: ngf
    meta.helm.sh/release-namespace: nginx-gateway
  labels:
    app.kubernetes.io/managed-by: Helm
spec:
  controllerName: gateway.nginx.org/nginx-gateway-controller
  parametersRef:
    group: gateway.nginx.org
    kind: NginxProxy
    name: ngf-proxy-config
    namespace: nginx-gateway
```


## NGINX Fabric: Gateway API controller

NGINX Fabric covers most of the core Gateway API features. </br>
It also supports custom features using policies </br>

### Configuration

Most of the Gateway API controllers are installed using `helm`. </br>
Before we install it, let's take a look at the [values.yaml](./values.yaml)

The Helm chart allows us to configure many options. Some I find quite important:

* Control plane deployment & Service
* Data plane deployment & Service modification
* Ports to use for incoming traffic
* Control Gateway API
  * Default CRDs
  * Default GatewayClass
  * Default Gateways

There are [helm examples](https://github.com/nginx/nginx-gateway-fabric/tree/v2.2.2/examples/helm) on Github maintained by the NGINX community. </br>

Here is the [helm chart documentation](https://github.com/nginx/nginx-gateway-fabric/blob/v2.2.2/charts/nginx-gateway-fabric/README.md)

It's always good to get a grip on the [default helm values](./default-values.yaml) to see what we can do with the chart. </br>

The values file is used to customise NGINX control & data plane, underlying pods, deployments and services as well as turning features on and off. </br>

We can go ahead and install the controller to start with and use `helm upgrade` to make changes as we need to. </br>

### Installation 
Install:

```shell
CHART_VERSION="2.3.0"

helm show values oci://ghcr.io/nginx/charts/nginx-gateway-fabric > kubernetes/gateway-api/nginx-fabric/default-values.yaml

helm show chart oci://ghcr.io/nginx/charts/nginx-gateway-fabric

helm install ngf oci://ghcr.io/nginx/charts/nginx-gateway-fabric \
  --values kubernetes/gateway-api/nginx-fabric/values.yaml \
  --version $CHART_VERSION \
  --namespace nginx-gateway \
  --create-namespace
```

Upgrade:

```shell
helm upgrade ngf oci://ghcr.io/nginx/charts/nginx-gateway-fabric \
  --values kubernetes/gateway-api/nginx-fabric/values.yaml \
  --version $CHART_VERSION \
  --namespace nginx-gateway
```

Check our installation

```shell
# check the pods
kubectl -n nginx-gateway get pods

# check the logs 
kubectl -n nginx-gateway logs -l app.kubernetes.io/instance=ngf

```

## Install a NGINX Gateway

So we have the `GatewayClass` managed by the `helm` chart, therefore we skip the `01-gatewayclass.yaml` and go straight to the gateway. </br>

This will deploy NGINX dataplane pods to our target `default` namespace:

```shell
kubectl apply -f kubernetes/gateway-api/nginx-fabric/02-gateway.yaml

# check gateway pods
kubectl get pods 

#check logs
kubectl logs -l app.kubernetes.io/instance=ngf

# port forward for access
kubectl port-forward svc/gateway-api-nginx-gateway 80
```

## Check & Configure the NGINX Dataplane

We can configure our NGINX Gateway, using the `spec.infrastructure.parametersRef` and point a Gateway to a `NginxProxy` resource. We can attach this config to a `Gateway` or `GatewayClass` </br>
This allows us to control specific gateways or all gateways globally

Let's create a configuration to set the number of replicas on our Gateway deployment and set our service to `ClusterIP` since we don't have a load balancer. </br>

Apply changes:

```shell
kubectl apply -f kubernetes/gateway-api/nginx-fabric/02.1-gateway-config.yaml
```

In addition - 

When we define `HTTPRoute`'s for our traffic, The NGINX control plane detects those and works with the data plane pods to get an `nginx.conf` for the desired traffic rules. </br>

If you are familiar with NGINX you will know all traffic rules are in the `nginx.conf`. So this solution transforms K8s CRDs to NGINX rules under the hood

```shell
# get a dataplane pod name
kubectl get pods 
pod=gateway-api-nginx-gateway-5899b6cd6f-dpkbh

# check the generated nginx configuration file
kubectl exec -it -n default $pod -- nginx -T
```

## HTTP Traffic management

Feel free to quickly run through the basic [traffic management table](../README.md#traffic-management-features--http-routes) for using `HTTPRoute` routing for traffic. </br>

<i>Note: HTTPRoute features are not specific to this controller and should be available to any other gateway API controller that you choose.</i>

## Policy APIs

Kubernetes Gateway API provides [policy attachment](https://gateway-api.sigs.k8s.io/reference/policy-attachment/) that allows us to augment or add configuration to existing resources </br>

### Client Settings Policies

`ClientSettingsPolicy` is an NGINX Fabric CRD that allows us to customize how our Gateway behaves with client connections. </br>
We can set things like Keep Alive , Timeout settings and HTTP Body sizes. </br>
This CRD uses the above-mentioned policy attachment API for Gateway API. </br>

Ensure the policy is accepted 

```shell
kubectl apply -f kubernetes/gateway-api/nginx-fabric/08-clientsettings-keepalive.yaml

# check 
kubectl describe clientsettingspolicies gateway-client-settings
```

Make  more than 5 requests and notice every one re-using the connection except for the last:

```shell
# make 6 requests
curl -v http://example-app.com/api/go \
  http://example-app.com/api/go \
  http://example-app.com/api/go \
  http://example-app.com/api/go \
  http://example-app.com/api/go \
  http://example-app.com/api/go 

# note connection re-use 
* Re-using existing connection with host example-app.com
```

### SnippetsFilter API

If you have used the NGINX Ingress controller, you may recall "custom snippets" , "server snippets" or "location snippets" </br>
`SnippetsFilter` is an API that allows NGINX Gateway Fabric to expose some of NGINX raw configuration for tweaking. </br>

Similar to NGINX Ingress, the NGINX proxy in Gateway Fabric also generates NGINX configuration files as demonstrated earlier. `SnippetsFilter` allows us to insert our own NGINX configuration into these files. </br>

This feature is enabled in our [values.yaml](./values.yaml) file. </br>

```shell
kubectl apply -f kubernetes/gateway-api/nginx-fabric/09-snippetsfilter.yaml

# check 
kubectl describe SnippetsFilter custom-snippet
```

---

### Overview (`kubernetes/gateway-api/nginx-ingress/README.md`)

# Ingress-NGINX Migration to Gateway API

<a href="https://youtu.be/7CaDvoKO9wo" title="nginx-ingress-migration"><img src="https://i.ytimg.com/vi/7CaDvoKO9wo/hqdefault.jpg" width="40%" alt="nginx-ingress-migration" /></a>


### Ingress Class

```shell
kubectl get ingress -A -o custom-columns="NAMESPACE:.metadata.namespace,NAME:.metadata.name,CLASS:.spec.ingressClassName"
```

### Ingress to Gateway Tool

```shell
wget -q "https://github.com/kubernetes-sigs/ingress2gateway/releases/download/v0.4.0/ingress2gateway_Linux_x86_64.tar.gz" -O /tmp/igtogw.tar.gz && \
  tar -C /tmp -xzf /tmp/igtogw.tar.gz && \
  mv /tmp/ingress2gateway /usr/local/bin/ingress2gateway && \
  chmod +x /usr/local/bin/ingress2gateway

./ingress2gateway print --providers=ingress-nginx
```

### Annotations Audit

To get a list of all NGINX Ingress Annotations we are using: 

```shell
kubectl get ingress -A -o json  | \
  jq -r '.items[].metadata.annotations | keys[]' | \
  grep "nginx.ingress" | \
  sort -u
```

Now we can generate our audit list by selecting the above annotated output:

```shell
apk add util-linux
audit_fields=(
  "NAMESPACE:.metadata.namespace"
  "NAME:.metadata.name"
  "HOSTS:.spec.rules[*].host"
  "MIGRATED:.metadata.labels.migrated"
  "REWRITE:.metadata.annotations.nginx\.ingress\.kubernetes\.io/rewrite-target"
  "SNIPPET:.metadata.annotations.nginx\.ingress\.kubernetes\.io/configuration-snippet"
  "AUTH:.metadata.annotations.nginx\.ingress\.kubernetes\.io/auth-type"
  "PathType:.spec.rules[*].http.paths[*].pathType"
  "TLS:.spec.tls[*].secretName"
)

IFS=$','
column_args="${audit_fields[*]}"

kubectl get ingress -A -o custom-columns="$column_args"
```

### Gateway API migration

| Feature | Ingress Nginx Annotation | Native Gateway API Resource / Filter | Example |
| :--- | :--- | :--- | :---|
| Basic Routing | `<none>` | `HTTPRoute` | [example: Route by Domain](#route-by-domain) |
| Path Rewrite | `rewrite-target` | `HTTPRoute` → `URLRewrite` filter | [example: URL Rewrite](#path-rewrite) |
| TLS termination | `tls.secretName` | `Gateway` → `Listeners` → `tls.certificateRefs` | [example: TLS termination](#tls-termination) |
| Redirects | `app-root`, `ssl-redirect` | `HTTPRoute` → `RequestRedirect` filter | [example: HTTP redirects](#http-redirects) |
| Header Mod | `add-header` | `HTTPRoute` → `RequestHeaderModifier` filter |
| Basic Auth | `auth-type: basic` | **Policy Attachment** (Targeting `HTTPRoute`) | [custom example: BasicAuth](#basic-auth-custom-feature) |
| Canary/Weight | `canary-weight` | `HTTPRoute` → `backendRefs.weight` |
| Mirroring | `mirror-target` | `HTTPRoute` → `RequestMirror` filter |
| Backend TLS | `backend-protocol: HTTPS` | `BackendTLSPolicy` (Standard Object) |
| Cross-Namespace | *(Standard Ingress)* | `ReferenceGrant` (Standard Object) |
| Request Timeout| `proxy-read-timeout` | `HTTPRoute` → `spec.rules.timeouts` |

### Examples

In these examples, we will run `port-forward` to test the old and new endpoints using `curl`. You want to ensure you get similar or same expected outcomes to ensure compatibility on your new endpoint. </br>

```shell
# port-forward to ingress
kubectl -n ingress-nginx port-forward svc/ingress-nginx-controller 80
# port-forward to gateway
kubectl -n traefik port-forward svc/traefik 80
```

#### Route by Domain

Before:
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: python-route
spec:
  ingressClassName: nginx
  rules:
  - host: example-app-python.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: python-svc
            port:
              number: 5000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: go-route
spec:
  ingressClassName: nginx
  rules:
  - host: example-app-go.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: go-svc
            port:
              number: 5000
```

This is what the [HTTPRoute:Route by Domain](../03-httproute-by-hostname.yaml) looks like. </br>

```shell 
kubectl apply -f kubernetes/gateway-api/03-httproute-by-hostname.yaml
```

Test:
```shell
curl example-app-python.com
Hello from Python!

curl example-app-go.com
Hello from Go
```

Mark as done
```
kubectl label ingress go-route python-route migrated=true
```

#### Path Rewrite

Before: 

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
  name: python-route-bypath
  namespace: default
spec:
  ingressClassName: nginx
  rules:
  - host: example-app.com
    http:
      paths:
      - backend:
          service:
            name: python-svc
            port:
              number: 5000
        path: /api/python(/|$)(.*)
        pathType: ImplementationSpecific
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
  name: go-route-bypath
  namespace: default
spec:
  ingressClassName: nginx
  rules:
  - host: example-app.com
    http:
      paths:
      - backend:
          service:
            name: go-svc
            port:
              number: 5000
        path: /api/go(/|$)(.*)
        pathType: ImplementationSpecific
```

After:
```yaml 
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: python-route-bypath
  namespace: default

spec:
  parentRefs:
  - name: gateway-api
    sectionName: http
    kind: Gateway

  hostnames:
  - example-app.com

  rules:
  - matches:
    - path: 
        type: PathPrefix
        value: /api/python
    filters:
    - type: URLRewrite
      urlRewrite:
        path:
          type: ReplacePrefixMatch
          replacePrefixMatch: /
    backendRefs:
    - name: python-svc
      port: 5000
      weight: 1
---

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: go-route-bypath
  namespace: default

spec:
  parentRefs:
  - name: gateway-api
    sectionName: http
    kind: Gateway

  hostnames:
  - example-app.com

  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /api/go 
    filters:
    - type: URLRewrite
      urlRewrite:
        path:
          type: ReplacePrefixMatch
          replacePrefixMatch: /
    backendRefs:
    - name: go-svc
      port: 5000
      weight: 1
```

Test:

```shell 
curl example-app.com/api/python
Hello from Python!

curl example-app.com/api/go
Hello from Go
```

Mark as done
```
kubectl label ingress go-route-bypath python-route-bypath  migrated=true
```

### TLS Termination

Before:
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: go-route-tls
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - example-app.com
    secretName: secret-tls
  rules:
  - host: example-app.com
    http:
      paths:
      - path: /api/go/tls(/|$)(.*)
        pathType: ImplementationSpecific
        backend:
          service:
            name: go-svc
            port:
              number: 5000
```

After:

```yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: go-route-tls
  namespace: default

spec:
  parentRefs:
  - name: gateway-api
    sectionName: http
    kind: Gateway
  - name: gateway-api
    sectionName: https
    kind: Gateway

  hostnames:
  - example-app.com
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /api/go/tls
    filters:
    - type: URLRewrite
      urlRewrite:
        path:
          type: ReplacePrefixMatch
          replacePrefixMatch: /
    backendRefs:
    - name: go-svc
      port: 5000
      weight: 1
```

We can use our `port-forward` on port `443` to test the TLS in the browser 

Mark as done
```
kubectl label ingress go-route-tls python-route migrated=true
```

### HTTP Redirects

Before:
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: python-route-tls
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - example-app.com
    secretName: secret-tls
  rules:
  - host: example-app.com
    http:
      paths:
      - path: /api/python/tls(/|$)(.*)
        pathType: ImplementationSpecific
        backend:
          service:
            name: python-svc
            port:
              number: 5000
```

After:
```yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: python-route-tls-redirect
  namespace: default
spec:
  parentRefs:
  - name: gateway-api
    sectionName: http
  hostnames:
  - example-app.com
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /api/python/tls
    filters:
    - type: RequestRedirect
      requestRedirect:
        scheme: https
        statusCode: 301
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: python-route-tls
  namespace: default

spec:
  parentRefs:
  - name: gateway-api
    sectionName: https
    kind: Gateway

  hostnames:
  - example-app.com
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /api/python/tls
    filters:
    - type: URLRewrite
      urlRewrite:
        path:
          type: ReplacePrefixMatch
          replacePrefixMatch: /
    backendRefs:
    - name: python-svc
      port: 5000
      weight: 1
```

Test:

```shell
# before
curl http://example-app.com/api/python/tls/
<html>
<head><title>308 Permanent Redirect</title></head>
<body>
<center><h1>308 Permanent Redirect</h1></center>
<hr><center>nginx</center>
</body>
</html>

# after 
curl http://example-app.com/api/python/tls/ -v
< HTTP/1.1 301 Moved Permanently
< Location: https://example-app.com/api/python/tls/
```

Mark as done
```
kubectl label ingress python-route-tls migrated=true
```


### Basic Auth (Custom Feature)

Create a Basic Auth secret:

```shell
apk add apache2-utils
htpasswd -c auth python
kubectl create secret generic python-route-auth-secret --from-file=auth

```
Before: 

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: python-route-auth
  annotations:
    nginx.ingress.kubernetes.io/auth-type: basic
    nginx.ingress.kubernetes.io/auth-secret: python-route-auth-secret
    nginx.ingress.kubernetes.io/auth-secret-type: auth-file
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  ingressClassName: nginx
  rules:
  - host: example-app.com
    http:
      paths:
      - path: /api/python/login(/|$)(.*)
        pathType: ImplementationSpecific
        backend:
          service:
            name: python-svc
            port:
              number: 5000
```

After:

```yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: python-route-auth
  namespace: default
spec:
  parentRefs:
  - name: gateway-api
    sectionName: http
    kind: Gateway
  hostnames:
  - "example-app.com"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /api/python/login
    filters:
    - type: ExtensionRef
      extensionRef:
        group: traefik.io
        kind: Middleware
        name: basic-auth
    - type: URLRewrite
      urlRewrite:
        path:
          type: ReplacePrefixMatch
          replacePrefixMatch: /
    backendRefs:
    - name: python-svc
      port: 5000
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: basic-auth
spec:
  basicAuth:
    secret: python-route-auth-secret
```

Test:

```shell
#Before:
curl http://example-app.com/api/python/login
<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx</center>
</body>
</html>

#After:
curl http://public.my-services.com/path-a/login
401 Unauthorized
```

Mark as done
```
kubectl label ingress python-route-auth migrated=true
```

---

### Overview (`kubernetes/gateway-api/traefik/README.md`)

# Introduction to Traefik: Gateway API

<a href="https://youtu.be/MN-k29g97ik" title="traefik"><img src="https://i.ytimg.com/vi/MN-k29g97ik/hqdefault.jpg" width="40%" alt="traefik" /></a>

## Prerequisites

To get started, you will need to follow the [Introduction to Gateway API](../README.md) first. </br>
You'll need an understanding of the Gateway API. </br>

<b>In the introduction guide, you will:</b>
* Create a local Kubernetes cluster
* Install the Gateway API CRDs
* Deploy example apps to our cluster
* Have Domains for our traffic
* Have TLS certificates

This will allow us access to the Gateway API so we can go ahead and deploy a Gateway API controller to use. </br

## Install a Traefik Gateway Class

```shell
kubectl apply -f kubernetes/gateway-api/traefik/01-gatewayclass.yaml
```

## Install a Traefik Gateway

```shell
kubectl apply -f kubernetes/gateway-api/traefik/02-gateway.yaml
```

Let's start with the [Official Documentation](https://doc.traefik.io/traefik/)

## Traefik: Gateway API controller

Traefik has a lot of features, including Ingress controller functionality. </br>
In this guide we will turn on the Gateway API feature in Traefik and use that specifically. </br>

### Configuration

Most of the Gateway API controllers are installed using `helm`. </br>
Before we install it, let's take a look at the [values.yaml](./values.yaml)

Traefik allows us to configure many options. Some I find quite important:

* Access logs - fields and format
* Ports to use for incoming traffic
* Infrastructure annotations \ labels
* Control Gateway API
  * Default CRDs
  * Default GatewayClass
  * Default Gateways

It's always good to get a grip on the [default helm values](https://github.dev/traefik/traefik-helm-chart/blob/master/traefik/values.yaml)

The values file is used to customise Traefik, underlying pods, deployments and services as well as turning features on and off. </br>

We can go ahead and install the controller to start with and use `helm upgrade` to make changes as we need to. </br>

### Installation 
Install:

```shell
CHART_VERSION="37.3.0" # traefik version v3.6.0
helm repo add traefik https://helm.traefik.io/traefik
helm repo update
helm search repo traefik --versions


helm install traefik traefik/traefik \
  --version $CHART_VERSION \
  --values kubernetes/gateway-api/traefik/values.yaml \
  --namespace traefik \
  --create-namespace

```

Upgrade:

```shell
helm upgrade traefik traefik/traefik \
  --version $CHART_VERSION \
  --values kubernetes/gateway-api/traefik/values.yaml \
  --namespace traefik
```

Check our installation

```shell
# check the pods
kubectl -n traefik get pods

# check the logs 
kubectl -n traefik logs -l app.kubernetes.io/instance=traefik-traefik

# port forward for access
kubectl -n traefik port-forward svc/traefik 80
```

## HTTP Traffic management

Feel free to quickly run through the basic [traffic management table](../README.md#traffic-management-features--http-routes) for using `HTTPRoute` routing for traffic. </br>

<i>Note: HTTPRoute features are not specific to this controller and should be available to any other gateway API controller that you choose.</i>

## Middlewares

One of the key strengths of using Traefik is that they have a catalogue of middlewares that are plugins to perform popular common actions on traffic. </br>

There are many [Middlewares](https://doc.traefik.io/traefik/reference/routing-configuration/http/middlewares/overview/) available. 

The cool thing here is that these Middlewares are now pluggable into Gateway API HTTPRoutes. </br>

In `HTTPRoute` objects, under `filters`, we can add extensions using something like:

```yaml
filters:
- type: ExtensionRef
  extensionRef:
    group: traefik.io
    kind: Middleware
    name: add-prefix
```

### Prefix

Note: To use `ExtensionRef` in Traefik you need to enable `providers.kubernetesCRD.enabled = true`. We have that enabled in our values.yaml file.

Expectation: </br>

http://example-app.com/anything 👉🏽 http://go-svc:5000/prefix/anything </br>

```shell
kubectl apply -f kubernetes/gateway-api/traefik/08-middleware-addprefix.yaml
```

We can follow the log of our upstream to see the HTTP request:

```shell
kubectl logs -f -l app=go-app
```

### Basic Auth

We can also add HTTP basic authentication to allow users to access secured resources protected by username and passwords. </br>

The Basic Auth middleware points to a secret which supports Kubernetes basic auth secret type. </br>

```shell
kubectl apply -f kubernetes/gateway-api/traefik/09-middleware-basicauth.yaml
```

### Headers

Traefik allows us to manipulate headers too using Middleware component called `headers` </br>
We can use this to handle scenarios like CORS (Cross-Origin Resource Sharing)

Let's access our web app directly over `localhost` which makes a call to `example-app.com` and should be blocked by CORS. </br>

```shell
kubectl port-forward svc/web-app 8000:80
```

Once we apply the Middleware update to our HTTPRoute, we can perform cross origin API calls:

```shell
kubectl apply -f kubernetes/gateway-api/traefik/10-middleware-headers.yaml
```

---

### Overview (`kubernetes/secrets/sealed-secrets/README.md`)

# Introduction to Sealed Secrets 

<a href="https://youtu.be/u0qtgUMLua0" title="k8s-sealedsecrets"><img src="https://i.ytimg.com/vi/u0qtgUMLua0/hqdefault.jpg" width="20%" alt="k8s-sealedsecrets" /></a> 

Checkout the [Sealed Secrets GitHub Repo](https://github.com/bitnami-labs/sealed-secrets) </br>

There are a number of use-cases where this is a really great concept. </br>

1) GitOps - Storing your YAML manifests in Git and using GitOps tools to sync the manifests to your clusters (For example Flux and ArgoCD!)

2) Giving a team access to secrets without revealing the secret material.

developer: "I want to confirm my deployed secret value is X in the cluster" </br>

developer can compare `sealedSecret` YAML in Git, with the `sealedSecret` in the cluster and confirm the value is the same. </br>

## Create a kubernetes cluster

In this guide we we'll need a Kubernetes cluster for testing. Let's create one using [kind](https://kind.sigs.k8s.io/) </br>

```
kind create cluster --name sealedsecrets --image kindest/node:v1.23.5
```

See cluster up and running:

```
kubectl get nodes
NAME                  STATUS   ROLES                  AGE     VERSION
sealedsecrets-control-plane   Ready    control-plane,master   2m12s   v1.23.5
```

## Run a container to work in

### run Alpine Linux: 
```
docker run -it --rm -v ${HOME}:/root/ -v ${PWD}:/work -w /work --net host alpine sh
```

### install kubectl

```
apk add --no-cache curl
curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl
chmod +x ./kubectl
mv ./kubectl /usr/local/bin/kubectl
```

### install helm

```
curl -o /tmp/helm.tar.gz -LO https://get.helm.sh/helm-v3.10.1-linux-amd64.tar.gz
tar -C /tmp/ -zxvf /tmp/helm.tar.gz
mv /tmp/linux-amd64/helm /usr/local/bin/helm
chmod +x /usr/local/bin/helm
```

### test cluster access:
```
/work # kubectl get nodes
NAME                    STATUS   ROLES    AGE   VERSION
sealedsecrets-control-plane   Ready    control-plane,master   3m26s   v1.23.5
```

## Install Sealed Secret Controller 

### download the YAML

In this demo we'll use version [0.19.1](https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.19.1/controller.yaml) of the sealed secrets controller downloaded from the 
[Github releases](https://github.com/bitnami-labs/sealed-secrets/releases) page

```
curl -L -o ./kubernetes/secrets/sealed-secrets/controller-v0.19.1.yaml https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.19.1/controller.yaml

```

### install using Helm

You can also install the controller using `helm`

```
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm search repo sealed-secrets --versions
helm template sealed-secrets --version 2.7.0 -n kube-system sealed-secrets/sealed-secrets \
> ./kubernetes/secrets/sealed-secrets/controller-helm-v0.19.1.yaml

```
With `helm template` we can explore the YAML and then replace the `helm template` with `helm install`
to install the chart 

### install using YAML manifest

```
kubectl apply -f kubernetes/secrets/sealed-secrets/controller-v0.19.1.yaml
```

### Check the installation

The controller deploys to the `kube-system` namespace by default.

```
kubectl -n kube-system get pods
```

Check the logs of the sealed secret controller 

```
kubectl -n kube-system logs -l name=sealed-secrets-controller --tail -1
```

From the logs we can see that it writes the encryption key its going to use as a kubernetes secret </br>
Example log:

```
2022/11/05 21:38:20 New key written to kube-system/sealed-secrets-keymwzn9
```

## Encryption keys

```
kubectl -n kube-system get secrets
kubectl -n kube-system get secret sealed-secrets-keygxlvg -o yaml
```

## Download KubeSeal

The same way we downloaded the sealed secrets controller from the [GitHub releases](https://github.com/bitnami-labs/sealed-secrets/releases) page,
we'll want to download kubeseal from the assets section 
```

curl -L -o /tmp/kubeseal.tar.gz \
https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.19.1/kubeseal-0.19.1-linux-amd64.tar.gz
tar -xzf /tmp/kubeseal.tar.gz -C /tmp/
chmod +x /tmp/kubeseal
mv /tmp/kubeseal /usr/local/bin/
```

We can now run `kubeseal --help`

## Sealing a basic Kubernetes Secret 

Looks at our existing Kubernetes secret YAML

```
cat kubernetes/secrets/secret.yaml 
```

If you run `kubeseal` you will see it pause and expect input from `stdin`. </br>
You can paste your secret YAML and press CTRL+D to terminate `stdin`. </br>
You will notice it writes a `sealedSecret` to `stdout`. </br>
We can then automate this using `|` characters. </br>

Create a sealed secret using `stdin` :

```
 cat kubernetes/secrets/secret.yaml | kubeseal -o yaml  > kubernetes/secrets/sealed-secrets/sealed-secret.yaml
```

Create a sealed secret using a YAML file:

```
kubeseal -f kubernetes/secrets/secret.yaml -o yaml > kubernetes/secrets/sealed-secrets/sealed-secret.yaml
```

Deploy the sealed secret

```
kubectl apply -f kubernetes/secrets/sealed-secrets/sealed-secret.yaml
```

Now few seconds later, see the secret

```
kubectl -n default get secret
NAME                  TYPE                                  DATA   AGE
mysecret              Opaque                                1      25s
```

## How the encryption key is managed

By default the controller generates a key as we saw earlier and stores it in a Kubernetes secret. </br>
By default, the controller will generate a new active key every 30 days.
It keeps old keys so it can decrypt previous encrypted sealed secrets and will use the active key with new encryption. </br>

It's important to keep these keys secured. </br>
When the controller starts it consumes all the secrets and will start using them </br>
This means we can backup these keys in a Vault and use them to migrate our clusters if we wanted to. </br>

We can also override the renewal period to increase or decrease the value. `0` turns it off </br>

To showcase this I can set `--key-renew-period=<value>` to 5min to watch how it works. </br>

```
apk add nano
export KUBE_EDITOR=nano
```
Set the flag on the command like so to add a new key every 5 min for testing:

```
spec:
  containers:
  - command:
    - controller
    - --key-renew-period=5m

kubectl edit deployment/sealed-secrets-controller --namespace=kube-system
```

You should see a new key created under secrets in the `kube-system` namespace 

```
kubectl -n kube-system get secrets
```

## Backup your encryption keys

To get your keys out for backup purpose, it's as simple as grabbing a secret by label using `kubectl` :

```
kubectl get secret -n kube-system \
  -l sealedsecrets.bitnami.com/sealed-secrets-key \
  -o yaml \
  >  kubernetes/secrets/sealed-secrets/sealed-secret-keys.key
```
This can be used when migrating from one cluster to another, or simply for keeping backups. </br>

## Migrate your encryption keys to a new cluster 

To test this, lets delete our cluster and recreate it. </br>

```
kind delete cluster --name sealedsecrets
kind create cluster --name sealedsecrets --image kindest/node:v1.23.5

# check the cluster
kubectl get nodes

# redeploy sealed-secrets controller
kubectl apply -f kubernetes/secrets/sealed-secrets/controller-v0.19.1.yaml

kubectl -n kube-system get pods

```

### restore our encryption keys

```
kubectl apply -f kubernetes/secrets/sealed-secrets/sealed-secret-keys.key
```

### apply our old sealed secret

```
kubectl apply -f kubernetes/secrets/sealed-secrets/sealed-secret.yaml
```

### see sealed secret status

To troubleshoot the secret, you can use the popular `kubectl describe` command. </br>
Note that we're unable to decrypt the secret. </br>
Why is that ? </br>

We'll this is because the encryption key secrets are read when the controller starts. </br> 
So we will need to restart the controller to that it can read ingest the encryption keys:

```
kubectl delete pod -n kube-system -l name=sealed-secrets-controller
```

## Re-encrypting secrets with the latest key

We can also use `kubeseal --re-encrypt` to encrypt a secret again. </br>
Let's say we want to encrypt with the latest key. </br>
This will re-encrypt the sealed secret without having to pull the actual secret to the client </br>

```
cat ./kubernetes/secrets/sealed-secrets/sealed-secret.yaml \
| kubeseal --re-encrypt -o yaml
```

I can then save this to override the original old local sealed secret file:

```
cat ./kubernetes/secrets/sealed-secrets/sealed-secret.yaml \
| kubeseal --re-encrypt -o yaml \
> tmp.yaml && mv tmp.yaml ./kubernetes/secrets/sealed-secrets/sealed-secret.yaml
```

---

### Overview (`kubernetes/servicemesh/istio/README.md`)

# Introduction to Istio

## We need a Kubernetes cluster

Lets create a Kubernetes cluster to play with using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/)

```
kind create cluster --name istio --image kindest/node:v1.19.1
```

## Deploy our microservices (Video catalog)

```
# ingress controller
kubectl create ns ingress-nginx
kubectl apply -f monitoring/opentelemetry/applications/ingress-nginx/

# applications
kubectl apply -f monitoring/opentelemetry/applications/playlists-api/
kubectl apply -f monitoring/opentelemetry/applications/playlists-db/
kubectl apply -f monitoring/opentelemetry/applications/videos-web/
kubectl apply -f monitoring/opentelemetry/applications/videos-api/
kubectl apply -f monitoring/opentelemetry/applications/videos-db/
```

## Make sure our applications are running 

```
kubectl get pods
NAME                            READY   STATUS    RESTARTS   AGE  
playlists-api-d7f64c9c6-rfhdg   1/1     Running   0          2m19s
playlists-db-67d75dc7f4-p8wk5   1/1     Running   0          2m19s
videos-api-7769dfc56b-fsqsr     1/1     Running   0          2m18s
videos-db-74576d7c7d-5ljdh      1/1     Running   0          2m18s
videos-web-598c76f8f-chhgm      1/1     Running   0          100s 

```

## Make sure our ingress controller is running

```
kubectl -n ingress-nginx get pods
NAME                                        READY   STATUS    RESTARTS   AGE  
nginx-ingress-controller-6fbb446cff-8fwxz   1/1     Running   0          2m38s
nginx-ingress-controller-6fbb446cff-zbw7x   1/1     Running   0          2m38s

```

We'll need a fake DNS name `servicemesh.demo` <br/>
Let's fake one by adding the following entry in our hosts (`C:\Windows\System32\drivers\etc\hosts`) file: <br/>

```
127.0.0.1  servicemesh.demo

```

## Let's access our applications via Ingress 

```
kubectl -n ingress-nginx port-forward deploy/nginx-ingress-controller 80
```

## Access our application in the browser

We should be able to access our site under `http://servicemesh.demo/home/`

<br/>
<hr/>

# Getting Started with Istio

Firstly, I like to do most of my work in containers so everything is reproducable <br/>
and my machine remains clean.

## Get a container to work in
<br/>
Run a small `alpine linux` container where we can install and play with `istio`: <br/>


```
docker run -it --rm -v ${HOME}:/root/ -v ${PWD}:/work -w /work --net host alpine sh

# install curl & kubectl
apk add --no-cache curl nano
curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl
chmod +x ./kubectl
mv ./kubectl /usr/local/bin/kubectl
export KUBE_EDITOR="nano"

#test cluster access:
/work # kubectl get nodes
NAME                    STATUS   ROLES    AGE   VERSION
istio-control-plane   Ready    master   26m   v1.18.4

```

## Install Istio CLI

```
curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.6.12 TARGET_ARCH=x86_64 sh -

mv istio-1.6.12/bin/istioctl /usr/local/bin/
chmod +x /usr/local/bin/istioctl
mv istio-1.6.12 /tmp/

```

## Pre flight checks

Istio has a great capability to check compatibility with the target cluster <br/>

```
istioctl x precheck

```

## Istio Profiles

https://istio.io/latest/docs/setup/additional-setup/config-profiles/

```
istioctl profile list

istioctl install --set profile=default

kubectl -n istio-system get pods

istioctl proxy-status

```

# Mesh our video catalog services

There are 2 ways to mesh:

1) Automated Injection: 

You can set the `istio-injection=enabled` label on a namespace to have the istio side car automatically injected into any pod that gets created in the labelled namespace

This is a more permanent solution:
Pods will need to be recreated for injection to occur

```
kubectl label namespace/default istio-injection=enabled

# restart all pods to get sidecar injected
kubectl delete pods --all
```

2) Manual Injection:
This may only be temporary as your CI/CD system may roll out the previous YAML.
You may want to add this command to your CI/CD to keep only certain deployments part of the mesh.

```
kubectl get deploy
NAME            READY   UP-TO-DATE   AVAILABLE   AGE
playlists-api   1/1     1            1           8h 
playlists-db    1/1     1            1           8h 
videos-api      1/1     1            1           8h 
videos-db       1/1     1            1           8h 
videos-web      1/1     1            1           8h 

# Lets manually inject istio sidecar into our Ingress Controller:

kubectl -n ingress-nginx get deploy nginx-ingress-controller  -o yaml | istioctl kube-inject -f - | kubectl apply -f -

# You can manually inject istio sidecar to every deployment like this:

kubectl get deploy playlists-api -o yaml | istioctl kube-inject -f - | kubectl apply -f -
kubectl get deploy playlists-db -o yaml | istioctl kube-inject -f - | kubectl apply -f -
kubectl get deploy videos-api -o yaml | istioctl kube-inject -f - | kubectl apply -f -
kubectl get deploy videos-db -o yaml | istioctl kube-inject -f - | kubectl apply -f -
kubectl get deploy videos-web -o yaml | istioctl kube-inject -f - | kubectl apply -f -


```

# TCP \ HTTP traffic

Let's run a `curl` loop to generate some traffic to our site </br>
We'll make a call to `/home/` and to simulate the browser making a call to get the playlists, <br/>
we'll make a follow up call to `/api/playlists`

```
While ($true) { curl -UseBasicParsing http://servicemesh.demo/home/;curl -UseBasicParsing http://servicemesh.demo/api/playlists; Start-Sleep -Seconds 1;}
```


# Observability


## Grafana

```
kubectl apply -n istio-system -f /tmp/istio-1.6.12/samples/addons/grafana.yaml
```

We can see the components in the `istio-system` namespace:
```
kubectl -n istio-system get pods
```

Access grafana dashboards :

```
kubectl -n istio-system port-forward svc/grafana 3000
```

## Kiali 

`NOTE: this may fail because CRDs need to generate, if so, just rerun the command:`

```
kubectl apply -f /tmp/istio-1.6.12/samples/addons/kiali.yaml

kubectl -n istio-system get pods
kubectl -n istio-system port-forward svc/kiali 20001
```

# Virtual Services

## Auto Retry

Let's add a fault in the `videos-api` by setting `env` variable `FLAKY=true`

```
kubectl edit deploy videos-api
```

```
kubectl apply -f kubernetes/servicemesh/istio/retries/videos-api.yaml
```

We can describe pods using `istioctl`

```
# istioctl x describe pod <videos-api-POD-NAME>

istioctl x describe pod videos-api-584768f497-jjrqd
Pod: videos-api-584768f497-jjrqd
   Pod Ports: 10010 (videos-api), 15090 (istio-proxy)
Suggestion: add 'version' label to pod for Istio telemetry.
--------------------
Service: videos-api
   Port: http 10010/HTTP targets pod port 10010
VirtualService: videos-api
   1 HTTP route(s)
```

Analyse our namespace:

```
istioctl analyze --namespace default
```

## Traffic Splits

Let's deploy V2 of our application which has a header that's under development

```
kubectl apply -f kubernetes/servicemesh/istio/traffic-splits/videos-web-v2.yaml

# we can see v2 pods
kubectl get pods

```

Let's send 50% of traffic to V1 and 50% to V2 by using a `VirtualService`

```
kubectl apply -f kubernetes/servicemesh/istio/traffic-splits/videos-web.yaml
```

## Canary Deployments

Traffic splits has its uses, but sometimes we may want to route traffic to other  <br/>
parts of the system using feature toggles, for example, setting a `cookie`<br/>
<br/>
Let's send all users that have the cookie value `version=v2` to V2 of our `videos-web`.

```
kubectl apply -f kubernetes/servicemesh/istio/canary/videos-web.yaml
```

We can confirm this works, by setting the cookie value `version=v2` followed by accessing https://servicemesh.demo/home/ on a browser page <br/>

---

### Overview (`kubernetes/servicemesh/linkerd/readme.md`)

# Introduction to Linkerd

## We need a Kubernetes cluster

Lets create a Kubernetes cluster to play with using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/)

```
kind create cluster --name linkerd --image kindest/node:v1.30.4
```

## Deploy our microservices (Video catalog)

```
# ingress controller
kubectl create ns ingress-nginx
kubectl apply -f monitoring/opentelemetry/ingress-nginx/

# applications
kubectl apply -f monitoring/opentelemetry/applications/playlists-api/
kubectl apply -f monitoring/opentelemetry/applications/playlists-db/
kubectl apply -f monitoring/opentelemetry/applications/videos-web/
kubectl apply -f monitoring/opentelemetry/applications/videos-api/
kubectl apply -f monitoring/opentelemetry/applications/videos-db/
```

## Make sure our applications are running 

```
kubectl get pods
NAME                            READY   STATUS    RESTARTS   AGE  
playlists-api-d7f64c9c6-rfhdg   1/1     Running   0          2m19s
playlists-db-67d75dc7f4-p8wk5   1/1     Running   0          2m19s
videos-api-7769dfc56b-fsqsr     1/1     Running   0          2m18s
videos-db-74576d7c7d-5ljdh      1/1     Running   0          2m18s
videos-web-598c76f8f-chhgm      1/1     Running   0          100s 

```

## Make sure our ingress controller is running

```
kubectl -n ingress-nginx get pods
NAME                                       READY   STATUS      RESTARTS   AGE
ingress-nginx-admission-create-fxzx8       0/1     Completed   0          9m2s
ingress-nginx-admission-patch-fwc2k        0/1     Completed   2          9m2s
ingress-nginx-controller-d49697d5f-6qggd   1/1     Running     0          9m2s

```

We'll need a fake DNS name `servicemesh.demo` <br/>
Let's fake one by adding the following entry in our hosts (`C:\Windows\System32\drivers\etc\hosts`) file: <br/>

```
127.0.0.1  servicemesh.demo

```

## Let's access our applications via Ingress 

```
kubectl -n ingress-nginx port-forward svc/ingress-nginx-controller 80
```

## Access our application in the browser

We should be able to access our site under `http://servicemesh.demo/home/`

<br/>
<hr/>

# Getting Started with Linkerd

Firstly, I like to do most of my work in containers so everything is reproducible  <br/>
and my machine remains clean.

## Get a container to work in
<br/>
Run a small `alpine linux` container where we can install and play with `linkerd`: <br/>

```
docker run -it --rm -v ${HOME}:/root/ -v ${PWD}:/work -w /work --net host alpine sh

# install curl & kubectl
apk add --no-cache curl nano
curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl
chmod +x ./kubectl
mv ./kubectl /usr/local/bin/kubectl
export KUBE_EDITOR="nano"

#test cluster access:
/work # kubectl get nodes
NAME                    STATUS   ROLES           AGE   VERSION
linkerd-control-plane   Ready    control-plane   40m   v1.30.4

```

## Linkerd CLI

Lets download the `linkerd` command line tool <br/>
I grabbed the `edge-24.11.3` release using `curl`

You can go to the [releases](https://github.com/linkerd/linkerd2/releases/tag/edge-24.11.3) page to get it

```
curl -L -o linkerd https://github.com/linkerd/linkerd2/releases/download/edge-24.11.3/linkerd2-cli-edge-24.11.3-linux-amd64 
chmod +x linkerd && mv ./linkerd /usr/local/bin/

linkerd --help
```

## Pre flight checks

Linkerd has a great capability to check compatibility with the target cluster <br/>

```
linkerd check --pre

```

## Get the YAML

```
# install CRDs first
linkerd install --crds > ./kubernetes/servicemesh/linkerd/manifest/linkerd-edge-24.11.3-crds.yaml
linkerd install  > ./kubernetes/servicemesh/linkerd/manifest/linkerd-edge-24.11.3.yaml
```

## Install Linkerd

```
kubectl apply -f ./kubernetes/servicemesh/linkerd/manifest/linkerd-edge-24.11.3-crds.yaml
kubectl apply -f ./kubernetes/servicemesh/linkerd/manifest/linkerd-edge-24.11.3.yaml
```

Let's wait until all components are running

```
watch kubectl -n linkerd get pods
kubectl -n linkerd get svc
```

## Do a final check

```
linkerd check
```

## The dashboard

To access the `linkerd` dashboard, we need to install the viz extension

```
linkerd viz install | kubectl apply -f -
kubectl -n linkerd-viz port-forward svc/web 8084
```

# Mesh our video catalog services

There are 2 ways to mesh:

1) We can add an annotation to your deployment to persist the mesh if our YAML is part of a GitOps flow:
This is a more permanent solution:

```
  template:
    metadata:
      annotations:
        linkerd.io/inject: enabled
```

2) Or inject `linkerd` on the fly:
This may only be temporary as your CI/CD system may roll out the previous YAML

```
kubectl get deploy
NAME            READY   UP-TO-DATE   AVAILABLE   AGE
playlists-api   1/1     1            1           8h 
playlists-db    1/1     1            1           8h 
videos-api      1/1     1            1           8h 
videos-db       1/1     1            1           8h 
videos-web      1/1     1            1           8h 

kubectl get deploy playlists-api -o yaml | linkerd inject - | kubectl apply -f -
kubectl get deploy playlists-db -o yaml | linkerd inject - | kubectl apply -f -
kubectl get deploy videos-api -o yaml | linkerd inject - | kubectl apply -f -
kubectl get deploy videos-db -o yaml | linkerd inject - | kubectl apply -f -
kubectl get deploy videos-web -o yaml | linkerd inject - | kubectl apply -f -
kubectl -n ingress-nginx get deploy ingress-nginx-controller  -o yaml | linkerd inject - | kubectl apply -f -

```

# Generate some traffic

Let's run a `curl` loop to generate some traffic to our site </br>
We'll make a call to `/home/` and to simulate the browser making a call to get the playlists, <br/>
we'll make a follow up call to `/api/playlists`

```
While ($true) { curl -UseBasicParsing http://servicemesh.demo/home/;curl -UseBasicParsing http://servicemesh.demo/api/playlists; Start-Sleep -Seconds 1;}

linkerd -n default check --proxy

linkerd -n default stat deploy

```

# Add Faulty behaviour in videos API

```
kubectl edit deploy videos-api

#set environment FLAKY=true
```

# Service Profile 

```
linkerd profile -n default videos-api --tap deploy/videos-api --tap-duration 10s
```

After crafting the `serviceprofile`, we can apply it using `kubectl`

```
 kubectl apply -f kubernetes/servicemesh/linkerd/serviceprofiles/videos-api.yaml
```

We can see that service profile helps us add retry policies in place: <br/>

```
linkerd routes -n default deploy/playlists-api --to svc/videos-api -o wide
linkerd top deploy/videos-api
```

# Mutual TLS

We can validate if mTLS is working 

```
/work # linkerd -n default edges deployment
SRC                  DST             SRC_NS    DST_NS    SECURED       
playlists-api        videos-api      default   default   √
linkerd-prometheus   playlists-api   linkerd   default   √
linkerd-prometheus   playlists-db    linkerd   default   √
linkerd-prometheus   videos-api      linkerd   default   √
linkerd-prometheus   videos-db       linkerd   default   √
linkerd-prometheus   videos-web      linkerd   default   √
linkerd-tap          playlists-api   linkerd   default   √
linkerd-tap          playlists-db    linkerd   default   √
linkerd-tap          videos-api      linkerd   default   √
linkerd-tap          videos-db       linkerd   default   √
linkerd-tap          videos-web      linkerd   default   √

linkerd -n default tap deploy

```

---

### Overview (`kubernetes/tutorials/basics/README.md`)

# Kubernetes Tutorial: The Basics 

<a href="https://youtu.be/JeAHlTYB1Qk" title="How to learn Kubernetes in 2022"><img src="https://i.ytimg.com/vi/JeAHlTYB1Qk/hqdefault.jpg" width="40%" alt="How to learn Kubernetes in 2022" /></a>

This guide is aimed to fast-track your Kubernetes learning by focusing on a practical hands-on overview guide. </br>

When learning Kubernetes, you usually have an idea of some existing system you own and manage, or a website that you are building. </br>

The challenge is understanding which Kubernetes building blocks you need in order to run your workloads on Kubernetes </br>

<hr/>
<b>The problem:</b> "I want to adopt Kubernetes" </br>
<b>The problem:</b> "I have some common existing infrastructure"
<hr/>

<b>Our focus:</b> Solving the problem by learning each building block
in order to port our infrastructure to Kubernetes. 

## Understanding Containers

Before even looking at Kubernetes, you need to have a general understanding of containers like `docker`.
Your workloads need to fit in containers in order to be shipped on Kubernetes. </br>
Containers also have a bunch of assumptions that you need to meet.

* Defining the container - `Dockerfile`
* Serving traffic - Exposing ports
* Configuration - mount config files & secrets or `env` variables
* Data persistence - When a container is terminated, everything inside the container is gone
* Container entrypoint - The main process that runs in the container. Your app


### Docker installation 

* Install Docker [here](https://docs.docker.com/get-docker/)
* Let's take a look at [Wordpress on Docker Hub](https://hub.docker.com/_/wordpress)
* Build our docker file

## Create Network

```
docker network create wordpress
```

## Build & Test container images

 ### Wordpress example
```
cd kubernetes\tutorials\basics\

docker build -f dockerfiles/wordpress.dockerfile . -t aimvector/wordpress-example

```

* Run our Wordpress container

```
docker run -it --rm -p 80:80 --net wordpress aimvector/wordpress-example
```

The wordpress container will be visible on port 80 on `http://localhost/`

### MySQL example

* We need a database, let's take a look at [MySQL on Docker Hub](https://hub.docker.com/_/mysql)
* Build our MySQl container image

```
docker build -f dockerfiles/mysql.dockerfile . -t aimvector/mysql-example
```

* How do we run our MySQL ? 

We need to understand that databases require storage and state
Just like installing software on a server, it will store its files in some 
directory. Mysql stores its files under `/var/lib/mysql`

* We need a volume mount

Let's see how to run this in docker

```
mkdir data

docker run --rm -d `
--name mysql `
--net wordpress `
-e MYSQL_DATABASE=exampledb `
-e MYSQL_USER=exampleuser `
-e MYSQL_PASSWORD=examplepassword `
-e MYSQL_RANDOM_ROOT_PASSWORD=1 `
-v ${PWD}/data:/var/lib/mysql `
aimvector/mysql-example

# we can see the container with
docker ps
CONTAINER ID   IMAGE                     COMMAND                  CREATED         STATUS         PORTS                 NAMES
92cde663a3f5   aimvector/mysql-example   "docker-entrypoint.s…"   5 seconds ago   Up 3 seconds   3306/tcp, 33060/tcp   mysql

```

* Run Wordpress and connect it to MySQL

```
docker run -d `
--rm `
-p 80:80 `
--name wordpress `
--net wordpress `
-e WORDPRESS_DB_HOST=mysql `
-e WORDPRESS_DB_USER=exampleuser `
-e WORDPRESS_DB_PASSWORD=examplepassword `
-e WORDPRESS_DB_NAME=exampledb `
aimvector/wordpress-example
```

### Clean up 

```
docker rm -f wordpress
docker rm -f mysql
docker network rm wordpress
rm data
```

## Kubernetes Tools: kubectl

To manage and work with Kubernetes, you need `kubectl` </br>
Let's grab that from [here](https://kubernetes.io/docs/tasks/tools/)


## Run Kubernetes Locally

* Install `kubectl` to work with kubernetes

We'll head over to the [kubernetes](https://kubernetes.io/docs/tasks/tools/) site to download `kubectl` 

* Install the `kind` binary

You will want to head over to the [kind](https://kind.sigs.k8s.io/) site

* Create a cluster 

```
kind create cluster --image kindest/node:v1.23.5
```

## Namespaces 

```
kubectl create namespace cms
```

## Configmaps

[Environment Variables](https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) for pods

[How to use](https://kubernetes.io/docs/concepts/configuration/configmap/) configmaps

```
kubectl -n cms create configmap mysql `
--from-literal MYSQL_RANDOM_ROOT_PASSWORD=1

kubectl -n cms get configmaps
```

## Secrets

[How to use](https://kubernetes.io/docs/concepts/configuration/secret/) secrets in pods

```
kubectl -n cms create secret generic wordpress `
--from-literal WORDPRESS_DB_HOST=mysql `
--from-literal WORDPRESS_DB_USER=exampleuser `
--from-literal WORDPRESS_DB_PASSWORD=examplepassword `
--from-literal WORDPRESS_DB_NAME=exampledb

kubectl -n cms create secret generic mysql `
--from-literal MYSQL_USER=exampleuser `
--from-literal MYSQL_PASSWORD=examplepassword `
--from-literal MYSQL_DATABASE=exampledb


kubectl -n cms get secret

```

## Deployments

* Deployment [documentation](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/)

cd kubernetes\tutorials\basics

```
kubectl -n cms apply -f yaml/deploy.yaml
kubectl -n cms get pods
```

# Services

Services [documentation](https://kubernetes.io/docs/concepts/services-networking/service/)

```
kubectl -n cms apply -f .\yaml\service.yaml
kubectl -n cms get svc
```

# Storage Class

StorageClass [documentation](https://kubernetes.io/docs/concepts/storage/storage-classes/)

```
kubectl get storageclass
```

# Statefulset

Statefulset [documentation](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/)

Let's deploy our `mysql` using what we learnt above:

```
kubectl -n cms apply -f yaml/statefulset.yaml

kubectl -n cms get pods
```

## Persistent Volumes

[Documentation](https://kubernetes.io/docs/concepts/storage/persistent-volumes/)

## Port Forwarding

We can access private service endpoints or pods using `port-forward` :

```
kubectl -n cms get pods
kubectl -n cms port-forward <pod-name> 80
```

## Public Traffic

In order to make our site public, its common practise to expose web servers via  </br>
a proxy or API gateway. </br>
In Kubernetes, an Ingress is used.

## Ingress

To use an ingress, we need an ingress controller

```
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.1.3/deploy/static/provider/cloud/deploy.yaml

kubectl -n ingress-nginx get pods

kubectl -n ingress-nginx --address 0.0.0.0 port-forward svc/ingress-nginx-controller 80
```

Create an Ingress

```
kubectl -n cms apply -f yaml/ingress.yaml
```

---

### Overview (`linux/containers/podman/README.md`)

# Introduction to Podman



In this guide, we will dive into Linux containers with Podman and explore the full container lifecycle. </br>
We will cover what Podman is, how it differs from Docker, how to install it, and how to build and run containers with it. </br>

## What is Podman

[Podman](https://podman.io/) is a daemonless, open source, Linux native tool for working with Linux containers </br>

* Free and open source
* Fast, lightweight, and daemonless
* Can run Kubernetes pods directly
* Provides a Docker-compatible CLI (alternative to `docker`)

<i>Note: Podman does not require a background daemon. It uses a fork-exec model directly with the Linux kernel (via `runc`), which makes it easier to run rootless and more secure by default.</i>

Podman also supports "Pods", groups of containers similar to Kubernetes pods, and can generate Kubernetes YAML directly from running containers. </br>


[Documentation](https://docs.podman.io/)

## Installing Podman

Podman runs natively on Linux, and also works well inside WSL if you're on Windows. </br>
Running it on native Linux (or a Linux VM/WSL) gives the best portability and closest match to how it runs in production. </br>

<i>Note: Podman Desktop is a separate GUI tool for managing Podman on Windows/macOS. We'll cover it in a dedicated video.</i>

```shell
# Debian/Ubuntu
sudo apt-get update
sudo apt-get install -y podman
```

## Basic Commands

Podman's CLI mirrors Docker's, so most commands feel familiar right away:

```shell
podman --help
```

## Building a Container Image

Podman builds standard OCI images, so any existing `Dockerfile` works without changes. </br>
We'll quickly build images across a few different languages, C#, Node.js, Python, and Go, to show that the same Dockerfile format applies regardless of stack.

```shell
cd python
podman build -t aimvector/python:1.0.0

# note that podman is case sensitive and looking for Dockerfile instread of dockerfile .
podman build -f dockerfile -t aimvector/python:1.0.0

cd nodejs
podman build -f dockerfile -t aimvector/nodejs:1.0.0

```

Notice how podman prefixes our tag with `localhost` for local images:
Docker uses default `docker.io` as prefix. </br>
Remember Kubernetes used to default to `docker.io` too, and no longer does. </br>
It's good practice to get used to referencing full image names (e.g. `docker.io/library/nginx` instead of just `nginx`) </br>

## Running Containers

Run a container the same way you would with Docker:

```shell
#interactive with -it or background with -d
podman run -d -p 80:5000 localhost/aimvector/python:1.0.0

# view containers
podman ps 

# check container logs
podman logs

# stop containers
podman stop

# view stopped containers
podman ps -a 

# remove containers
podman rm 

```

## Compose 

Podman also supports Compose files through `podman compose`:
Note in my demo its not found, as podman "wraps" docker compose under the hood if it exists. </br>

We can install it with `sudo apt-get install -y podman-compose`

```shell
podman compose build
```

---

### Overview (`messaging/rabbitmq/kubernetes/readme.md`)

# RabbitMQ on Kubernetes

<a href="https://youtu.be/_lpDfMkxccc" title="rabbitmq-k8s"><img src="https://i.ytimg.com/vi/_lpDfMkxccc/hqdefault.jpg" width="20%" alt="rabbitmq-k8s" /></a> 

Create a cluster with [kind](https://kind.sigs.k8s.io/docs/user/quick-start/)

```
kind create cluster --name rabbit --image kindest/node:v1.18.4
```

## Namespace

```
kubectl create ns rabbits
```

## Storage Class

```
kubectl get storageclass
NAME                 PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
standard (default)   rancher.io/local-path   Delete          WaitForFirstConsumer   false                  84s
```

## Deployment

```
kubectl apply -n rabbits -f .\kubernetes\rabbit-rbac.yaml
kubectl apply -n rabbits -f .\kubernetes\rabbit-configmap.yaml
kubectl apply -n rabbits -f .\kubernetes\rabbit-secret.yaml
kubectl apply -n rabbits -f .\kubernetes\rabbit-statefulset.yaml
```

## Access the UI

```
kubectl -n rabbits port-forward rabbitmq-0 8080:15672
```
Go to htttp://localhost:8080 <br/>
Username: `guest` <br/>
Password: `guest` <br/>

# Message Publisher

```

cd messaging\rabbitmq\applications\publisher
docker build . -t aimvector/rabbitmq-publisher:v1.0.0

kubectl apply -n rabbits -f deployment.yaml
```

# Automatic Synchronization

https://www.rabbitmq.com/ha.html#unsynchronised-mirrors

```
rabbitmqctl set_policy ha-fed \
    ".*" '{"federation-upstream-set":"all", "ha-sync-mode":"automatic", "ha-mode":"nodes", "ha-params":["rabbit@rabbitmq-0.rabbitmq.rabbits.svc.cluster.local","rabbit@rabbitmq-1.rabbitmq.rabbits.svc.cluster.local","rabbit@rabbitmq-2.rabbitmq.rabbits.svc.cluster.local"]}' \
    --priority 1 \
    --apply-to queues
```

---

### Overview (`monitoring/logging/opentelemetry/README.md`)

# Logging with OpenTelemetry

<a href="https://youtu.be/bIxt1b0GOU4" title="fluentd-intro"><img src="https://i.ytimg.com/vi/bIxt1b0GOU4/hqdefault.jpg" width="20%" alt="opentel-intro" /></a>

---

### Overview (`monitoring/opentelemetry/kubernetes/README.md`)

# Introduction to OpenTelemetry Operator

OpenTelemetry Operator [documentation](https://opentelemetry.io/docs/platforms/kubernetes/operator/)

## We need a Kubernetes cluster

Lets create a Kubernetes cluster to play with using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/)

```
kind create cluster --name otel --image kindest/node:v1.34.0
```

Test our cluster:

```
kubectl get nodes
NAME                 STATUS   ROLES           AGE   VERSION
otel-control-plane   Ready    control-plane   40s   v1.34.0
```

## Helm charts

### OpenTelemetry Operator chart
```
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
helm repo update

helm search repo open-telemetry --versions
```

We'll install version `0.93.1` at the time of this guide.
I would suggest to make sure the version you pick is compatible with the Kubernetes version you are running. </br> 

```
OTEL_VERSION=0.93.1
```

### Cert-manager chart

We will need cert-manager deployed which Otel uses for local TLS certificate management

```
helm repo add jetstack https://charts.jetstack.io

helm search repo jetstack --versions
```

We'll install version `v1.18.2` of cert-manager at the time of this guide which is compatible with our Kubernetes version. </br>

```
CERTMANAGER_VERSION=v1.18.2
```

### Install helm charts 

Install cert-manager:

```
helm install \
cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--version $CERTMANAGER_VERSION \
--set crds.enabled=true \
--set startupapicheck.timeout="5m"
```

Install OpenTelemetry Operator:

```
helm install opentelemetry-operator open-telemetry/opentelemetry-operator \
--namespace opentelemetry-operator-system \
--create-namespace \
--version $OTEL_VERSION \
--values=monitoring/opentelemetry/kubernetes/values.yaml
```

View our install: 

```
kubectl get pods -n cert-manager
kubectl get pods -n opentelemetry-operator-system
```

## Create a Collector

In this guide I copied a starter collector from the [official documentation](https://opentelemetry.io/docs/platforms/kubernetes/operator/#getting-started) and changed some of the values. 

To split out the guides, we'll have separate collectors for logs, metrics and traces.

Let's deploy our collectors in a central `monitoring` namespace:

```

kubectl create namespace monitoring

kubectl apply -n monitoring -f monitoring/opentelemetry/kubernetes/collector-tracing.yaml
```

View our collector:

```
kubectl -n monitoring get pods
kubectl -n monitoring get svc
```

## Tracing

### Instrumentation 

To start receiving traces, we'll be using the OpenTelemetry [auto instrumentation](https://opentelemetry.io/docs/platforms/kubernetes/operator/automatic/) to inject the instrumentation into our Kubernetes pods. 

To make use of the auto injection for Opentelemetry, we use the `Instrumentation` resources. 

Note that I am creating this in the default namespace next to where my applications are. 

```
kubectl apply -f monitoring/opentelemetry/kubernetes/instrumentation.yaml
```

### Deploy Microservices

Build applications: 

```
docker compose --file monitoring/opentelemetry/docker-compose.yaml build
```

Load images into `kind`:

```
kind load docker-image aimvector/service-mesh:videos-web-1.0.0 --name otel
kind load docker-image aimvector/service-mesh:playlists-api-1.0.0 --name otel
kind load docker-image aimvector/service-mesh:videos-api-1.0.0 --name otel
```

```
kubectl apply -f monitoring/opentelemetry/applications/playlists-api/
kubectl apply -f monitoring/opentelemetry/applications/playlists-db/
kubectl apply -f monitoring/opentelemetry/applications/videos-web/
kubectl apply -f monitoring/opentelemetry/applications/videos-db/
kubectl apply -f monitoring/opentelemetry/applications/videos-api/
```

Generate some traffic with `port-forward` 

```
kubectl port-forward svc/videos-web 80:80
kubectl port-forward svc/playlists-api 81:80
```

### Tracing Data Store

In this guide, we'll use a Tempo database for tracing data

```
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update

helm search repo  grafana/tempo

TEMPO_VERSION=1.23.3

helm install tempo grafana/tempo \
    --create-namespace \
    --namespace grafana \
    --version $TEMPO_VERSION \
    --values monitoring/opentelemetry/kubernetes/tempo.yaml
```

### Metrics Data Store

In this guide, we'll use a Prometheus for our metrics data

```
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm search repo prometheus-community --versions

PROMETHEUS_STACK_VERSION=77.5.0

helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack \
  --version ${PROMETHEUS_STACK_VERSION} \
  --namespace prometheus-operator-system \
  --create-namespace \
  --set prometheusOperator.enabled=true \
  --set prometheusOperator.nodeSelector."kubernetes\.io/os"=linux \
  --set prometheusOperator.fullnameOverride="prometheus-operator" \
  --set prometheusOperator.manageCrds=true \
  --set alertmanager.enabled=false \
  --set grafana.enabled=false \
  --set prometheus-node-exporter.enabled=false \
  --set nodeExporter.enabled=false \
  --set kubeStateMetrics.enabled=false \
  --set prometheus.enabled=false

kubectl -n prometheus-operator-system get pods 

# Deploy our dedicated Prometheus for metrics storage

kubectl apply -n monitoring -f monitoring/opentelemetry/kubernetes/prometheus.yaml
```

### Dashboards

In this guide I use a simple Grafana for dashboards

```
helm search repo grafana/grafana

GRAFANA_VERSION=9.4.4

helm install grafana grafana/grafana \
  --namespace grafana \
  --version $GRAFANA_VERSION \
  --values monitoring/opentelemetry/kubernetes/grafana.yaml

```

### Access Grafana 

We can `port-forward` to Grafana

```
kubectl -n grafana port-forward svc/grafana 3000:80
```

---

### Overview (`monitoring/opentelemetry/metrics/README.md`)

# OpenTelemetry Metrics

<a href="https://youtu.be/bIxt1b0GOU4" title="fluentd-intro"><img src="https://i.ytimg.com/vi/bIxt1b0GOU4/hqdefault.jpg" width="20%" alt="opentel-intro" /></a> 


## Build the applications 

To build the applications navigate your terminal to the location of the compose file

```
cd monitoring/opentelemetry/
```

Build: 

```
docker compose build
```

## Docker

Start up the stack using local docker compose, by running:

```
docker compose up
```

## Kubernetes

For Kubernetes, we will use the OpenTelemetry Operator. </br>
See that guide over [here](../kubernetes/README.md)


## Generate from Traffic

You can generate some traffic by visiting the application on http://localhost

<i>Note: You will need port 80 and 81 open for the applications to function</i>

## Access Grafana

You can access Grafana on http://localhost:3000 to test the Tempo datasource.

---

### Overview (`monitoring/opentelemetry/tracing/README.md`)

# Tracing with OpenTelemetry


<a href="https://youtu.be/bIxt1b0GOU4" title="fluentd-intro"><img src="https://i.ytimg.com/vi/bIxt1b0GOU4/hqdefault.jpg" width="20%" alt="opentel-intro" /></a> 

<a href="https://youtu.be/LQOeaxfiAt8" title="fluentd-intro"><img src="https://i.ytimg.com/vi/LQOeaxfiAt8/hqdefault.jpg" width="20%" alt="opentel-intro" /></a> 


## Build the applications 

To build the applications navigate your terminal to the location of the compose file

```
cd monitoring/opentelemetry/
```

Build: 

```
docker compose build
```

## Docker

Start up the stack using local docker compose, by running:

```
docker compose up
```

## Kubernetes

For Kubernetes, we will use the OpenTelemetry Operator. </br>
See that guide over [here](../kubernetes/README.md)


## Generate from Traffic

You can generate some traffic by visiting the application on http://localhost

<i>Note: You will need port 80 and 81 open for the applications to function</i>

## Access Grafana

You can access Grafana on http://localhost:3000 to test the Tempo datasource.

---

### Overview (`monitoring/prometheus/kubernetes/readme.md`)

# Kubernetes monitoring with Prometheus

Kubernetes [1.14.8](./1.14.8/readme.md) <br/>
Kubernetes [1.15-1.17](./1.15-1.17/readme.md) <br/>
Kubernetes [1.18.4](./1.18.4/readme.md) <br/>
Kubernetes [1.23](./1.23/README.md) <br/>

## Prometheus Overview

Prometheus Overview and Architecture - Video : [here](https://youtu.be/5o37CGlNLr8) <br/>
Source: [here](https://github.com/marcel-dempers/docker-development-youtube-series/releases/tag/prometheus-operator-1)<br/>

## Application Monitoring 

Monitoring Python code with Prometheus - Video : [here](https://youtu.be/HzEiRwJP6ag) <br/>
Monitoring Nodejs code with Prometheus - Video : [here](https://youtu.be/m2zM3zOZl34) <br/>
Monitoring C# code with Prometheus - Video : [here](https://youtu.be/o4tdSrFnkvw) <br/>

Source: [here](https://github.com/marcel-dempers/docker-development-youtube-series/releases/tag/prometheus-operator-1)<br/>

## Prometheus Operator guide

Video : [here](https://youtu.be/LQpmeb7idt8) <br/>
Source Code tested on K8s version 1.14.8 <br/>
Source: [here](https://github.com/marcel-dempers/docker-development-youtube-series/releases/tag/prometheus-operator-1)<br/>

## Node Exporter guide

Video : [here](https://youtu.be/1-tRiThpFrY) <br/>
Source Code tested on K8s version 1.14.8 <br/>
Source: [here](https://github.com/marcel-dempers/docker-development-youtube-series/releases/tag/prometheus-node-exporter-1)<br/>

---

### Overview (`monitoring/prometheus/sharding/README.md`)

# Prometheus Basic Sharding
## a Video reference guide

To run any of the commands, please ensure you open a terminal and navigate to the path where this readme is located.

## Start example applications 

To start our example applications, we'll need to navigate to the folder where we have our example services in our docker-compose file 

```
cd monitoring\prometheus\

docker compose up -d --build go-application
docker compose up -d --build python-application
docker compose up -d --build dotnet-application
docker compose up -d --build nodejs-application
```

## Start Prometheus 

```
cd sharding
docker compose up -d prometheus-00
docker compose up -d prometheus-01
```

---

### Overview (`monitoring/prometheus/thanos/README.md`)

# Prometheus Thanos Basics

This guide relies on our Prometheus Application Monitoring Guide. </br>

## Start example applications 

To start our example applications, we'll need to navigate to the folder where we have our example services in our docker-compose file 

```
cd monitoring\prometheus\

docker compose up -d --build go-application
docker compose up -d --build python-application
docker compose up -d --build dotnet-application
docker compose up -d --build nodejs-application
```

## Start Sharded Prometheus Instances

The following will start two shards of Prometheus, using the `hasmod` sharding technique. </br>
Basically two random services will be selected and scraped by each Prometheus shard. </br

```
cd sharding
docker compose up -d prometheus-00
docker compose up -d prometheus-01
```

# Start the Thanos Components 

The following will start two Thanos side cars, one for each Prometheus instance. 
It will also start a Minio S3 service
```
cd ../thanos
docker compose up
```

---

### Overview (`python/introduction/part-2.files/README.md`)

# Introduction to Python: FILES

In Python, dealing with files is very common and is a very important part of 
programming for a number of reasons: </br>

* Applications may need to read configuration files
* In Data science, data is often sourced from files (`CSV`, `XML`, `JSON`, etc)
* Data is often analysed in Python when its written in different stages of analysis
* DevOps engineers often stores the state of infrastructure or data as files for automation purposes.

Files are not the endgame for storage. </br>
Remember there are things like Caches and Databases. </br>
But before learning those things, file handling is the best place to start. </br>

## Python Dev Environment

The same as Part 1, we start with a [dockerfile](./dockerfile) where we declare our version of `python`.

```
cd python\introduction\part-2.files

docker build --target dev . -t python
docker run -it -v ${PWD}:/work python sh

/work # python --version
Python 3.9.6

```

## Our application

Firstly we have a class to define what a customer looks like:
```
class Customer:
  def __init__(self, c="",f="",l=""):
    self.customerID = c
    self.firstName  = f
    self.lastName   = l
  def fullName(self):
    return self.firstName + " " + self.lastName
```

Then we need a function which returns our customers:
```
def getCustomers():
  customers = {
    "a": Customer("a","James", "Baker"),
    "b": Customer("b", "Jonathan", "D"),
    "c": Customer("c", "Aleem", "Janmohamed"),
    "d": Customer("d", "Ivo", "Galic"),
    "e": Customer("e", "Joel", "Griffiths"),
    "f": Customer("f", "Michael", "Spinks"),
    "g": Customer("g", "Victor", "Savkov"),
    "h" : Customer("h", "Marcel", "Dempers")
  }
  return customers
```

Here is a function to return a specific customer:
```
def getCustomer(customerID):
  customer = getCustomers()
  return customer[customerID]
```

## Opening Files

Python provides an `open` function to open files. </br>
`open()` takes a file path\name and access mode

```
"r" - Read - Default value. Opens a file for reading, error if the file does not exist
"a" - Append - Opens a file for appending, creates the file if it does not exist
"w" - Write - Opens a file for writing, creates the file if it does not exist
"x" - Create - Creates the specified file, returns an error if the file exists
```

Try open a file that holds our customer data:

```
open("customers.log")
```

We can see the file does not exist:

```
/work # python src/app.py
Traceback (most recent call last):
  File "/work/src/app.py", line 26, in <module>
    open("customers.log")
FileNotFoundError: [Errno 2] No such file or directory: 'customers.log'
```

Let's use what we learned (`if` statements), to check if the file exists!
We'll need a built in library for handling files

```
import os.path
```

Then we can use the `os.path.isfile("customers.log")` command to check if the file exists

```
os.path.isfile("customers.log")
```

Using `if` logic we can check if the file is there:

```
if os.path.isfile("customers.log"):
  print("file exists")
else:
  print("file does not exists")
```

Now we know the file does not exist, but if it did, we can now read it with `open`


```
f = open("customers.log")
```

Let's also loop each customer in the file and print it

```
for customer in f:
  print(customer)
f.close()
```

Now we know the file does not exist, let's create it!

```
customers = getCustomers()
for customerID in customers:
  c = customers[customerID]
  f.write(c.customerID + "," + c.firstName + "," + c.lastName)
```

Now if we run our code the first time, it will create and populate the file as it does not exist,
and will read the file and display the content on the second run. </br>

Instead of looping each line in the file, we can read the entire file with the file's `read()` function:

```
print(f.read())
```

## Comma-Separated Values : CSV

As we can see, our `customers.log` file is in CSV format with every field separated by commas. </br>

So far, we've demonstrated using primitives to read and write to files to store our data.
When looping data structures like dictionaries and writing each line one by one to a file
will use a lot of CPU if the data is large. </br>

### CSV: Reading our file

To work with CSV's, we need to import a library
We also need to add headers to our file so it makes setting fields easier:

```
customerID, firstName, lastName
```

```
import csv
with open('customers.log', newline='') as customerFile:
  reader = csv.DictReader(customerFile)
  for row in reader:
    #print(row)
    print("customer id:" + row['customerID'] + " fullName : " + row['firstName'] + " " + row['lastName'])

```
### CSV: Writing our file

Create an array with our field headers

```
fields = ['customerID', 'firstName', 'lastName']
with open('customers.log', 'w', newline='') as customerFile:
  writer = csv.writer(customerFile)
  writer.writerow(fields)
  customers = getCustomers()
  for customerID in customers:
    customer = customers[customerID]
    writer.writerow([customer.customerID, customer.firstName, customer.lastName])
```

## Putting it all together

Now that we have code that reads and writes to a file, let's update our `getCustomers` function to return 
customers from our file. </br>

We read the file if it exists, read it into a list and convert the list to a dictionary:

```
def getCustomers():
  if os.path.isfile("customers.log"):
    with open('customers.log', newline='') as customerFile:
      reader = csv.DictReader(customerFile)
      l = list(reader)
      customers = {c["customerID"]: c for c in l}
      return customers
  else: 
    return {}
```

We can test our function to see it working:

```
customers = getCustomers()
for customerID in customers:
  print(customers[customerID])
```

Let's also create a function to update customers

```
def updateCustomers(customers):
  fields = ['customerID', 'firstName', 'lastName']
  with open('customers.log', 'w', newline='') as customerFile:
    writer = csv.writer(customerFile)
    writer.writerow(fields)
    for customerID in customers:
      customer = customers[customerID]
      writer.writerow([customer.customerID, customer.firstName, customer.lastName])
```

Let's test our two functions by deleting our file and recreate it using our functions:

```
customers = {
    "a": Customer("a","James", "Baker"),
    "b": Customer("b", "Jonathan", "D"),
    "c": Customer("c", "Aleem", "Janmohamed"),
    "d": Customer("d", "Ivo", "Galic"),
    "e": Customer("e", "Joel", "Griffiths"),
    "f": Customer("f", "Michael", "Spinks"),
    "g": Customer("g", "Victor", "Savkov"),
    "h" : Customer("h", "Marcel", "Dempers")
}

#save it
updateCustomers(customers)

#add another test customer
test = Customer("t", "Test", "Customer")
customers["t"] = test

#save it
updateCustomers(customers)

#see the changes
customers = getCustomers()
for customer in customers:
  print(customers[customer])
```

## Docker

Let's build our container image and run it while mounting our customer file

Our final `dockerfile`
```
FROM python:3.9.6-alpine3.13 as dev

WORKDIR /work

FROM dev as runtime
COPY ./src/ /app 

ENTRYPOINT [ "python", "/app/app.py" ]

```

Build and run our container.
Notice the `customers.log` file get created if it does not exists.

```
cd python\introduction\part-2.files

docker build . -t customer-app

docker run -v ${PWD}:/work -w /work customer-app

```

---

### Overview (`python/introduction/part-3.json/README.md`)

# Introduction to Python: JSON

JSON is a very important format for storing data in Software engineering. </br>
JSON is popular for the following scenarios :

* Popular standard for data structures in general.
* Applications may talk to each other by passing JSON data back and forth (HTTP Web APIs).
* Applications often store configuration in JSON format as a configuration file.
* Data may be stored in databases or caches in JSON format.
* DevOps engineers may store infrastructure configuration in JSON format.

## Python Dev Environment

The same as Part 1, we start with a [dockerfile](./dockerfile) where we declare our version of `python`.

```
FROM python:3.9.6-alpine3.13 as dev

WORKDIR /work
```

Let's build and start our container: 

```
cd python\introduction\part-3.json

docker build --target dev . -t python
docker run -it -v ${PWD}:/work python sh

/work # python --version
Python 3.9.6

```

## Our application

Firstly we have a class to define what a customer looks like:
```
class Customer:
  def __init__(self, c="",f="",l=""):
    self.customerID = c
    self.firstName  = f
    self.lastName   = l
  def fullName(self):
    return self.firstName + " " + self.lastName
```

Then we need a function which returns our customers:
```
def getCustomers():
  customers = {
    "a": Customer("a","James", "Baker"),
    "b": Customer("b", "Jonathan", "D"),
    "c": Customer("c", "Aleem", "Janmohamed"),
    "d": Customer("d", "Ivo", "Galic"),
    "e": Customer("e", "Joel", "Griffiths"),
    "f": Customer("f", "Michael", "Spinks"),
    "g": Customer("g", "Victor", "Savkov"),
    "h" : Customer("h", "Marcel", "Dempers")
  }
  return customers
```

Here is a function to return a specific customer:
```
def getCustomer(customerID):
  customer = getCustomers()
  return customer[customerID]
```

Test our functions:

```
customers = getCustomers()
customer = customers["h"]

print(customers)
print(customer.fullName)
```

## Files

In [part-2](../part-2.files/README.md) we learnt about reading and writing to files. </br>
We changed our functions to get customers from file and update customers by writing the data back to file. </br>

Let's do that quick:

## Get Customers

```
import os.path
import csv

def getCustomers():
  if os.path.isfile("customers.log"):
    with open('customers.log', newline='') as customerFile:
      reader = csv.DictReader(customerFile)
      l = list(reader)
      customers = {c["customerID"]: c for c in l}
      return customers
  else: 
    return {}
```

## Update Customers

Let's create a function to update our customers:

```
def updateCustomers(customers):
  fields = ['customerID', 'firstName', 'lastName']
  with open('customers.log', 'w', newline='') as customerFile:
    writer = csv.writer(customerFile)
    writer.writerow(fields)
    for customerID in customers:
      customer = customers[customerID]
      writer.writerow([customer.customerID, customer.firstName, customer.lastName])
```

## Test 

Let's test our two functions to ensure they work

```
customers = {
    "a": Customer("a","James", "Baker"),
    "b": Customer("b", "Jonathan", "D"),
    "c": Customer("c", "Aleem", "Janmohamed"),
    "d": Customer("d", "Ivo", "Galic"),
    "e": Customer("e", "Joel", "Griffiths"),
    "f": Customer("f", "Michael", "Spinks"),
    "g": Customer("g", "Victor", "Savkov"),
    "h" : Customer("h", "Marcel", "Dempers")
}

#save it
updateCustomers(customers)

#see the changes
customers = getCustomers()
for customer in customers:
  print(customers[customer])

```

## JSON

JSON is a very simple data structure for building objects.
you can define any type of object and data using JSON. </br>

In JSON, objects are defined by open and close brackets, like `{}` </br>
Arrays or lists are defined as square brackets, like `[]` </br>

We can therefore define a customer like :
`Notice keys and values have quotes, and separated by colon`

```
{
  "customerID" : "a",
  "firstName: "Bob",
  "lastName": "Smith"
}

```
We can define an array of customers in JSON by declaring our array with square brackets and having customer objects inside, separated by commas:

```
# Example Array:
[{customer-1},{customer-2},{customer-3},{customer-4} ]
```

## JSON Library

Python provides a library for dealing with JSON. </br>
Let's take our customer dictionary, and convert it to json structure and 
finally write it to file using our update function.

```
import json
```

Now first things first, we need to convert our customers dictionary to a JSON structured string so we can store it to file. </br>

The `json` library allows us to convert dictionaries to files using 
the `dumps` function:

```
jsonData = json.dumps(customers)
print(jsonData)
```

But if we run this we get an error:
`TypeError: Object of type Customer is not JSON serializable`

That is because the library can only deal with full dictionaries.
Taking a closer look at our dictionary, our customer is a class.

```
# our key is a string
# our value is a class
"h" : Customer("h", "Marcel", "Dempers")
```

`json.dumps()` only allows full dictionaries so we need our customer object to be in dictionary form like so:

```
"h": { "customerID": "h", "firstName" : "Marcel", "lastName" : "Dempers"}
```

To fix our dictionary, we can iterate it and build a new one

```
# start with an empty one
customerDict = {}

#populate our new dictionary
for id in customers:
  customerDict[id] = customers[id].__dict__

#print it
print(customerDict)
```

Now we can convert our customer dictionary to json

```
customerJSON = json.dumps(customerDict)
print(customerJSON)
```

Now we've converted our data into JSON, we can view this in a new tab window and change the language mode to JSON to visualise the data in our new format.

Now essentially our application will use dictionaries when working with the data
but store and transport it as JSON. </br>

Therefore our update function should take a dictionary and convert it to JSON
and store it.

```
def updateCustomers(customer):
  with open('customers.json', 'w', newline='') as customerFile:
    customerJSON = json.dumps(customer) 
    customerFile.write(customerJSON)
```

We also need to change our `getCustomers` function to read the JSON file
and convert that data correctly back to a dictionary for processing.

```
def getCustomers():
  if os.path.isfile("customers.json"):
    with open('customers.json', newline='') as customerFile:
      data = customerFile.read()
      customers = json.loads(data) 
      return customers
  else: 
    return {}
```

Finally let's test our functions :

```
customers = getCustomers()
print(customers)
```

It's also very easy to add a customer to our JSON data using our class and using pythons internal `__dict__` property to convert the object to a dictionary and add it to our customers dictionary

```
customers["i"] = Customer("i", "Bob", "Smith").__dict__
updateCustomers(customers)
```

## Docker

Let's build our container image and run it while mounting our customer file

Our final `dockerfile`
```
FROM python:3.9.6-alpine3.13 as dev

WORKDIR /work

FROM dev as runtime
COPY ./src/ /app 

ENTRYPOINT [ "python", "/app/app.py" ]

```

Build and run our container.
Notice the `customers.json` file gets created if it does not exist.

```
cd python\introduction\part-3.json

docker build . -t customer-app

docker run -v ${PWD}:/work -w /work customer-app

```

---

### Overview (`python/introduction/part-4.http/README.md`)

# Introduction to Python: HTTP (Flask)

So far, we've learnt Python fundamentals, worked with data, files and 
more importantly, basic data structures like `csv` and `json`.

JSON is a very popular format for now only storing, but transporting data in web HTTP applications. Files are important because we may 
need to read configuration files for our application. </br>

One application could make a web request to ask for customer data from our application. By sending back JSON, the receiving application can easily process the data it gets.

Be sure to checkout [Part 2: Files](../part-2.files) </br>
As well as [Part 3: JSON](../part-3.json) </br>

## Python Dev Environment

The same as Part 1, we start with a [dockerfile](./dockerfile) where we declare our version of `python`.

```
FROM python:3.9.6-alpine3.13 as dev

WORKDIR /work
```

Let's build and start our container: 

```
cd python\introduction\part-4.http

docker build --target dev . -t python
docker run -it -v ${PWD}:/work python sh

/work # python --version
Python 3.9.6

```

## Our application

We're going to use what we've learnt in part 1,2 & 3 and create
our customer app that stores customer data </br>
Firstly we have to import our dependencies:

```
import os.path
import csv
import json

```

Then we have a class to define what a customer looks like:
```
class Customer:
  def __init__(self, c="",f="",l=""):
    self.customerID = c
    self.firstName  = f
    self.lastName   = l
  def fullName(self):
    return self.firstName + " " + self.lastName
```

Then we need a function which returns our customers:
```
def getCustomers():
  if os.path.isfile("customers.json"):
    with open('customers.json', newline='') as customerFile:
      data = customerFile.read()
      customers = json.loads(data)
      return customers
  else: 
    return {}
```

Here is a function to return a specific customer:
```
def getCustomer(customerID):
  customer = getCustomers()
  return customer[customerID]
```
And finally a function for updating our customers:

```
def updateCustomers(customers):
  with open('customers.json', 'w', newline='') as customerFile:
    customerJSON = json.dumps(customers)
    customerFile.write(customerJSON)
```

In the previous episode, we've created a `json` file to hold all our customers. </br>
We've learnt how to read and write to file and temporarily use the file for our storage. </br>

```
{
  "a": {
    "customerID": "a",
    "firstName": "James",
    "lastName": "Baker"
  },
  "b": {
    "customerID": "b",
    "firstName": "Jonathan",
    "lastName": "D"
  },
  "c": {
    "customerID": "c",
    "firstName": "Aleem",
    "lastName": "Janmohamed"
  },
  "d": {
    "customerID": "d",
    "firstName": "Ivo",
    "lastName": "Galic"
  },
  "e": {
    "customerID": "e",
    "firstName": "Joel",
    "lastName": "Griffiths"
  },
  "f": {
    "customerID": "f",
    "firstName": "Michael",
    "lastName": "Spinks"
  },
  "g": {
    "customerID": "g",
    "firstName": "Victor",
    "lastName": "Savkov"
  }
}
```

Now that we have our customer data, we can read and update the data with our two simple functions: 


```
customers = getCustomers()
print(customers)
```

Let's add a customer and write it back to file:

```
customers["h"] = Customer("h", "Marcel", "Dempers").__dict__
updateCustomers(customers)
```

Now `customers.json` has the new entry if we rerun our code:

```
python src/app.py
```

## Flask

Checkout official Flask documentation [here](https://flask.palletsprojects.com/en/2.0.x/)

A minimal flask application looks like this: 

```
from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "<p>Hello, World!</p>"
```

To get this to work, we need to look at Pythons Library system, or `pip`

## External Packages

If we look at the `from flask import Flask` statement, it assumes we have a `flask` dependency. Our previous import statements worked, because they are built-in libraries in python. </br>
Flask is an external package. </br>
External packages are managed by [pip](https://pypi.org/project/pip/)

We may use `pip` to install `Flask`. If we need to connect to databases, we may install packages to do so. Like a `mysql` or a `redis` package. </br>

With `pip`, we define our dependencies in a `requirements.txt` file.
We can check for the latest version of `Flask` [here](https://pypi.org/project/Flask/)

We can also use `pip install` commands to install dependencies. </br>
However, this means we need a new `pip install` command for every dependency which will grow as 
our application and needs grow. Best is to use a `requirements.txt` file.

Let's create a `requirements.txt` file:

```
Flask == 2.0.2
```

We can install our dependencies using:

```
pip install -r src/requirements.txt
```

Let's implement the minimal example we posted above. </br>
To run our application, note that the `app.py` runs as per usual. <br/>
But to start `Flask`, we can follow the instructions as per the official document:

```
export FLASK_APP=src/app
flask run -h 0.0.0 -p 5000
```

Note that we cannot access `http://localhost:5000` because our app is running in docker. </br>
To access it, we need to restart our container and this time expose our port.

```
exit
docker run -it -p 5000:5000 -v ${PWD}:/work python sh

#get our dependencies and start our application
pip install -r requirements.txt
export FLASK_APP=src/app
flask run -h 0.0.0 -p 5000
```
Now we can access our app on `http://localhost:5000/`

## Routes

Web servers allow us to define various URLs or routes. </br>
Currently we can see logs showing `GET` requests for route `/` </br>
Notice the HTTP status code `200` indicating success.

```
 * Running on http://0.0.0:5000/ (Press CTRL+C to quit)
172.17.0.1 - - [22/Sep/2021 23:10:14] "GET / HTTP/1.1" 200 -
172.17.0.1 - - [22/Sep/2021 23:10:14] "GET /favicon.ico HTTP/1.1" 404 -
172.17.0.1 - - [22/Sep/2021 23:12:20] "GET / HTTP/1.1" 200 -
172.17.0.1 - - [22/Sep/2021 23:12:21] "GET / HTTP/1.1" 200 -
172.17.0.1 - - [22/Sep/2021 23:12:21] "GET / HTTP/1.1" 200 -
```

Routes allow us to have many endpoints, so we could have endpoints such as:

```
/all            --> which returns all customers
/get/<customerID>    --> which returns one customer by CustomerID
/add         --> which adds or updates a customer
```

Defining these routes are pretty straight forward, see [docs](https://flask.palletsprojects.com/en/2.0.x/quickstart/#variable-rules)

Let's define our routes:

```

@app.route("/")
def get_customers():
    return "<p>Hello, get_customers!</p>"

@app.route("/get/<string:customerID>", methods=['GET'])
def get_customer(customerID):
    return "<p>Hello, get!</p>"

@app.route("/add", methods=['POST'])
def add_customer(customer):
    return "<p>Hello, add!</p>"

```

Now that we've built our URLs using routes, next we need to understand HTTP methods

## HTTP Methods

There are a number of HTTP methods for web services, popular ones being `POST` and `GET`

`GET` method is for general retrieval of data </br>
`POST` method is for passing data to our service </br>

Let's setup each of our routes with dedicated HTTP Methods

```
@app.route("/", methods=['GET'])
def get_customers():
    return "<p>Hello, get_customers!</p>"

@app.route("/get/<string:customerID>", methods=['GET'])
def get_customer(customerID):
    return "<p>Hello, get!</p>"

@app.route("/add", methods=['POST'])
def add_customer():
    return "<p>Hello, add!</p>"

```

Now we can see if we access `http://localhost:5000/add`
We get: `Method Not Allowed` , because it only accepts `POST` and browser by default, does `GET`

Let's fill out our routes

### Get Customers

```
@app.route("/", methods=['GET'])
def get_customers():
    customers = getCustomers()
    return json.dumps(customers)
```

### Get Customer

```
@app.route("/get/<string:customerID>", methods=['GET'])
def get_customer(customerID):
     customer = getCustomer(customerID)
     return customer
```

## HTTP Status Codes 

You can access your browser's developer tools and see the network tab </br>
Status codes are important for troubleshooting production traffic.

`200` = Success. </br>

We can then access a customer via the `/get/<customerID>` route.
Our Customer ID's are `a` to `h` and notice we get an `Internal Server Error` if
we pass an incorrect ID. </br> 

`500` = Internal Server Error </br>
This means something went wrong in our code. See the logs indicating a `KeyError`, because we're trying to access a key in our customer dictionary that does not exist. </br>

Let's handle that error

```
#update our getCustomer function to check if the key exists

def getCustomer(customerID):
  customers = getCustomers()
  if customerID in customers:
    return customers[customerID]
  else: 
    return {} 
```

Note when we rerun this, we get a blank customer back. </br>
Also note we're getting a `200` status code. </br>
This may not be super appropriate, as our customer we requested is technically not found. There is a status code for that </br>

`404` = Not Found </br>

Let's handle the status code.

```
@app.route("/get/<string:customerID>", methods=['GET'])
def get_customer(customerID):
    customer = getCustomer(customerID)
    if customer == {}:
      return {}, 404
    else:
      return customer
```

## Add Customer 

In order to add a customer, we need to be able to read request data. </br>
More about `Requests` [here](https://flask.palletsprojects.com/en/2.0.x/api/#flask.Request)

Import requests library
```
from flask import request
```

Read the request body as JSON:

```
@app.route("/add", methods=['POST'])
def add_customer():
    print(request.json)
    return "success", 200
```
Let's test that!

```
curl -X POST http://localhost:5000/add -d '{ "data" : "hello" }'
```
Notice our print message is `None`, this is because we need to specify content type in the request header. Let's send the content type as JSON.

```
curl -X POST \
-H 'Content-Type: application/json' \
http://localhost:5000/add -d '{ "data" : "hello" }'
```

Now we see that we read the data as `{'data': 'hello'}` </br>
This allows us to read customer data as JSON, validate it, and write it to storage. </br>

Let's `POST` a customer:

```
curl -X POST -v \
-H 'Content-Type: application/json' \
http://localhost:5000/add -d '
{
  "customerID": "i",
  "firstName": "Bob",
  "lastName": "Smith"
}'
```
Note if we break the `json` format, we get a `400` status code </br>

`400` = Bad Request </br>

400 indicates a bad request, letting the client know they have to send the right formatted data.

## Docker

Let's build our container image and run it while mounting our customer file

Our final `dockerfile`
```
FROM python:3.9.6-alpine3.13 as dev

WORKDIR /work

FROM dev as runtime
WORKDIR /app
COPY ./src/requirements.txt /app/
RUN pip install -r /app/requirements.txt

COPY ./src/app.py /app/app.py
ENV FLASK_APP=app.py

CMD flask run -h 0.0.0 -p 5000

```

Build our container.

```
cd python\introduction\part-4.http

docker build . -t customer-app

```

Notice that we need to mount our `customers.json` file into the container. </br>
But also notice the containers working directory is `/app` and in the `app.py`
we load `customers.json` from the working directory which is the same directory as the app. </br>
When mounting files to a container, docker effectively formats that path, so to mount our json file, we would lose our `app.py` because of the format. <br/>

Therefore it's always important to mount configs, secrets or data to seperate folders.

Let's change that to `/data`

```
#set a global variable 
dataPath = "/data/customers.json"
```
Now we can change all references to `customers.json` to our variable.
And then mount that location:

```
docker build . -t customer-app
docker run -it -p 5000:5000 -v ${PWD}:/data customer-app
```

---

### Overview (`python/introduction/part-5.database.redis/README.md`)

# Introduction to Python: Storing data in Redis Database

So far, we've learnt Python fundamentals, worked with data, files , HTTP and more importantly, basic data structures like `csv` and `json`.

Be sure to checkout: </br>
[Part 1: Intro to Python](../README.md) </br>
[Part 2: Files](../part-2.files/README.md) </br>
[Part 3: JSON](../part-3.json/README.md) </br>

## Start up a Redis Cluster

Follow my Redis clustering Tutorial </br>

<a href="https://youtube.com/playlist?list=PLHq1uqvAteVtlgFkmOlIqWro3XP26y_oW" title="Redis"><img src="https://i.ytimg.com/vi/L3zp347cWNw/hqdefault.jpg" width="30%" alt="Redis Guide" /></a>

Code is over [here](../../../storage/redis/clustering/readme.md)

## Python Dev Environment

The same as Part 1+2+3+4, we start with a [dockerfile](./dockerfile) where we declare our version of `python`.

```
FROM python:3.9.6-alpine3.13 as dev

WORKDIR /work
```

Let's build and start our container: 

```
cd python\introduction\part-5.database.redis

docker build --target dev . -t python
docker run -it -v ${PWD}:/work -p 5000:5000 --net redis python sh

/work # python --version
Python 3.9.6

```

## Our application

We're going to use what we've learnt in part 1,2 & 3 and create
our customer app that handles customer data </br>
Firstly we have to import our dependencies:

```
import os.path
import csv
import json
from flask import Flask
from flask import request

```

Then we have a class to define what a customer looks like:
```
class Customer:
  def __init__(self, c="",f="",l=""):
    self.customerID = c
    self.firstName  = f
    self.lastName   = l
  def fullName(self):
    return self.firstName + " " + self.lastName
```

And also set a global variable for the location of our videos `json` file:

```
dataPath = "./customers.json"
```

Then we need a function which returns our customers:
```
def getCustomers():
  if os.path.isfile(dataPath):
    with open(dataPath, newline='') as customerFile:
      data = customerFile.read()
      customers = json.loads(data)
      return customers
  else: 
    return {}
```

Here is a function to return a specific customer:
```
def getCustomer(customerID):
  customers = getCustomers()

  if customerID in customers:
    return customers[customerID]
  else:
    return {}
```
And finally a function for updating our customers:

```
def updateCustomers(customers):
  with open(dataPath, 'w', newline='') as customerFile:
    customerJSON = json.dumps(customers)
    customerFile.write(customerJSON)
```

In the previous episode, we've created a `json` file to hold all our customers. </br>
We've learnt how to read and write to file and temporarily use the file for our storage. </br>

Let's create a file called `customers.json` : 
```
{
  "a": {
    "customerID": "a",
    "firstName": "James",
    "lastName": "Baker"
  },
  "b": {
    "customerID": "b",
    "firstName": "Jonathan",
    "lastName": "D"
  },
  "c": {
    "customerID": "c",
    "firstName": "Aleem",
    "lastName": "Janmohamed"
  },
  "d": {
    "customerID": "d",
    "firstName": "Ivo",
    "lastName": "Galic"
  },
  "e": {
    "customerID": "e",
    "firstName": "Joel",
    "lastName": "Griffiths"
  },
  "f": {
    "customerID": "f",
    "firstName": "Michael",
    "lastName": "Spinks"
  },
  "g": {
    "customerID": "g",
    "firstName": "Victor",
    "lastName": "Savkov"
  }
}
```

Now that we have our customer data and functions to read and update our customer data, let's define our `Flask` application:


```
app = Flask(__name__)
```

We create our route to get all customers:

```
@app.route("/", methods=['GET'])
def get_customers():
    customers = getCustomers()
    return json.dumps(customers)
```

A route to get one customer by ID:

```
@app.route("/get/<string:customerID>", methods=['GET'])
def get_customer(customerID):
    customer = getCustomer(customerID)

    if customer == {}:
      return {}, 404
    else:
      return customer
```

And finally a route to update or add customers called `/set` :

```
@app.route("/set", methods=['POST'])
def add_customer():
    jsonData = request.json

    if "customerID" not in jsonData:
      return "customerID required", 400
    if "firstName" not in jsonData:
      return "firstName required", 400
    if "lastName" not in jsonData:
      return "lastName required", 400
    
    customers = getCustomers()
    customers[jsonData["customerID"]] = Customer( jsonData["customerID"], jsonData["firstName"], jsonData["lastName"]).__dict__
    updateCustomers(customers)
    return "success", 200
```

Before we can be done, we need to import our `Flask` dependency we covered in our Python HTTP video. </br>
Let's create a `requirements.txt` file:

```
Flask == 2.0.2
```

We can install our dependencies using:

```
pip install -r requirements.txt
```

This gives us a web application that handles customer data and using a file as it's storage </br>
To test it, we can start up Flask:

```
export FLASK_APP=src/app
flask run -h 0.0.0 -p 5000
```

Now we can confirm it's working by accessing our application in the browser on `http://localhost:5000`

## Redis

To connect to Redis, we'll use a popular library called `redis-py` which we can grab from [here](https://github.com/redis/redis-py) </br>
The pip install is over [here](https://pypi.org/project/redis/3.5.3/) </br>

Let's add that to our `requirements.txt` dependency files.

```
redis == 3.5.3
```

We can proceed to install it using `pip install`

```
pip install -r requirements.txt
```

Now to connect to Redis in a highly available manner, we need to take a look at the 
`Sentinel support` section of the guide </br>

Let's test the library. The beauty of Python is that it's a scripting language, so we don't have to compile and keep restarting our application, we can test each line of code. </br>

```
python
from redis.sentinel import Sentinel

sentinel = Sentinel([('sentinel-0', 5000),('sentinel-1', 5000),('sentinel-2', 5000)], socket_timeout=0.1)

sentinel.discover_master('mymaster')

sentinel.discover_slaves('mymaster')

master = sentinel.master_for('mymaster',password = "a-very-complex-password-here", socket_timeout=0.1)

slave = sentinel.slave_for('mymaster',password = "a-very-complex-password-here", socket_timeout=0.1)

master.set('foo', 'bar')
slave.get('foo')
```

We can demonstrate reading and writing a key value pair. </br>
We can also demonstrate failure, when we stop the current master, we'll get a connection error. It's important to implement retry logic. </br>
If we wait a moment and execute commands again, we will see that it starts to work.


```
# stop current master
docker rm -f redis-0

master.set('foo', 'bar2')

redis.exceptions.ConnectionError: Connection closed by server.

# retry moments later...

master.set('foo', 'bar2')
slave.get('foo')

sentinel.discover_master('mymaster')
sentinel.discover_slaves('mymaster')
```

We can find the current master by running `docker inspect` to see who owns that IP address.

Start up `redis-0` again, to simulate a recovery from failure.

## Connecting our App to Redis

To connect to redis, we'll want to read the connection info from environment variables. Let's set some global variables.

```
import os

redis_sentinels = os.environ.get('REDIS_SENTINELS')
redis_master_name = os.environ.get('REDIS_MASTER_NAME')
redis_password = os.environ.get('REDIS_PASSWORD')

```

We will need to restart our container so we can inject these environment variables. Let's go ahead and do that:

```
docker run -it -p 5000:5000 `
  --net redis `
  -v ${PWD}:/work `
  -e REDIS_SENTINELS="sentinel-0:5000,sentinel-1:5000,sentinel-2:5000" `
  -e REDIS_MASTER_NAME="mymaster" `
  -e REDIS_PASSWORD="a-very-complex-password-here" `
  python sh

# re-install our dependencies
pip install -r requirements.txt
```

Now we can setup a client:

```
from redis.sentinel import Sentinel

sentinels = []

for s in redis_sentinels.split(","):
  sentinels.append((s.split(":")[0], s.split(":")[1]))

redis_sentinel = Sentinel(sentinels, socket_timeout=5)
redis_master = redis_sentinel.master_for(redis_master_name,password = redis_password, socket_timeout=5)
```

## Retry logic

Now we noticed that if we have a master that fails, the sentinels will choose and assign a new master. We can see this by simply retrying our redis command. </br>

When talking to redis we need to have some retry capability to be able to recover from this scenario. </br>

Let's build a retry function at the top of our application, that runs a redis command:

```
def redis_command(command, *args):
  max_retries = 3
  count = 0
  backoffSeconds = 5
  while True:
    try:
      return command(*args)
    except (redis.exceptions.ConnectionError, redis.exceptions.TimeoutError):
      count += 1
      if count > max_retries:
        raise
    print('Retrying in {} seconds'.format(backoffSeconds))
    time.sleep(backoffSeconds)
```

We can test out our `redis_command` by calling it and printing the result 
to the screen

```
print(redis_command(redis_master.set, 'foo', 'bar'))
print(redis_command(redis_master.get, 'foo'))
```

We can simulate failure again, by finding and stopping the current master.

Once we're done with our tests, we can `exec` into the current master and run `FLUSHALL` to remove our test records from redis.

## Saving our data to Redis

Now let's change our customer functions to point to Redis instead of file </br>

Starting with `getCustomer` to retrieve a single customer
```
def getCustomer(customerID):
  customer = redis_command(redis_master.get, customerID)
  
  if customer == None:
    return {}
  else:
    c = str(customer, 'utf-8')
    return json.loads(c)
```
Now we can use that to return all our customers by updating the `getCustomers` function:

```
def getCustomers():
  customers = {}
  customerIDs = redis_command(redis_master.scan_iter, "*")
  for customerID in customerIDs:
    customer = getCustomer(customerID)
    customers[customer["customerID"]] = customer
  
  return customers
```

Let's improve our functions by adding a new function to update a single customer:

```
def updateCustomer(customer):
  redis_command(redis_master.set, customer.customerID, json.dumps(customer.__dict__))

```

And finally we can use that function to update all customers by tweaking our `updateCustomers` function:

```
def updateCustomers(customers):
  for customer in customers:
    updateCustomer(customer)
```

Now our simple functions are done, let's hook them up to our endpoints

```
# firstly delete these test lines
print(redis_command(redis_master.set, 'foo', 'bar'))
print(redis_command(redis_master.get, 'foo'))
```

Our simple Get all 

```
@app.route("/", methods=['GET'])
def get_customers():
  customers = getCustomers()
  return json.dumps(customers)
```

Our Get by ID

```
@app.route("/get/<string:customerID>", methods=['GET'])
def get_customer(customerID):
    customer = getCustomer(customerID)

    if customer == {}:
      return {}, 404
    else:
      return customer
```

And our update endpoint to update a customer

```
@app.route("/set", methods=['POST'])
def add_customer():
    jsonData = request.json

    if "customerID" not in jsonData:
      return "customerID required", 400
    if "firstName" not in jsonData:
      return "firstName required", 400
    if "lastName" not in jsonData:
      return "lastName required", 400
    
    customer = Customer( jsonData["customerID"], jsonData["firstName"], jsonData["lastName"])
    updateCustomer(customer)
    return "success", 200
```
## Docker

Let's build our container image and run it while mounting our customer file

Our final `dockerfile`
```
FROM python:3.9.6-alpine3.13 as dev

WORKDIR /work

FROM dev as runtime
WORKDIR /app

COPY ./requirements.txt /app/
RUN pip install -r /app/requirements.txt

COPY ./src/app.py /app/app.py
ENV FLASK_APP=app.py

CMD flask run -h 0.0.0 -p 5000

```

Build our container.

```
cd python\introduction\part-5.database.redis

docker build . -t customer-app

```

Now we can run our production container:

```
docker build . -t customer-app

docker run -it -p 5000:5000 `
  --net redis `
  -e REDIS_SENTINELS="sentinel-0:5000,sentinel-1:5000,sentinel-2:5000" `
  -e REDIS_MASTER_NAME="mymaster" `
  -e REDIS_PASSWORD="a-very-complex-password-here" `
  customer-app
```

---

### Overview (`security/letsencrypt/introduction/readme.md`)

# Let's encrypt

## Introduction
To start off, I run an NGINX web server. <br/>
This could be running anywhere in the cloud. <br/>

```
docker run -it -p 80:80 nginx bash

# get my public IP for this server 
curl ifconfig.co

# lets get out of the container
exit

```

Now that we have the public IP for our server, lets start it up again <br/>
This time, without bash <br/>
We should be able to access it in the browser <br/>

```
docker run -it -p 80:80 nginx
```

In the video, we create a DNS record and point it to the IP of our server <br/>

## Certbot

The [docs](https://certbot.eff.org/)

To build certbot, i simply change directory and build my certbot container <br/>

```
cd .\security\letsencrypt\introduction\

docker build . -t certbot

docker run -it --rm --name certbot `
-v ${PWD}:/letsencrypt `
-v ${PWD}/certs:/etc/letsencrypt `
certbot bash

```

## NGINX 

We've customised our `nginx.conf` as shown in the video <br/>

Run this NGINX, we mount the shared folder that certbot will use:

```
cd .\security\letsencrypt\introduction\

docker run -it --rm --name nginx `
-v ${PWD}/nginx.conf:/etc/nginx/nginx.conf `
-v ${PWD}:/letsencrypt `
-v ${PWD}/certs:/etc/letsencrypt `
-p 80:80 `
-p 443:443 `
nginx

```

## Issue certificate

In certbot, generate our cert:

```
certbot certonly --webroot

# webroot is the folder we mounted: /letsencrypt

# certificate outputs under etc/letsencrypt/live/**
# since we share this volume with our webserver, we dont need to copy
# certificates across.

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/marcel.guru/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/marcel.guru/privkey.pem
   Your cert will expire on 2020-12-03. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot
   again. To non-interactively renew *all* of your certificates, run
   "certbot renew"
 - Your account credentials have been saved in your Certbot
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Certbot so
   making regular backups of this folder is ideal.
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

```

## Renewal

To do a dry run of cert renewal:

```
certbot renew --dry-run
```

Reload our NGINX web server if the certs change:

```
docker exec -it nginx sh -c "nginx -s reload"
```

Checkout the Certbot [docs](https://certbot.eff.org/instructions) for more details

---

### Overview (`storage/redis/clustering/readme.md`)

## Replication

<a href="https://youtu.be/GEg7s3i6Jak" title="redis-cluster"><img src="https://i.ytimg.com/vi/GEg7s3i6Jak/hqdefault.jpg" width="20%" alt="redis-cluster" /></a> 

Documentation [here](https://redis.io/topics/replication)

### Configuration

```
#persistence
dir /data
dbfilename dump.rdb
appendonly yes
appendfilename "appendonly.aof"

```
### redis-0 Configuration

```
protected-mode no
port 6379

#authentication
masterauth a-very-complex-password-here
requirepass a-very-complex-password-here
```
### redis-1 Configuration

```
protected-mode no
port 6379
slaveof redis-0 6379

#authentication
masterauth a-very-complex-password-here
requirepass a-very-complex-password-here

```
### redis-2 Configuration

```
protected-mode no
port 6379
slaveof redis-0 6379

#authentication
masterauth a-very-complex-password-here
requirepass a-very-complex-password-here

```

```

# remember to update above in configs!

docker network create redis

cd .\storage\redis\clustering\

#redis-0
docker run -d --rm --name redis-0 `
    --net redis `
    -v ${PWD}/redis-0:/etc/redis/ `
    redis:6.0-alpine redis-server /etc/redis/redis.conf

#redis-1
docker run -d --rm --name redis-1 `
    --net redis `
    -v ${PWD}/redis-1:/etc/redis/ `
    redis:6.0-alpine redis-server /etc/redis/redis.conf


#redis-2
docker run -d --rm --name redis-2 `
    --net redis `
    -v ${PWD}/redis-2:/etc/redis/ `
    redis:6.0-alpine redis-server /etc/redis/redis.conf

```

## Example Application

Run example application in video, to show application writing to the master

```
cd .\storage\redis\applications\client\
docker build . -t aimvector/redis-client:v1.0.0

docker run -it --net redis `
-e REDIS_HOST=redis-0 `
-e REDIS_PORT=6379 `
-e REDIS_PASSWORD="a-very-complex-password-here" `
-p 80:80 `
aimvector/redis-client:v1.0.0

```

## Test Replication

Technically written data should now be on the replicas

```
# go to one of the clients
docker exec -it redis-2 sh
redis-cli
auth "a-very-complex-password-here"
keys *

```

## Running Sentinels

Documentation [here](https://redis.io/topics/sentinel)

```
#********BASIC CONFIG************************************
port 5000
sentinel monitor mymaster redis-0 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
sentinel auth-pass mymaster a-very-complex-password-here
#********************************************

```
Starting Redis in sentinel mode

```
cd .\storage\redis\clustering\

docker run -d --rm --name sentinel-0 --net redis `
    -v ${PWD}/sentinel-0:/etc/redis/ `
    redis:6.0-alpine `
    redis-sentinel /etc/redis/sentinel.conf

docker run -d --rm --name sentinel-1 --net redis `
    -v ${PWD}/sentinel-1:/etc/redis/ `
    redis:6.0-alpine `
    redis-sentinel /etc/redis/sentinel.conf

docker run -d --rm --name sentinel-2 --net redis `
    -v ${PWD}/sentinel-2:/etc/redis/ `
    redis:6.0-alpine `
    redis-sentinel /etc/redis/sentinel.conf


docker logs sentinel-0
docker exec -it sentinel-0 sh
redis-cli -p 5000
info
sentinel master mymaster

# clean up 

docker rm -f redis-0 redis-1 redis-2
docker rm -f sentinel-0 sentinel-1 sentinel-2


```

---

--- METRICS ---
- Files Extracted: 99
- Estimated Token Budget: ~125719 tokens
- Canonical Reference: https://codewiki.google/github.com/marcel-dempers/docker-development-youtube-series
