diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..451221d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,27 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{yaml,yml}] +indent_style = space +indent_size = 2 + +[*.md] +indent_style = space +indent_size = 2 +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab + +[*.go] +indent_style = tab +indent_size = 4 + +[*.sh] +indent_style = space +indent_size = 2 diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..5439a26 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,89 @@ +# Contributing to Cloud Native Lab + +Thank you for your interest in contributing to this repository. +This guide explains the conventions, standards and workflow for contributions. + +## Naming Conventions + +- Use **lowercase kebab-case** for all directory and file names +- Examples: `my-lab/`, `config-map.yaml`, `validate.sh` +- Do not use spaces, underscores, or camelCase in file or directory names + +## Lab Structure + +Every lab must contain at minimum: + +```text +lab-name/ +├── README.md # Objective, instructions, validation, cleanup +├── namespace.yaml # Kubernetes namespace for the exercise +├── resources.yaml # Kubernetes resources (or split into individual files) +├── validate.sh # Validation commands +└── cleanup.sh # Cleanup commands +``` + +Refer to the [lab README template](../docs/templates/lab-readme-template.md) +for the expected structure. + +## Validation + +Before submitting a pull request: + +1. Verify Kubernetes YAML indentation is correct +2. Verify that selector labels match Pod template labels +3. Verify that Services target the correct ports +4. Run `bash validate.sh` from within the lab directory +5. Run `bash scripts/validate-manifests.sh` from the repository root +6. Run `make lint` to check Markdown, YAML and shell scripts + +## Documentation Expectations + +- Every lab must have a complete `README.md` +- Every top-level directory must have a `README.md` +- Markdown must pass `markdownlint` with the `.markdownlint.json` configuration +- Use clear, professional English +- Explain _why_, not just _what_ + +## Safe Scripting + +- All Bash scripts must begin with `#!/usr/bin/env bash` +- Use `set -euo pipefail` in all scripts +- Scripts must not silently delete unrelated resources +- Cleanup scripts must only delete resources created by the specific lab +- Validate that required tools are present before using them + +## No Secrets + +- Never commit secrets, credentials, API keys, passwords, or tokens +- Use obviously fake placeholder values such as `REPLACE_ME` or `` +- Kubernetes Secret manifests must use placeholder values only + +## Small Pull Requests + +- Keep pull requests focused on a single lab or feature +- Do not mix unrelated changes in the same pull request +- Add a clear description explaining the purpose of the changes + +## Proposing a New Lab + +To propose a new lab: + +1. Open an issue using the **Lab Proposal** template +2. Describe the objective, target audience, and prerequisites +3. Wait for feedback before implementing +4. Follow the lab structure requirements above +5. Submit a pull request referencing the issue + +## Commit Messages + +Use clear, imperative commit messages: + +```text +Add multi-container Pod lab for CKAD application design +Fix namespace mismatch in rolling-update lab +Update kubectl cheatsheet with rollout commands +``` + +## Code of Conduct + +All contributors must adhere to the [Code of Conduct](../CODE_OF_CONDUCT.md). diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 0000000..37a3917 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,41 @@ +--- +name: Bug Report +about: Report a problem with a lab, script, or manifest +title: "[BUG] " +labels: bug +assignees: "" +--- + +## Description + +A clear description of the problem. + +## Affected Area + +Specify the directory, file, or lab where the issue occurs. + +## Steps to Reproduce + +1. +2. +3. + +## Expected Behaviour + +What should happen. + +## Actual Behaviour + +What actually happens. + +## Environment + +- Operating System: +- Docker version: +- kubectl version: +- Kind version: +- Other relevant tools and versions: + +## Additional Context + +Add any other context, error messages, or screenshots here. diff --git a/.github/ISSUE_TEMPLATE/lab-proposal.md b/.github/ISSUE_TEMPLATE/lab-proposal.md new file mode 100644 index 0000000..07bec10 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/lab-proposal.md @@ -0,0 +1,55 @@ +--- +name: Lab Proposal +about: Propose a new lab or exercise +title: "[LAB] " +labels: enhancement +assignees: "" +--- + +## Lab Title + +A concise name for the proposed lab. + +## Objective + +What will the learner achieve by completing this lab? + +## Target Area + +Which directory or certification domain does this lab belong to? + +- [ ] CKAD +- [ ] CKA +- [ ] CKS +- [ ] Kubernetes general +- [ ] Go +- [ ] Observability +- [ ] GitOps +- [ ] Security +- [ ] Networking +- [ ] Linux +- [ ] Containers +- [ ] Other + +## Prerequisites + +What tools, knowledge, or setup is required before starting this lab? + +## Proposed Structure + +```text +lab-name/ +├── README.md +├── namespace.yaml +├── resources.yaml +├── validate.sh +└── cleanup.sh +``` + +## Learning Outcomes + +What will learners understand or be able to do after completing this lab? + +## Notes + +Any additional context, references, or considerations. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..d6ddd5f --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,45 @@ +## Description + +Briefly describe the changes in this pull request. + +## Type of Change + +- [ ] New lab or exercise +- [ ] Bug fix or correction +- [ ] Documentation update +- [ ] Script or automation improvement +- [ ] Refactoring (no behaviour change) +- [ ] Other (describe below) + +## Lab or Area Affected + +Specify the directory or area affected by this change. + +## Checklist + +- [ ] Directory and file names use lowercase kebab-case +- [ ] README.md is complete and follows the lab template +- [ ] Kubernetes YAML passes manifest validation (`make validate`) +- [ ] Shell scripts pass ShellCheck (`make lint-shell`) +- [ ] Markdown passes markdownlint (`make lint-markdown`) +- [ ] YAML passes yamllint (`make lint-yaml`) +- [ ] No secrets, credentials or real values committed +- [ ] Cleanup script only removes resources created by this lab +- [ ] Validation script works correctly +- [ ] Links in README files are correct + +## Testing + +Describe how you tested this change. Include any commands run. + +```bash +# Example +kubectl apply -f namespace.yaml +kubectl apply -f resources.yaml +bash validate.sh +bash cleanup.sh +``` + +## Related Issues + +Closes # diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml new file mode 100644 index 0000000..ff52b14 --- /dev/null +++ b/.github/workflows/go-ci.yml @@ -0,0 +1,64 @@ +--- +name: Go CI + +on: + push: + paths: + - "**/*.go" + - "**/go.mod" + - "**/go.sum" + pull_request: + paths: + - "**/*.go" + - "**/go.mod" + - "**/go.sum" + +permissions: + contents: read + +jobs: + go-ci: + name: Go CI + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go-labs/kubernetes-client/list-pods/go.mod + cache: true + + - name: Find Go modules + id: find-modules + run: | + modules=$(find . -name 'go.mod' -not -path '*/vendor/*' | xargs -I{} dirname {}) + echo "modules=${modules}" >> "$GITHUB_OUTPUT" + + - name: Check formatting + run: | + find . -name '*.go' -not -path '*/vendor/*' | while read -r file; do + if ! gofmt -l "$file" | grep -q .; then + : + else + echo "File is not formatted: $file" + exit 1 + fi + done + + - name: Run go vet + run: | + find . -name 'go.mod' -not -path '*/vendor/*' | while read -r modfile; do + dir=$(dirname "$modfile") + echo "Running go vet in ${dir}..." + (cd "$dir" && go vet ./...) + done + + - name: Run tests + run: | + find . -name 'go.mod' -not -path '*/vendor/*' | while read -r modfile; do + dir=$(dirname "$modfile") + echo "Running tests in ${dir}..." + (cd "$dir" && go test ./...) + done diff --git a/.github/workflows/kubernetes-validation.yml b/.github/workflows/kubernetes-validation.yml new file mode 100644 index 0000000..7ca3d12 --- /dev/null +++ b/.github/workflows/kubernetes-validation.yml @@ -0,0 +1,42 @@ +--- +name: Kubernetes Manifest Validation + +on: + push: + paths: + - "kubernetes-labs/**/*.yaml" + - "kubernetes-labs/**/*.yml" + - "certifications/**/*.yaml" + - "certifications/**/*.yml" + pull_request: + paths: + - "kubernetes-labs/**/*.yaml" + - "kubernetes-labs/**/*.yml" + - "certifications/**/*.yaml" + - "certifications/**/*.yml" + +permissions: + contents: read + +jobs: + validate: + name: Validate Kubernetes Manifests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install kubeconform + run: | + curl -sSL https://github.com/yannh/kubeconform/releases/latest/download/kubeconform-linux-amd64.tar.gz \ + | tar -xz -C /usr/local/bin + + - name: Validate manifests + run: | + find kubernetes-labs certifications -name '*.yaml' -o -name '*.yml' \ + | grep -v '.github' \ + | xargs kubeconform \ + -kubernetes-version 1.30.0 \ + -strict \ + -summary \ + -ignore-missing-schemas diff --git a/.github/workflows/markdown-lint.yml b/.github/workflows/markdown-lint.yml new file mode 100644 index 0000000..47e3c76 --- /dev/null +++ b/.github/workflows/markdown-lint.yml @@ -0,0 +1,32 @@ +--- +name: Markdown Lint + +on: + push: + paths: + - "**/*.md" + pull_request: + paths: + - "**/*.md" + +permissions: + contents: read + +jobs: + markdown-lint: + name: Lint Markdown + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Install markdownlint-cli + run: npm install -g markdownlint-cli + + - name: Run markdownlint + run: markdownlint --config .markdownlint.json '**/*.md' diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml new file mode 100644 index 0000000..55f849f --- /dev/null +++ b/.github/workflows/shellcheck.yml @@ -0,0 +1,27 @@ +--- +name: ShellCheck + +on: + push: + paths: + - "**/*.sh" + pull_request: + paths: + - "**/*.sh" + +permissions: + contents: read + +jobs: + shellcheck: + name: ShellCheck + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Run ShellCheck + uses: ludeeus/action-shellcheck@master + with: + check_together: "yes" + scandir: "./scripts" diff --git a/.github/workflows/yaml-lint.yml b/.github/workflows/yaml-lint.yml new file mode 100644 index 0000000..b882d8a --- /dev/null +++ b/.github/workflows/yaml-lint.yml @@ -0,0 +1,36 @@ +--- +name: YAML Lint + +on: + push: + paths: + - "**/*.yaml" + - "**/*.yml" + - ".yamllint.yml" + pull_request: + paths: + - "**/*.yaml" + - "**/*.yml" + - ".yamllint.yml" + +permissions: + contents: read + +jobs: + yaml-lint: + name: Lint YAML + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install yamllint + run: pip install yamllint + + - name: Run yamllint + run: yamllint -c .yamllint.yml . diff --git a/.gitignore b/.gitignore index aaadf73..5c18618 100644 --- a/.gitignore +++ b/.gitignore @@ -1,32 +1,84 @@ -# If you prefer the allow list template instead of the deny list, see community template: -# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore -# -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib +# macOS +.DS_Store +.AppleDouble +.LSOverride +._* +.Spotlight-V8 +.Trashes -# Test binary, built with `go test -c` -*.test +# Linux +*~ +.fuse_hidden* +.directory +.Trash-* +.nfs* -# Code coverage profiles and other test artifacts -*.out -coverage.* -*.coverprofile -profile.cov +# Windows +Thumbs.db +ehthumbs.db +Desktop.ini +$RECYCLE.BIN/ + +# VS Code +.vscode/ +*.code-workspace -# Dependency directories (remove the comment below to include it) -# vendor/ +# IntelliJ / JetBrains +.idea/ +*.iml +*.iws +*.ipr +out/ -# Go workspace file -go.work -go.work.sum +# Go +*.exe +*.test +*.out +vendor/ +coverage.txt +coverage.html +dist/ +bin/ -# env file +# Environment files .env +.env.* +!.env.example +*.local + +# Kubernetes temporary outputs +*.kubeconfig +kubeconfig +.kubeconfig + +# Terraform (if introduced later) +.terraform/ +*.tfstate +*.tfstate.backup +*.tfplan +.terraformrc +terraform.rc + +# Helm +*.tgz + +# Local cluster artefacts +clusters/local/ +kind-kubeconfig +k3d-kubeconfig +minikube-kubeconfig +.kube/ + +# Logs +*.log +logs/ + +# Temporary files +tmp/ +temp/ +.tmp/ -# Editor/IDE -# .idea/ -# .vscode/ +# Generated / build artefacts +_output/ +build/ +release/ diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..6092ee1 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,10 @@ +{ + "default": true, + "MD013": { + "line_length": 120, + "tables": false, + "code_blocks": false + }, + "MD033": false, + "MD041": false +} diff --git a/.shellcheckrc b/.shellcheckrc new file mode 100644 index 0000000..cc7ec66 --- /dev/null +++ b/.shellcheckrc @@ -0,0 +1,4 @@ +# ShellCheck configuration +shell=bash +enable=all +disable=SC2148 diff --git a/.yamllint.yml b/.yamllint.yml new file mode 100644 index 0000000..c45199a --- /dev/null +++ b/.yamllint.yml @@ -0,0 +1,24 @@ +--- +extends: default + +rules: + line-length: + max: 120 + level: warning + document-start: + present: true + truthy: + allowed-values: ["true", "false"] + check-keys: false + comments: + min-spaces-from-content: 1 + braces: + max-spaces-inside: 1 + brackets: + max-spaces-inside: 1 + +ignore: | + .github/workflows/ + node_modules/ + vendor/ + kubernetes-labs/helm/charts/ diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..0ed0325 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,45 @@ +# Code of Conduct + +## Our Pledge + +We as contributors and maintainers pledge to make participation in this +project a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behaviour that contributes to creating a positive environment include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behaviour include: + +- The use of sexualised language or imagery and unwelcome sexual attention or advances +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behaviour and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behaviour. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behaviour may be +reported by opening a GitHub issue or contacting the maintainers directly. +All complaints will be reviewed and investigated and will result in a response +that is deemed necessary and appropriate to the circumstances. + +## Attribution + +This Code of Conduct is adapted from the +[Contributor Covenant](https://www.contributor-covenant.org), version 2.1. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7e16fa6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,203 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +Copyright 2026 ferreiraad + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..797d2e0 --- /dev/null +++ b/Makefile @@ -0,0 +1,76 @@ +# Cloud Native Lab — Root Makefile +# Provides common targets for managing labs, clusters, linting and validation. +# Run `make help` to see all available targets. + +.DEFAULT_GOAL := help + +CLUSTER_NAME ?= cloud-native-lab +KIND_CONFIG := kubernetes-labs/clusters/kind/kind-config.yaml + +.PHONY: help +help: ## Show this help message + @echo "Cloud Native Lab" + @echo "" + @echo "Usage: make " + @echo "" + @awk 'BEGIN {FS = ":.*##"; printf "%-22s %s\n", "Target", "Description"} \ + /^[a-zA-Z_-]+:.*?##/ { printf " %-20s %s\n", $$1, $$2 }' $(MAKEFILE_LIST) + +.PHONY: check +check: ## Check that required tools are installed + @bash scripts/check-prerequisites.sh + +.PHONY: cluster-create +cluster-create: ## Create a local Kind cluster named $(CLUSTER_NAME) + @CLUSTER_NAME=$(CLUSTER_NAME) bash scripts/create-kind-cluster.sh + +.PHONY: cluster-delete +cluster-delete: ## Delete the local Kind cluster named $(CLUSTER_NAME) + @CLUSTER_NAME=$(CLUSTER_NAME) bash scripts/delete-kind-cluster.sh + +.PHONY: validate +validate: ## Validate Kubernetes manifests + @bash scripts/validate-manifests.sh + +.PHONY: lint +lint: lint-markdown lint-yaml lint-shell ## Run all linters + +.PHONY: lint-markdown +lint-markdown: ## Lint Markdown files + @echo "Linting Markdown files..." + @if command -v markdownlint >/dev/null 2>&1; then \ + markdownlint --config .markdownlint.json '**/*.md'; \ + else \ + echo " markdownlint not installed, skipping (npm install -g markdownlint-cli)"; \ + fi + +.PHONY: lint-yaml +lint-yaml: ## Lint YAML files + @echo "Linting YAML files..." + @if command -v yamllint >/dev/null 2>&1; then \ + yamllint -c .yamllint.yml .; \ + else \ + echo " yamllint not installed, skipping (pip install yamllint)"; \ + fi + +.PHONY: lint-shell +lint-shell: ## Lint Bash scripts with ShellCheck + @echo "Linting shell scripts..." + @if command -v shellcheck >/dev/null 2>&1; then \ + find scripts -name '*.sh' -exec shellcheck {} +; \ + else \ + echo " shellcheck not installed, skipping"; \ + fi + +.PHONY: test +test: ## Run available tests + @echo "Running manifest validation tests..." + @bash scripts/validate-manifests.sh + @echo "Tests complete." + +.PHONY: clean +clean: ## Remove generated/temporary local files (does NOT delete cluster or namespaces) + @echo "Cleaning local temporary files..." + @find . -name '*.log' -not -path './.git/*' -delete 2>/dev/null || true + @find . -name 'coverage.txt' -not -path './.git/*' -delete 2>/dev/null || true + @echo "Done." diff --git a/README.md b/README.md index 5d5cfe9..0606aa5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,257 @@ -# cloud-native-lab -Hands-on cloud-native labs for Kubernetes, Linux, containers, Go, observability, GitOps, security, CNCF technologies, and CKAD/CKA certification preparation. +# Cloud Native Lab + +A hands-on learning and experimentation repository for cloud-native +technologies, Linux Foundation and CNCF-related training, Kubernetes +certification preparation, automation, observability, security and Go +development. + +> **Repository description:** +> Hands-on cloud-native labs for Kubernetes, Linux, containers, Go, observability, +> GitOps, security, CNCF technologies, and CKAD/CKA certification preparation. + +--- + +## Overview + +This repository is a long-term, practical workspace for studying and +demonstrating cloud-native engineering skills. It provides executable exercises, +Kubernetes manifests, Bash scripts, Go examples, troubleshooting scenarios, +and documentation covering a wide range of cloud-native topics. + +The repository is structured to support personal study and to serve as a +public technical portfolio. It is designed to grow gradually and remain +lightweight, with each lab being small, focused, and reproducible. + +--- + +## Objectives + +- Prepare for the **Certified Kubernetes Application Developer (CKAD)** exam +- Prepare later for **CKA** (Certified Kubernetes Administrator) and **CKS** (Certified Kubernetes Security Specialist) +- Practise Kubernetes application development and administration +- Build Custom Resource Definitions (CRDs) +- Develop controllers and Operators +- Improve Linux and networking knowledge +- Experiment with containers and container runtimes +- Explore observability (Prometheus, Grafana, OpenTelemetry) +- Explore GitOps (Argo CD, Flux) +- Practise cloud-native security +- Develop Kubernetes-related tools with Go +- Document troubleshooting scenarios and lessons learned + +--- + +## Repository Structure + +```text +cloud-native-lab/ +├── certifications/ # Certification preparation (CKAD, CKA, CKS) +├── kubernetes-labs/ # General Kubernetes exercises +├── go-labs/ # Go code for Kubernetes tooling +├── linux-labs/ # Linux administration exercises +├── container-labs/ # Container and runtime exercises +├── observability-labs/ # Prometheus, Grafana, OpenTelemetry +├── gitops-labs/ # GitOps with Argo CD and Flux +├── security-labs/ # Security practices and tools +├── networking-labs/ # Networking concepts and tools +├── cncf-labs/ # CNCF project explorations +├── automation/ # Scripts, Makefiles, GitHub Actions +├── docs/ # Cheatsheets, templates, notes +├── examples/ # Sample applications +├── scripts/ # Utility scripts +└── tests/ # Manifest and integration tests +``` + +--- + +## Current Focus + +**Active track: CKAD (Certified Kubernetes Application Developer)** + +```text +- [x] Repository foundation +- [ ] CKAD application design and build +- [ ] CKAD application deployment +- [ ] CKAD observability and maintenance +- [ ] CKAD configuration and security +- [ ] CKAD services and networking +- [ ] CKAD mock exams +- [ ] CKA preparation +- [ ] CKS preparation +- [ ] Kubernetes Operators with Go +``` + +--- + +## Certification Tracks + +| Track | Status | Directory | +|-------|-----------|--------------------------------------| +| CKAD | In Progress | [certifications/ckad/](certifications/ckad/) | +| CKA | Planned | [certifications/cka/](certifications/cka/) | +| CKS | Planned | [certifications/cks/](certifications/cks/) | + +--- + +## Technologies + +| Area | Tools and Technologies | +|----------------|----------------------------------------------------------| +| Kubernetes | kubectl, Kind, Minikube, k3d, Helm, Kustomize | +| Containers | Docker, containerd, BuildKit, Podman | +| Go | client-go, controller-runtime, cobra, Prometheus client | +| Observability | Prometheus, Grafana, OpenTelemetry, Loki | +| GitOps | Argo CD, Flux | +| Security | Falco, OPA/Gatekeeper, Kyverno, Trivy, Cosign | +| Networking | CoreDNS, Cilium, Calico, Istio, Gateway API | +| CNCF Projects | Various graduated and incubating projects | +| Automation | Bash, Make, GitHub Actions | + +--- + +## Prerequisites + +The following tools are required to run the labs locally. + +**Core (required):** + +- [Docker](https://docs.docker.com/get-docker/) ≥ 24.x +- [kubectl](https://kubernetes.io/docs/tasks/tools/) ≥ 1.29 +- [Kind](https://kind.sigs.k8s.io/docs/user/quick-start/) ≥ 0.23 + +**Optional (required for specific labs):** + +- [Helm](https://helm.sh/docs/intro/install/) ≥ 3.x +- [Kustomize](https://kubectl.docs.kubernetes.io/installation/kustomize/) ≥ 5.x +- [Go](https://go.dev/doc/install) ≥ 1.22 +- [Git](https://git-scm.com/) + +Run the prerequisite check: + +```bash +make check +# or +bash scripts/check-prerequisites.sh +``` + +--- + +## Getting Started + +```bash +# 1. Clone the repository +git clone https://github.com/ferreiraad/cloud-native-lab.git +cd cloud-native-lab + +# 2. Check prerequisites +make check + +# 3. Create a local Kind cluster +make cluster-create + +# 4. Explore a lab (example) +cd certifications/ckad/application-design-and-build/multi-container-pod +cat README.md +``` + +--- + +## Running the Labs + +Each lab directory contains: + +- `README.md` — objective, instructions and learning outcomes +- `namespace.yaml` — namespace for the exercise +- `resources.yaml` (or individual YAML files) — Kubernetes manifests +- `validate.sh` — validation commands +- `cleanup.sh` — cleanup commands + +Run a lab: + +```bash +kubectl apply -f namespace.yaml +kubectl apply -f resources.yaml +bash validate.sh +``` + +--- + +## Validation + +Validate all Kubernetes manifests: + +```bash +make validate +``` + +Run linters: + +```bash +make lint +``` + +--- + +## Cleanup + +Each lab has a `cleanup.sh` script. To clean up a specific lab: + +```bash +bash cleanup.sh +``` + +To delete the Kind cluster: + +```bash +make cluster-delete +``` + +--- + +## Learning Method + +1. Read the lab `README.md` before attempting any commands +2. Try to complete the exercise without looking at the answer +3. Use `kubectl explain` and `kubectl --help` to discover options +4. Run the validation script to confirm correct behaviour +5. Run the cleanup script before moving to the next lab +6. Review the key commands section and note anything new +7. For CKAD preparation, practice the imperative command versions as well + +--- + +## Roadmap + +See [roadmap.md](roadmap.md) for the full development plan. + +--- + +## Security + +See [SECURITY.md](SECURITY.md) for responsible disclosure guidelines and +security practices followed in this repository. + +--- + +## Contributing + +See [.github/CONTRIBUTING.md](.github/CONTRIBUTING.md) and +[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). + +--- + +## Disclaimer + +This repository is for **learning purposes only**. The exercises, scripts +and configurations are intended to run against local development clusters +(Kind, Minikube, k3d) and should not be applied to production environments +without careful review and adaptation. + +No certification vendor endorses this repository. All trademarks belong to +their respective owners. + +--- + +## Licence + +[Apache License 2.0](LICENSE) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..11401a7 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,123 @@ +# Security Policy + +## Responsible Disclosure + +If you discover a security vulnerability in any example code, script, or +configuration in this repository, please report it responsibly. + +**Do not open a public GitHub issue for security vulnerabilities.** + +Instead, contact the maintainer directly via the GitHub Security Advisories +feature or by email (visible on the GitHub profile). + +Please include: + +- A description of the vulnerability +- Steps to reproduce +- Potential impact +- Suggested remediation if known + +## Important Notice + +This repository contains **educational and lab content only**. +None of the examples are intended for direct production use without +thorough review and adaptation. + +## No Secrets or Credentials + +**Never commit secrets, credentials, tokens, or passwords** to this repository. + +This includes: + +- API keys +- Passwords +- Tokens +- Private keys +- Connection strings containing credentials + +Use obviously fake placeholder values such as `REPLACE_ME` or `` +in all examples. + +## Secret Scanning + +GitHub secret scanning is enabled on this repository. Push protection will +block commits containing recognised secret patterns. + +Before committing, review all files for secrets using tools such as: + +```bash +# Install truffleHog or gitleaks locally for pre-commit scanning +gitleaks detect --source . --verbose +``` + +## Local Environment Files + +Use `.env` files for local development secrets. These files are excluded +from version control via `.gitignore`. + +Never reference `.env` file values directly in YAML or code that is committed. + +## Kubernetes Secrets + +Kubernetes `Secret` resources in this repository use **placeholder values only**. + +```yaml +# Example: Placeholder only — do not use real values +apiVersion: v1 +kind: Secret +metadata: + name: example-secret +type: Opaque +stringData: + username: REPLACE_ME + password: REPLACE_ME +``` + +Kubernetes Secrets are base64-encoded, **not encrypted at rest** by default. + +For production workloads, prefer: + +- External Secrets Operator +- HashiCorp Vault +- Sealed Secrets +- Cloud provider secret managers (AWS Secrets Manager, GCP Secret Manager, Azure Key Vault) + +## Image Security + +All container images used in examples should: + +- Come from trusted, official sources +- Be pinned to a specific digest or tag +- Be regularly updated +- Be scanned for vulnerabilities before use in sensitive environments + +Use lightweight images such as `nginx:alpine`, `busybox`, or official distroless images +where possible. + +## Least Privilege + +All Kubernetes manifests in this repository follow the principle of least privilege: + +- Containers run as non-root users where possible +- `allowPrivilegeEscalation: false` is set where applicable +- Linux capabilities are dropped where possible +- Read-only root filesystems are used where practical +- Resource requests and limits are defined + +## RBAC + +ServiceAccounts and RBAC configurations use minimal required permissions. +Avoid using `cluster-admin` unless explicitly required by an exercise, +and clearly document why. + +## Supply-Chain Security + +- Pin GitHub Actions to specific commit SHAs or major version tags +- Review third-party dependencies before adding them +- Use `go mod verify` to validate Go module checksums +- Keep dependencies updated + +## Dependency Updates + +Dependencies in Go modules and other tooling should be kept up to date. +Consider enabling GitHub Dependabot alerts for this repository. diff --git a/automation/README.md b/automation/README.md new file mode 100644 index 0000000..9d088e4 --- /dev/null +++ b/automation/README.md @@ -0,0 +1,41 @@ +# Automation + +Scripts, Makefiles, and GitHub Actions for automating lab tasks. + +## Directories + +| Directory | Description | +|-----------------------------------|-----------------------------------------------| +| [scripts/setup/](scripts/setup/) | Environment setup scripts | +| [scripts/validation/](scripts/validation/) | Validation scripts | +| [scripts/cleanup/](scripts/cleanup/) | Cleanup scripts | +| [scripts/troubleshooting/](scripts/troubleshooting/) | Diagnostic scripts | +| [makefiles/](makefiles/) | Reusable Makefile snippets | +| [github-actions/](github-actions/) | Reusable GitHub Actions workflows | + +## Root Scripts + +The main utility scripts are in [../scripts/](../scripts/): + +| Script | Purpose | +|---------------------------|------------------------------------------| +| check-prerequisites.sh | Verify required tools are installed | +| create-kind-cluster.sh | Create a local Kind cluster | +| delete-kind-cluster.sh | Delete the local Kind cluster | +| validate-manifests.sh | Validate Kubernetes YAML manifests | +| cleanup.sh | Remove temporary local files | + +## Root Makefile + +The [../Makefile](../Makefile) provides common targets: + +```bash +make help # Show available targets +make check # Check prerequisites +make cluster-create # Create Kind cluster +make cluster-delete # Delete Kind cluster +make validate # Validate manifests +make lint # Run all linters +make test # Run tests +make clean # Clean temporary files +``` diff --git a/automation/github-actions/README.md b/automation/github-actions/README.md new file mode 100644 index 0000000..9fc02a6 --- /dev/null +++ b/automation/github-actions/README.md @@ -0,0 +1,4 @@ +# github-actions + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/automation/makefiles/README.md b/automation/makefiles/README.md new file mode 100644 index 0000000..c1ad124 --- /dev/null +++ b/automation/makefiles/README.md @@ -0,0 +1,4 @@ +# makefiles + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/automation/scripts/cleanup/README.md b/automation/scripts/cleanup/README.md new file mode 100644 index 0000000..42bdf7f --- /dev/null +++ b/automation/scripts/cleanup/README.md @@ -0,0 +1,4 @@ +# cleanup + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/automation/scripts/setup/README.md b/automation/scripts/setup/README.md new file mode 100644 index 0000000..4571dcf --- /dev/null +++ b/automation/scripts/setup/README.md @@ -0,0 +1,4 @@ +# setup + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/automation/scripts/troubleshooting/README.md b/automation/scripts/troubleshooting/README.md new file mode 100644 index 0000000..995ef04 --- /dev/null +++ b/automation/scripts/troubleshooting/README.md @@ -0,0 +1,4 @@ +# troubleshooting + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/automation/scripts/validation/README.md b/automation/scripts/validation/README.md new file mode 100644 index 0000000..8058f3f --- /dev/null +++ b/automation/scripts/validation/README.md @@ -0,0 +1,4 @@ +# validation + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/certifications/README.md b/certifications/README.md new file mode 100644 index 0000000..dc9c630 --- /dev/null +++ b/certifications/README.md @@ -0,0 +1,23 @@ +# Certifications + +This directory contains certification preparation materials for Kubernetes certifications +offered by the Cloud Native Computing Foundation (CNCF) and the Linux Foundation. + +## Tracks + +| Certification | Status | Directory | +|---------------|-------------|---------------------| +| CKAD | In Progress | [ckad/](ckad/) | +| CKA | Planned | [cka/](cka/) | +| CKS | Planned | [cks/](cks/) | + +## General Advice + +- Always confirm the current official curriculum before your exam +- Practise imperative kubectl commands for speed +- Set up shell aliases and autocompletion before the exam +- Read each question carefully and manage time strictly +- Use `kubectl explain` to check resource field definitions + +> **Disclaimer:** This repository does not guarantee exam success and is not affiliated +> with the Linux Foundation or CNCF. All certification trademarks belong to their respective owners. diff --git a/certifications/cka/README.md b/certifications/cka/README.md new file mode 100644 index 0000000..10584f7 --- /dev/null +++ b/certifications/cka/README.md @@ -0,0 +1,26 @@ +# CKA — Certified Kubernetes Administrator + +> **Status:** Planned. This track will be developed after completing CKAD preparation. +> +> Always confirm the current CKA curriculum at +> [training.linuxfoundation.org](https://training.linuxfoundation.org/). + +## Planned Topics + +- Cluster architecture and components +- Cluster installation with kubeadm +- Cluster upgrades +- ETCD backup and restore +- Networking (CNI, CoreDNS, kube-proxy) +- Storage (PersistentVolumes, StorageClasses, dynamic provisioning) +- Workload management +- Scheduling +- Cluster maintenance +- Troubleshooting nodes and workloads +- Security (RBAC, certificates, audit logging) + +## Prerequisites for CKA + +- Solid understanding of Kubernetes from CKAD +- Experience with Linux system administration +- Familiarity with kubeadm and cluster lifecycle management diff --git a/certifications/ckad/README.md b/certifications/ckad/README.md new file mode 100644 index 0000000..b5f161d --- /dev/null +++ b/certifications/ckad/README.md @@ -0,0 +1,103 @@ +# CKAD — Certified Kubernetes Application Developer + +> **Note:** Always confirm the current official CKAD curriculum at +> [training.linuxfoundation.org](https://training.linuxfoundation.org/) +> before your exam. Exam weightings and topics are subject to change. + +## Purpose + +This track provides hands-on exercises for the CKAD examination. +The focus is on deploying, configuring and managing application workloads +on Kubernetes using the tools and techniques expected in the exam. + +## How Exercises Are Organised + +Each domain directory contains one or more labs. Every lab follows this structure: + +```text +lab-name/ +├── README.md # Objective, instructions, learning outcomes, validation +├── namespace.yaml # Dedicated namespace for the lab +├── resources.yaml # Kubernetes manifests (may be split into multiple files) +├── validate.sh # Validation commands to confirm correct results +└── cleanup.sh # Cleanup commands to remove all created resources +``` + +## Timed Exercises + +CKAD is a performance-based exam. Speed and accuracy are essential. + +Principles for timed practice: + +1. Use `kubectl` imperative commands to generate YAML scaffolding quickly +2. Set up shell aliases before starting: `alias k=kubectl` +3. Enable kubectl autocompletion: `source <(kubectl completion bash)` +4. Use `--dry-run=client -o yaml` to preview resources before applying +5. Work through exercises under a strict time limit +6. Focus on the most common resource types first + +## Command-Line Speed and Accuracy + +Practice these imperative patterns: + +```bash +# Create a pod quickly +kubectl run mypod --image=nginx --restart=Never --dry-run=client -o yaml > pod.yaml + +# Create a deployment +kubectl create deployment myapp --image=nginx --replicas=3 --dry-run=client -o yaml + +# Expose a deployment +kubectl expose deployment myapp --port=80 --type=ClusterIP + +# Create a configmap +kubectl create configmap myconfig --from-literal=key=value + +# Create a secret +kubectl create secret generic mysecret --from-literal=****** +``` + +## Validation Requirements + +Every lab must be validated before moving on: + +- All resources are in the correct namespace +- All Pods are in `Running` or `Completed` state +- Services reach the correct endpoints +- ConfigMaps and Secrets are mounted correctly +- Output of `validate.sh` shows no errors + +## Cleanup Requirements + +Every lab must be cleaned up after completion: + +- Run `bash cleanup.sh` from within the lab directory +- Confirm the namespace is removed: `kubectl get namespace ` +- Do not leave resources running between unrelated labs + +## Progress Checklist + +- [ ] Application Design and Build + - [ ] Multi-container Pod +- [ ] Application Deployment + - [ ] Rolling Update +- [ ] Application Observability and Maintenance + - [ ] Configure Probes +- [ ] Application Environment, Configuration and Security + - [ ] ConfigMap and Secret +- [ ] Services and Networking + - [ ] ClusterIP Service +- [ ] Timed Exercises +- [ ] Mock Exams +- [ ] Exam Strategy + +## Domain Directories + +- [Application Design and Build](application-design-and-build/) +- [Application Deployment](application-deployment/) +- [Application Observability and Maintenance](application-observability-and-maintenance/) +- [Application Environment, Configuration and Security](application-environment-configuration-and-security/) +- [Services and Networking](services-and-networking/) +- [Mock Exams](mock-exams/) +- [Timed Exercises](timed-exercises/) +- [Exam Strategy](exam-strategy/) diff --git a/certifications/ckad/application-deployment/rolling-update/README.md b/certifications/ckad/application-deployment/rolling-update/README.md new file mode 100644 index 0000000..bd81e45 --- /dev/null +++ b/certifications/ckad/application-deployment/rolling-update/README.md @@ -0,0 +1,99 @@ +# Lab: Rolling Update + +## Objective + +Deploy an application using a Kubernetes Deployment and perform a rolling update +to a new image version. + +## Scenario + +You have a Deployment running version 1 of an application. You need to update +it to version 2 with zero downtime using a rolling update strategy. + +## Learning Outcomes + +- Understand the Deployment rolling update strategy +- Use `kubectl set image` to trigger a rolling update +- Monitor rollout status +- Understand rollback with `kubectl rollout undo` +- Know how to configure `maxSurge` and `maxUnavailable` + +## Prerequisites + +- A running Kubernetes cluster +- `kubectl` configured and working + +## Files + +| File | Description | +|------------------|---------------------------------| +| namespace.yaml | Creates the lab namespace | +| resources.yaml | Deployment manifest | +| validate.sh | Validates the lab | +| cleanup.sh | Removes all lab resources | + +## Instructions + +```bash +kubectl apply -f namespace.yaml +kubectl apply -f resources.yaml + +# Watch the rollout +kubectl rollout status deployment/web-app -n ckad-deployment + +# Trigger a rolling update +kubectl set image deployment/web-app web=nginx:1.27-alpine -n ckad-deployment + +# Watch the update +kubectl rollout status deployment/web-app -n ckad-deployment + +# Run validation +bash validate.sh +``` + +## Validation + +```bash +kubectl get deployment web-app -n ckad-deployment +kubectl get pods -n ckad-deployment +kubectl rollout history deployment/web-app -n ckad-deployment +``` + +## Expected Result + +All 3 replicas are running and the Deployment shows the new image. + +## Troubleshooting + +- Check events: `kubectl describe deployment web-app -n ckad-deployment` +- Check Pod logs: `kubectl logs -l app=web-app -n ckad-deployment` + +## Cleanup + +```bash +bash cleanup.sh +``` + +## Additional Challenges + +1. Roll back to the previous revision +2. Pause the rollout mid-way and then resume it +3. Set a custom `progressDeadlineSeconds` + +## Key Commands + +```bash +kubectl set image deployment/ = -n +kubectl rollout status deployment/ -n +kubectl rollout history deployment/ -n +kubectl rollout undo deployment/ -n +kubectl rollout pause deployment/ -n +kubectl rollout resume deployment/ -n +``` + +## Lessons Learned + +- Rolling updates replace Pods gradually, maintaining availability +- `maxSurge` controls how many extra Pods can be created during the update +- `maxUnavailable` controls how many Pods can be unavailable during the update +- Always check `rollout history` before undoing to understand available revisions diff --git a/certifications/ckad/application-deployment/rolling-update/cleanup.sh b/certifications/ckad/application-deployment/rolling-update/cleanup.sh new file mode 100755 index 0000000..2064130 --- /dev/null +++ b/certifications/ckad/application-deployment/rolling-update/cleanup.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail + +NAMESPACE="ckad-deployment" + +echo "==> Cleaning up rolling-update lab..." +kubectl delete namespace "${NAMESPACE}" --ignore-not-found=true +echo "==> Cleanup complete." diff --git a/certifications/ckad/application-deployment/rolling-update/namespace.yaml b/certifications/ckad/application-deployment/rolling-update/namespace.yaml new file mode 100644 index 0000000..e97e934 --- /dev/null +++ b/certifications/ckad/application-deployment/rolling-update/namespace.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: ckad-deployment + labels: + purpose: ckad-lab diff --git a/certifications/ckad/application-deployment/rolling-update/resources.yaml b/certifications/ckad/application-deployment/rolling-update/resources.yaml new file mode 100644 index 0000000..5d1b872 --- /dev/null +++ b/certifications/ckad/application-deployment/rolling-update/resources.yaml @@ -0,0 +1,42 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: web-app + namespace: ckad-deployment + labels: + app: web-app +spec: + replicas: 3 + selector: + matchLabels: + app: web-app + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + template: + metadata: + labels: + app: web-app + spec: + containers: + - name: web + image: nginx:1.26-alpine + ports: + - containerPort: 80 + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 100m + memory: 128Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE diff --git a/certifications/ckad/application-deployment/rolling-update/validate.sh b/certifications/ckad/application-deployment/rolling-update/validate.sh new file mode 100755 index 0000000..860892e --- /dev/null +++ b/certifications/ckad/application-deployment/rolling-update/validate.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -euo pipefail + +NAMESPACE="ckad-deployment" +DEPLOYMENT="web-app" + +echo "==> Validating rolling-update lab..." + +echo "--- Waiting for rollout to complete..." +kubectl rollout status deployment/"${DEPLOYMENT}" \ + --namespace="${NAMESPACE}" \ + --timeout=120s + +echo "--- Checking replica count..." +READY=$(kubectl get deployment "${DEPLOYMENT}" \ + --namespace="${NAMESPACE}" \ + -o jsonpath='{.status.readyReplicas}') + +if [ "${READY}" -lt 3 ]; then + echo "ERROR: Expected 3 ready replicas, found ${READY}" + exit 1 +fi + +echo "" +echo "==> Validation passed." +kubectl get deployment "${DEPLOYMENT}" --namespace="${NAMESPACE}" +kubectl get pods -l app="${DEPLOYMENT}" --namespace="${NAMESPACE}" diff --git a/certifications/ckad/application-design-and-build/multi-container-pod/README.md b/certifications/ckad/application-design-and-build/multi-container-pod/README.md new file mode 100644 index 0000000..13aa727 --- /dev/null +++ b/certifications/ckad/application-design-and-build/multi-container-pod/README.md @@ -0,0 +1,93 @@ +# Lab: Multi-Container Pod + +## Objective + +Create a Kubernetes Pod with multiple containers sharing the same network namespace +and an emptyDir volume. + +## Scenario + +You need to deploy an application Pod with two containers: a main web server +and a log sidecar that reads from a shared volume. + +## Learning Outcomes + +- Understand the multi-container Pod pattern +- Use emptyDir volumes to share data between containers +- Understand that containers in a Pod share the same network namespace +- Know how to target a specific container with `kubectl logs` and `kubectl exec` + +## Prerequisites + +- A running Kubernetes cluster (Kind or Minikube) +- `kubectl` configured and working + +## Files + +| File | Description | +|-----------------|-------------------------------| +| namespace.yaml | Creates the lab namespace | +| resources.yaml | Multi-container Pod manifest | +| validate.sh | Validates the lab | +| cleanup.sh | Removes all lab resources | + +## Instructions + +```bash +kubectl apply -f namespace.yaml +kubectl apply -f resources.yaml +bash validate.sh +``` + +## Validation + +```bash +# Check the Pod is running +kubectl get pod multi-container-pod -n ckad-design + +# Check both containers are running +kubectl get pod multi-container-pod -n ckad-design -o jsonpath='{.status.containerStatuses[*].name}' + +# Tail logs from the sidecar container +kubectl logs multi-container-pod -n ckad-design -c log-sidecar + +# Execute into the main container +kubectl exec -it multi-container-pod -n ckad-design -c web-server -- /bin/sh +``` + +## Expected Result + +The Pod should be in `Running` state with both containers ready (2/2). + +## Troubleshooting + +- If the Pod is in `Pending`, check node resources: `kubectl describe node` +- If a container is in `CrashLoopBackOff`, check logs: `kubectl logs -c ` +- If the volume is not shared, confirm both containers reference the same `volumeMount` name + +## Cleanup + +```bash +bash cleanup.sh +``` + +## Additional Challenges + +1. Add an init container that writes an initial message to the shared volume +2. Modify the sidecar to use a different image +3. Add resource requests and limits to each container + +## Key Commands + +```bash +kubectl get pods -n ckad-design +kubectl describe pod multi-container-pod -n ckad-design +kubectl logs multi-container-pod -n ckad-design -c web-server +kubectl exec -it multi-container-pod -n ckad-design -c web-server -- /bin/sh +``` + +## Lessons Learned + +- Containers in a Pod share network (localhost) and can use volumes to share files +- Each container has its own filesystem apart from mounted volumes +- Use `-c ` to target a specific container in multi-container Pods diff --git a/certifications/ckad/application-design-and-build/multi-container-pod/cleanup.sh b/certifications/ckad/application-design-and-build/multi-container-pod/cleanup.sh new file mode 100755 index 0000000..ad6a9d5 --- /dev/null +++ b/certifications/ckad/application-design-and-build/multi-container-pod/cleanup.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail + +NAMESPACE="ckad-design" + +echo "==> Cleaning up multi-container Pod lab..." +kubectl delete namespace "${NAMESPACE}" --ignore-not-found=true +echo "==> Cleanup complete." diff --git a/certifications/ckad/application-design-and-build/multi-container-pod/namespace.yaml b/certifications/ckad/application-design-and-build/multi-container-pod/namespace.yaml new file mode 100644 index 0000000..064e6fc --- /dev/null +++ b/certifications/ckad/application-design-and-build/multi-container-pod/namespace.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: ckad-design + labels: + purpose: ckad-lab diff --git a/certifications/ckad/application-design-and-build/multi-container-pod/resources.yaml b/certifications/ckad/application-design-and-build/multi-container-pod/resources.yaml new file mode 100644 index 0000000..96c7a3c --- /dev/null +++ b/certifications/ckad/application-design-and-build/multi-container-pod/resources.yaml @@ -0,0 +1,69 @@ +--- +apiVersion: v1 +kind: Pod +metadata: + name: multi-container-pod + namespace: ckad-design + labels: + app: multi-container-demo +spec: + containers: + - name: web-server + image: nginx:1.27-alpine + ports: + - containerPort: 80 + volumeMounts: + - name: shared-logs + mountPath: /var/log/nginx + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 100m + memory: 128Mi + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + runAsNonRoot: false + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE + - name: log-sidecar + image: busybox:1.36 + command: + - /bin/sh + - -c + - | + while true; do + if [ -f /var/log/nginx/access.log ]; then + tail -f /var/log/nginx/access.log + else + echo "Waiting for access.log..." + sleep 5 + fi + done + volumeMounts: + - name: shared-logs + mountPath: /var/log/nginx + resources: + requests: + cpu: 10m + memory: 16Mi + limits: + cpu: 50m + memory: 32Mi + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + capabilities: + drop: + - ALL + volumes: + - name: shared-logs + emptyDir: {} + restartPolicy: Always diff --git a/certifications/ckad/application-design-and-build/multi-container-pod/validate.sh b/certifications/ckad/application-design-and-build/multi-container-pod/validate.sh new file mode 100755 index 0000000..b475f54 --- /dev/null +++ b/certifications/ckad/application-design-and-build/multi-container-pod/validate.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -euo pipefail + +NAMESPACE="ckad-design" +POD_NAME="multi-container-pod" + +echo "==> Validating multi-container Pod lab..." + +echo "--- Checking Pod status..." +kubectl wait pod "${POD_NAME}" \ + --namespace="${NAMESPACE}" \ + --for=condition=Ready \ + --timeout=60s + +echo "--- Checking container count..." +CONTAINER_COUNT=$(kubectl get pod "${POD_NAME}" \ + --namespace="${NAMESPACE}" \ + -o jsonpath='{range .status.containerStatuses[*]}{.name}{"\n"}{end}' | wc -l) + +if [ "${CONTAINER_COUNT}" -lt 2 ]; then + echo "ERROR: Expected at least 2 containers, found ${CONTAINER_COUNT}" + exit 1 +fi + +echo "--- Checking both containers are ready..." +NOT_READY=$(kubectl get pod "${POD_NAME}" \ + --namespace="${NAMESPACE}" \ + -o jsonpath='{range .status.containerStatuses[*]}{.name}{" ready="}{.ready}{"\n"}{end}' \ + | grep "ready=false" || true) + +if [ -n "${NOT_READY}" ]; then + echo "ERROR: Some containers are not ready:" + echo "${NOT_READY}" + exit 1 +fi + +echo "" +echo "==> Validation passed." +echo " Pod: ${POD_NAME}" +echo " Namespace: ${NAMESPACE}" +kubectl get pod "${POD_NAME}" --namespace="${NAMESPACE}" diff --git a/certifications/ckad/application-environment-configuration-and-security/config-map-and-secret/README.md b/certifications/ckad/application-environment-configuration-and-security/config-map-and-secret/README.md new file mode 100644 index 0000000..5c06cce --- /dev/null +++ b/certifications/ckad/application-environment-configuration-and-security/config-map-and-secret/README.md @@ -0,0 +1,85 @@ +# Lab: ConfigMap and Secret + +## Objective + +Create a ConfigMap and a Secret, then consume them as environment variables in a Pod. + +## Scenario + +Your application requires configuration values and credentials injected as +environment variables. Use a ConfigMap for non-sensitive configuration and +a Secret for sensitive values. + +## Learning Outcomes + +- Create ConfigMaps and Secrets declaratively +- Consume ConfigMap values with `envFrom` +- Consume Secret values with `env.valueFrom.secretKeyRef` +- Understand the difference between ConfigMaps and Secrets +- Understand that Secrets are base64-encoded, not encrypted by default + +## Prerequisites + +- A running Kubernetes cluster +- `kubectl` configured and working + +## Files + +| File | Description | +|-----------------|--------------------------------------| +| namespace.yaml | Creates the lab namespace | +| resources.yaml | ConfigMap, Secret, and Pod manifests | +| validate.sh | Validates the lab | +| cleanup.sh | Removes all lab resources | + +## Instructions + +```bash +kubectl apply -f namespace.yaml +kubectl apply -f resources.yaml +bash validate.sh +``` + +## Validation + +```bash +kubectl get pod config-demo -n ckad-config +kubectl logs config-demo -n ckad-config +kubectl get configmap app-config -n ckad-config -o yaml +``` + +## Expected Result + +The Pod logs show the ConfigMap values printed and confirms the Secret key is set. + +## Troubleshooting + +- If the Pod fails, check: `kubectl describe pod config-demo -n ckad-config` +- Verify the ConfigMap and Secret exist: `kubectl get cm,secrets -n ckad-config` + +## Cleanup + +```bash +bash cleanup.sh +``` + +## Additional Challenges + +1. Mount the ConfigMap as a volume file instead of environment variables +2. Create the Secret imperatively using `kubectl create secret` +3. Add a second container that reads from the same ConfigMap + +## Key Commands + +```bash +kubectl create configmap myconfig --from-literal=key=value +kubectl create secret generic mysecret --from-literal=****** +kubectl get configmap -o yaml +kubectl get secret -o jsonpath='{.data}' +``` + +## Lessons Learned + +- Secrets are base64-encoded, not encrypted; use external secret managers in production +- `envFrom` injects all keys from a ConfigMap or Secret as environment variables +- Individual keys can be selected using `env.valueFrom.configMapKeyRef` or `secretKeyRef` diff --git a/certifications/ckad/application-environment-configuration-and-security/config-map-and-secret/cleanup.sh b/certifications/ckad/application-environment-configuration-and-security/config-map-and-secret/cleanup.sh new file mode 100755 index 0000000..7df2002 --- /dev/null +++ b/certifications/ckad/application-environment-configuration-and-security/config-map-and-secret/cleanup.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail + +NAMESPACE="ckad-config" + +echo "==> Cleaning up config-map-and-secret lab..." +kubectl delete namespace "${NAMESPACE}" --ignore-not-found=true +echo "==> Cleanup complete." diff --git a/certifications/ckad/application-environment-configuration-and-security/config-map-and-secret/namespace.yaml b/certifications/ckad/application-environment-configuration-and-security/config-map-and-secret/namespace.yaml new file mode 100644 index 0000000..53a050e --- /dev/null +++ b/certifications/ckad/application-environment-configuration-and-security/config-map-and-secret/namespace.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: ckad-config + labels: + purpose: ckad-lab diff --git a/certifications/ckad/application-environment-configuration-and-security/config-map-and-secret/resources.yaml b/certifications/ckad/application-environment-configuration-and-security/config-map-and-secret/resources.yaml new file mode 100644 index 0000000..c491937 --- /dev/null +++ b/certifications/ckad/application-environment-configuration-and-security/config-map-and-secret/resources.yaml @@ -0,0 +1,66 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: app-config + namespace: ckad-config +data: + APP_ENV: development + LOG_LEVEL: debug + MAX_CONNECTIONS: "100" +--- +apiVersion: v1 +kind: Secret +metadata: + name: app-secret + namespace: ckad-config +type: Opaque +stringData: + DB_PASSWORD: REPLACE_ME_DO_NOT_USE_IN_PRODUCTION + API_KEY: REPLACE_ME_DO_NOT_USE_IN_PRODUCTION +--- +apiVersion: v1 +kind: Pod +metadata: + name: config-demo + namespace: ckad-config + labels: + app: config-demo +spec: + containers: + - name: app + image: busybox:1.36 + command: + - /bin/sh + - -c + - | + echo "APP_ENV=${APP_ENV}" + echo "LOG_LEVEL=${LOG_LEVEL}" + echo "MAX_CONNECTIONS=${MAX_CONNECTIONS}" + echo "DB_PASSWORD is set: $([ -n "${DB_PASSWORD}" ] && echo yes || echo no)" + sleep 3600 + envFrom: + - configMapRef: + name: app-config + env: + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: app-secret + key: DB_PASSWORD + resources: + requests: + cpu: 10m + memory: 16Mi + limits: + cpu: 50m + memory: 32Mi + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + capabilities: + drop: + - ALL + restartPolicy: Never diff --git a/certifications/ckad/application-environment-configuration-and-security/config-map-and-secret/validate.sh b/certifications/ckad/application-environment-configuration-and-security/config-map-and-secret/validate.sh new file mode 100755 index 0000000..8dcdfcf --- /dev/null +++ b/certifications/ckad/application-environment-configuration-and-security/config-map-and-secret/validate.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail + +NAMESPACE="ckad-config" +POD_NAME="config-demo" + +echo "==> Validating config-map-and-secret lab..." + +echo "--- Waiting for Pod to complete or be running..." +kubectl wait pod "${POD_NAME}" \ + --namespace="${NAMESPACE}" \ + --for=condition=Ready \ + --timeout=60s || \ +kubectl wait pod "${POD_NAME}" \ + --namespace="${NAMESPACE}" \ + --for=jsonpath='{.status.phase}'=Succeeded \ + --timeout=60s || true + +echo "--- Checking ConfigMap exists..." +kubectl get configmap app-config --namespace="${NAMESPACE}" > /dev/null + +echo "--- Checking Secret exists..." +kubectl get secret app-secret --namespace="${NAMESPACE}" > /dev/null + +echo "--- Checking Pod logs for expected output..." +LOGS=$(kubectl logs "${POD_NAME}" --namespace="${NAMESPACE}" 2>/dev/null || echo "") +if echo "${LOGS}" | grep -q "APP_ENV=development"; then + echo " ConfigMap values injected correctly" +else + echo "WARNING: Could not verify APP_ENV in logs (Pod may still be starting)" +fi + +echo "" +echo "==> Validation passed." +kubectl get pod "${POD_NAME}" --namespace="${NAMESPACE}" diff --git a/certifications/ckad/application-observability-and-maintenance/configure-probes/README.md b/certifications/ckad/application-observability-and-maintenance/configure-probes/README.md new file mode 100644 index 0000000..1820c31 --- /dev/null +++ b/certifications/ckad/application-observability-and-maintenance/configure-probes/README.md @@ -0,0 +1,83 @@ +# Lab: Configure Probes + +## Objective + +Configure liveness, readiness, and startup probes on a Kubernetes Pod. + +## Scenario + +You are deploying a web application that needs health checks to ensure: +- The container restarts if it becomes unhealthy (liveness) +- Traffic is only sent when the application is ready (readiness) +- The container is given enough time to start up (startup probe) + +## Learning Outcomes + +- Understand the difference between liveness, readiness and startup probes +- Configure HTTP, TCP, and exec probes +- Understand `initialDelaySeconds`, `periodSeconds`, `failureThreshold`, and `successThreshold` +- Observe probe failures and understand how Kubernetes responds + +## Prerequisites + +- A running Kubernetes cluster +- `kubectl` configured and working + +## Files + +| File | Description | +|-----------------|-------------------------------| +| namespace.yaml | Creates the lab namespace | +| resources.yaml | Pod with probes configured | +| validate.sh | Validates the lab | +| cleanup.sh | Removes all lab resources | + +## Instructions + +```bash +kubectl apply -f namespace.yaml +kubectl apply -f resources.yaml +bash validate.sh +``` + +## Validation + +```bash +kubectl get pod probe-demo -n ckad-observability +kubectl describe pod probe-demo -n ckad-observability +``` + +## Expected Result + +Pod is in `Running` state with all probes passing. + +## Troubleshooting + +- If the Pod keeps restarting, the liveness probe is failing +- Check events: `kubectl describe pod probe-demo -n ckad-observability` +- Verify the probe path and port match the application + +## Cleanup + +```bash +bash cleanup.sh +``` + +## Additional Challenges + +1. Introduce a deliberate liveness probe failure and observe the restart +2. Configure a TCP probe instead of HTTP +3. Add a startup probe with a long `failureThreshold` for slow-starting apps + +## Key Commands + +```bash +kubectl describe pod -n +kubectl get events -n --sort-by='.lastTimestamp' +``` + +## Lessons Learned + +- Liveness probes restart failing containers; readiness probes remove them from Service endpoints +- Startup probes protect slow-starting containers from premature liveness probe failures +- Set `initialDelaySeconds` to give the application time to start diff --git a/certifications/ckad/application-observability-and-maintenance/configure-probes/cleanup.sh b/certifications/ckad/application-observability-and-maintenance/configure-probes/cleanup.sh new file mode 100755 index 0000000..52a5acd --- /dev/null +++ b/certifications/ckad/application-observability-and-maintenance/configure-probes/cleanup.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail + +NAMESPACE="ckad-observability" + +echo "==> Cleaning up configure-probes lab..." +kubectl delete namespace "${NAMESPACE}" --ignore-not-found=true +echo "==> Cleanup complete." diff --git a/certifications/ckad/application-observability-and-maintenance/configure-probes/namespace.yaml b/certifications/ckad/application-observability-and-maintenance/configure-probes/namespace.yaml new file mode 100644 index 0000000..3a89023 --- /dev/null +++ b/certifications/ckad/application-observability-and-maintenance/configure-probes/namespace.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: ckad-observability + labels: + purpose: ckad-lab diff --git a/certifications/ckad/application-observability-and-maintenance/configure-probes/resources.yaml b/certifications/ckad/application-observability-and-maintenance/configure-probes/resources.yaml new file mode 100644 index 0000000..c53c832 --- /dev/null +++ b/certifications/ckad/application-observability-and-maintenance/configure-probes/resources.yaml @@ -0,0 +1,50 @@ +--- +apiVersion: v1 +kind: Pod +metadata: + name: probe-demo + namespace: ckad-observability + labels: + app: probe-demo +spec: + containers: + - name: web + image: nginx:1.27-alpine + ports: + - containerPort: 80 + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 100m + memory: 128Mi + startupProbe: + httpGet: + path: / + port: 80 + failureThreshold: 30 + periodSeconds: 2 + livenessProbe: + httpGet: + path: / + port: 80 + initialDelaySeconds: 5 + periodSeconds: 10 + failureThreshold: 3 + successThreshold: 1 + readinessProbe: + httpGet: + path: / + port: 80 + initialDelaySeconds: 3 + periodSeconds: 5 + failureThreshold: 3 + successThreshold: 1 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE diff --git a/certifications/ckad/application-observability-and-maintenance/configure-probes/validate.sh b/certifications/ckad/application-observability-and-maintenance/configure-probes/validate.sh new file mode 100755 index 0000000..0d9686d --- /dev/null +++ b/certifications/ckad/application-observability-and-maintenance/configure-probes/validate.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -euo pipefail + +NAMESPACE="ckad-observability" +POD_NAME="probe-demo" + +echo "==> Validating configure-probes lab..." + +echo "--- Waiting for Pod to be ready..." +kubectl wait pod "${POD_NAME}" \ + --namespace="${NAMESPACE}" \ + --for=condition=Ready \ + --timeout=60s + +echo "--- Checking probe configuration..." +LIVENESS=$(kubectl get pod "${POD_NAME}" \ + --namespace="${NAMESPACE}" \ + -o jsonpath='{.spec.containers[0].livenessProbe}') + +if [ -z "${LIVENESS}" ]; then + echo "ERROR: Liveness probe not configured" + exit 1 +fi + +READINESS=$(kubectl get pod "${POD_NAME}" \ + --namespace="${NAMESPACE}" \ + -o jsonpath='{.spec.containers[0].readinessProbe}') + +if [ -z "${READINESS}" ]; then + echo "ERROR: Readiness probe not configured" + exit 1 +fi + +echo "" +echo "==> Validation passed." +kubectl get pod "${POD_NAME}" --namespace="${NAMESPACE}" diff --git a/certifications/ckad/exam-strategy/README.md b/certifications/ckad/exam-strategy/README.md new file mode 100644 index 0000000..10e7fce --- /dev/null +++ b/certifications/ckad/exam-strategy/README.md @@ -0,0 +1,16 @@ +# Exam Strategy + +This directory will contain notes and tips for approaching the CKAD examination. + +Topics to be documented: + +- Time management strategies +- When to skip and return to a question +- Context switching between cluster contexts +- Setting up aliases and autocompletion +- Using `kubectl explain` effectively +- Using the Kubernetes documentation during the exam +- Common pitfalls and mistakes to avoid + +> Always confirm the current exam format and allowed resources at +> [training.linuxfoundation.org](https://training.linuxfoundation.org/). diff --git a/certifications/ckad/mock-exams/README.md b/certifications/ckad/mock-exams/README.md new file mode 100644 index 0000000..459f1d3 --- /dev/null +++ b/certifications/ckad/mock-exams/README.md @@ -0,0 +1,16 @@ +# Mock Exams + +This directory contains mock CKAD exam scenarios for timed practice. + +Mock exams will be added here once the core domain exercises are complete. +Each mock exam should simulate the structure and time constraints of the +real examination. + +## Guidelines + +- Attempt each mock exam under timed conditions without referring to notes +- Use only resources available in the official exam environment +- Validate each answer before marking it complete +- Review any items you could not complete within the time limit + +> See the [CKAD README](../README.md) for general exam preparation advice. diff --git a/certifications/ckad/services-and-networking/cluster-ip-service/README.md b/certifications/ckad/services-and-networking/cluster-ip-service/README.md new file mode 100644 index 0000000..d0a3b4a --- /dev/null +++ b/certifications/ckad/services-and-networking/cluster-ip-service/README.md @@ -0,0 +1,94 @@ +# Lab: ClusterIP Service + +## Objective + +Expose a Deployment internally within the cluster using a ClusterIP Service. + +## Scenario + +You have a web backend Deployment that needs to be accessible to other Pods +inside the cluster. Create a ClusterIP Service to expose it on port 80. + +## Learning Outcomes + +- Understand ClusterIP Service type +- Understand how selector labels connect a Service to Pods +- Verify Service endpoints +- Test connectivity from within the cluster +- Understand DNS-based service discovery + +## Prerequisites + +- A running Kubernetes cluster +- `kubectl` configured and working + +## Files + +| File | Description | +|-----------------|--------------------------------------| +| namespace.yaml | Creates the lab namespace | +| resources.yaml | Deployment and ClusterIP Service | +| validate.sh | Validates the lab | +| cleanup.sh | Removes all lab resources | + +## Instructions + +```bash +kubectl apply -f namespace.yaml +kubectl apply -f resources.yaml +bash validate.sh +``` + +## Validation + +```bash +kubectl get service web-backend-svc -n ckad-networking +kubectl get endpoints web-backend-svc -n ckad-networking +kubectl describe service web-backend-svc -n ckad-networking +``` + +Test connectivity from within the cluster: + +```bash +kubectl run test-client --image=busybox:1.36 --rm -it --restart=Never \ + -n ckad-networking \ + -- wget -qO- http://web-backend-svc +``` + +## Expected Result + +The Service has two endpoints matching the two Deployment Pods. +The test client successfully reaches the Service. + +## Troubleshooting + +- If endpoints are empty: verify the Service selector matches the Pod labels +- Check: `kubectl get pods -l app=web-backend -n ckad-networking` + +## Cleanup + +```bash +bash cleanup.sh +``` + +## Additional Challenges + +1. Create a NodePort Service to expose the application externally +2. Test DNS resolution: `nslookup web-backend-svc.ckad-networking.svc.cluster.local` +3. Scale the Deployment and confirm the endpoint list updates + +## Key Commands + +```bash +kubectl get service -n +kubectl get endpoints -n +kubectl describe service -n +kubectl run test --image=busybox --rm -it --restart=Never -- wget -qO- http:// +``` + +## Lessons Learned + +- ClusterIP is the default Service type and is only reachable from within the cluster +- The Service selector must exactly match the Pod labels +- Kubernetes DNS resolves `..svc.cluster.local` +- Check `endpoints` to debug connectivity issues — empty endpoints mean the selector does not match diff --git a/certifications/ckad/services-and-networking/cluster-ip-service/cleanup.sh b/certifications/ckad/services-and-networking/cluster-ip-service/cleanup.sh new file mode 100755 index 0000000..6651e5f --- /dev/null +++ b/certifications/ckad/services-and-networking/cluster-ip-service/cleanup.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail + +NAMESPACE="ckad-networking" + +echo "==> Cleaning up cluster-ip-service lab..." +kubectl delete namespace "${NAMESPACE}" --ignore-not-found=true +echo "==> Cleanup complete." diff --git a/certifications/ckad/services-and-networking/cluster-ip-service/namespace.yaml b/certifications/ckad/services-and-networking/cluster-ip-service/namespace.yaml new file mode 100644 index 0000000..ccc1964 --- /dev/null +++ b/certifications/ckad/services-and-networking/cluster-ip-service/namespace.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: ckad-networking + labels: + purpose: ckad-lab diff --git a/certifications/ckad/services-and-networking/cluster-ip-service/resources.yaml b/certifications/ckad/services-and-networking/cluster-ip-service/resources.yaml new file mode 100644 index 0000000..43b37d9 --- /dev/null +++ b/certifications/ckad/services-and-networking/cluster-ip-service/resources.yaml @@ -0,0 +1,53 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: web-backend + namespace: ckad-networking + labels: + app: web-backend +spec: + replicas: 2 + selector: + matchLabels: + app: web-backend + template: + metadata: + labels: + app: web-backend + spec: + containers: + - name: web + image: nginx:1.27-alpine + ports: + - containerPort: 80 + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 100m + memory: 128Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE +--- +apiVersion: v1 +kind: Service +metadata: + name: web-backend-svc + namespace: ckad-networking + labels: + app: web-backend +spec: + selector: + app: web-backend + ports: + - protocol: TCP + port: 80 + targetPort: 80 + type: ClusterIP diff --git a/certifications/ckad/services-and-networking/cluster-ip-service/validate.sh b/certifications/ckad/services-and-networking/cluster-ip-service/validate.sh new file mode 100755 index 0000000..68f532f --- /dev/null +++ b/certifications/ckad/services-and-networking/cluster-ip-service/validate.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -euo pipefail + +NAMESPACE="ckad-networking" +DEPLOYMENT="web-backend" +SERVICE="web-backend-svc" + +echo "==> Validating cluster-ip-service lab..." + +echo "--- Waiting for Deployment rollout..." +kubectl rollout status deployment/"${DEPLOYMENT}" \ + --namespace="${NAMESPACE}" \ + --timeout=120s + +echo "--- Checking Service exists..." +kubectl get service "${SERVICE}" --namespace="${NAMESPACE}" > /dev/null + +echo "--- Checking endpoints..." +ENDPOINTS=$(kubectl get endpoints "${SERVICE}" \ + --namespace="${NAMESPACE}" \ + -o jsonpath='{.subsets[*].addresses}') + +if [ -z "${ENDPOINTS}" ]; then + echo "ERROR: Service has no endpoints. Check that selector labels match Pod labels." + exit 1 +fi + +echo "" +echo "==> Validation passed." +kubectl get deployment "${DEPLOYMENT}" --namespace="${NAMESPACE}" +kubectl get service "${SERVICE}" --namespace="${NAMESPACE}" +kubectl get endpoints "${SERVICE}" --namespace="${NAMESPACE}" diff --git a/certifications/ckad/timed-exercises/README.md b/certifications/ckad/timed-exercises/README.md new file mode 100644 index 0000000..1d2c057 --- /dev/null +++ b/certifications/ckad/timed-exercises/README.md @@ -0,0 +1,17 @@ +# Timed Exercises + +Short, focused timed exercises for building speed and accuracy with `kubectl` +and Kubernetes resource management. + +Timed exercise sets will be added here. Each set should take 2–5 minutes +and focus on imperative command fluency. + +## Recommended Setup + +```bash +alias k=kubectl +source <(kubectl completion bash) +export KUBE_EDITOR=vi +``` + +> See the [CKAD README](../README.md) for command-line speed tips. diff --git a/certifications/cks/README.md b/certifications/cks/README.md new file mode 100644 index 0000000..aaa73ab --- /dev/null +++ b/certifications/cks/README.md @@ -0,0 +1,21 @@ +# CKS — Certified Kubernetes Security Specialist + +> **Status:** Planned. This track will be developed after completing CKA preparation. +> +> Always confirm the current CKS curriculum at +> [training.linuxfoundation.org](https://training.linuxfoundation.org/). + +## Planned Topics + +- Cluster setup and hardening +- Cluster hardening (RBAC, network policies, API server security) +- System hardening (AppArmor, seccomp, pod security) +- Minimising microservice vulnerabilities +- Supply chain security (image scanning, admission control, SBOM) +- Monitoring, logging, and runtime security (Falco, audit logs) + +## Prerequisites for CKS + +- CKA certification (required by the Linux Foundation) +- Experience with Kubernetes security concepts +- Familiarity with Linux security tools diff --git a/cncf-labs/README.md b/cncf-labs/README.md new file mode 100644 index 0000000..2cc43f0 --- /dev/null +++ b/cncf-labs/README.md @@ -0,0 +1,58 @@ +# CNCF Labs + +Explorations of CNCF (Cloud Native Computing Foundation) projects and the CNCF landscape. + +## Overview + +The CNCF hosts over 150 open-source projects across the cloud-native ecosystem. +Projects are organised by maturity level: Sandbox, Incubating, and Graduated. + +## CNCF Landscape + +The [CNCF Landscape](https://landscape.cncf.io/) provides an interactive map of +the cloud-native ecosystem. + +## Directories + +| Directory | Description | +|----------------------------------------|----------------------------------------| +| [projects/](projects/) | Notes and examples for CNCF projects | +| [landscape-notes/](landscape-notes/) | CNCF landscape exploration notes | +| [sandbox-projects/](sandbox-projects/) | Sandbox-stage project experiments | +| [incubating-projects/](incubating-projects/) | Incubating-stage projects | +| [graduated-projects/](graduated-projects/) | Graduated-stage projects | + +## Graduated Projects (selection) + +| Project | Category | +|-----------------|--------------------| +| Kubernetes | Orchestration | +| Prometheus | Monitoring | +| Envoy | Service Proxy | +| CoreDNS | Service Discovery | +| containerd | Container Runtime | +| Argo | GitOps | +| Flux | GitOps | +| Jaeger | Distributed Tracing | +| Vitess | Database | +| etcd | Key-Value Store | +| Helm | Package Management | +| Falco | Security | +| OPA | Policy | +| Cilium | Networking | + +## Incubating Projects (selection) + +| Project | Category | +|---------------------|------------------------| +| OpenTelemetry | Observability | +| KEDA | Event-Driven Autoscaling | +| Knative | Serverless | +| Crossplane | Infrastructure Control | +| Cert-manager | Certificate Management | +| External Secrets | Secrets Management | +| Kyverno | Policy | +| Backstage | Developer Portal | + +> Check [landscape.cncf.io](https://landscape.cncf.io/) for the current project status. +> Project maturity levels change over time. diff --git a/cncf-labs/graduated-projects/README.md b/cncf-labs/graduated-projects/README.md new file mode 100644 index 0000000..ca5d111 --- /dev/null +++ b/cncf-labs/graduated-projects/README.md @@ -0,0 +1,4 @@ +# graduated-projects + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/cncf-labs/incubating-projects/README.md b/cncf-labs/incubating-projects/README.md new file mode 100644 index 0000000..9f58be3 --- /dev/null +++ b/cncf-labs/incubating-projects/README.md @@ -0,0 +1,4 @@ +# incubating-projects + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/cncf-labs/landscape-notes/README.md b/cncf-labs/landscape-notes/README.md new file mode 100644 index 0000000..43f756f --- /dev/null +++ b/cncf-labs/landscape-notes/README.md @@ -0,0 +1,4 @@ +# landscape-notes + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/cncf-labs/projects/README.md b/cncf-labs/projects/README.md new file mode 100644 index 0000000..82e567c --- /dev/null +++ b/cncf-labs/projects/README.md @@ -0,0 +1,4 @@ +# projects + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/cncf-labs/sandbox-projects/README.md b/cncf-labs/sandbox-projects/README.md new file mode 100644 index 0000000..af67620 --- /dev/null +++ b/cncf-labs/sandbox-projects/README.md @@ -0,0 +1,4 @@ +# sandbox-projects + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/container-labs/README.md b/container-labs/README.md new file mode 100644 index 0000000..9ac52da --- /dev/null +++ b/container-labs/README.md @@ -0,0 +1,112 @@ +# Container Labs + +Hands-on exercises for containers, container runtimes, and container security. + +## Learning Roadmap + +| Area | Topics | +|--------------------------------------------|---------------------------------------------| +| [docker/](docker/) | Docker CLI, images, volumes, networks | +| [containerd/](containerd/) | containerd runtime, nerdctl, crictl | +| [images/](images/) | Image layers, building, tagging, pushing | +| [registries/](registries/) | Registry setup, auth, image promotion | +| [namespaces-and-cgroups/](namespaces-and-cgroups/) | Linux namespaces, cgroups, isolation | +| [buildkit/](buildkit/) | Advanced build with BuildKit | +| [multi-stage-builds/](multi-stage-builds/) | Multi-stage Dockerfiles | +| [container-security/](container-security/) | Image scanning, non-root, capabilities | + +## Core Concepts + +### Images + +- A container image is a layered, read-only filesystem +- Each `RUN`, `COPY`, and `ADD` instruction in a Dockerfile adds a layer +- Images are identified by a digest (SHA256) and optionally a tag +- Use minimal base images: `scratch`, `distroless`, `alpine`, or official slim variants + +### Layers + +```dockerfile +# Bad: creates unnecessary layers and leaves cache files +RUN apt-get update +RUN apt-get install -y curl + +# Good: combine related commands and clean up in the same layer +RUN apt-get update && apt-get install -y curl \ + && rm -rf /var/lib/apt/lists/* +``` + +### Registries + +- Docker Hub: `docker.io/library/nginx:alpine` +- GitHub Container Registry: `ghcr.io/org/image:tag` +- Google Artifact Registry: `us-docker.pkg.dev/project/repo/image:tag` + +### Namespaces and cgroups + +Containers use Linux kernel features: + +- **namespaces**: isolate PID, network, mount, UTS, IPC, user +- **cgroups**: limit CPU, memory, I/O + +### Dockerfiles + +Key best practices: + +- Use specific image tags, not `latest` +- Use non-root users +- Use `.dockerignore` to exclude unnecessary files +- Combine `RUN` commands to minimise layers +- Use multi-stage builds to reduce image size + +### Multi-Stage Builds + +```dockerfile +# Stage 1: Build +FROM golang:1.22-alpine AS builder +WORKDIR /app +COPY . . +RUN go build -o myapp . + +# Stage 2: Runtime (minimal image) +FROM scratch +COPY --from=builder /app/myapp /myapp +ENTRYPOINT ["/myapp"] +``` + +### Rootless and Non-Root Containers + +```dockerfile +# Create a non-root user +RUN addgroup -S appgroup && adduser -S appuser -G appgroup +USER appuser +``` + +In Kubernetes: +```yaml +securityContext: + runAsNonRoot: true + runAsUser: 1000 +``` + +### Image Scanning + +Use tools such as Trivy, Grype, or Docker Scout to scan images for vulnerabilities: + +```bash +trivy image nginx:latest +``` + +### containerd Basics + +```bash +# crictl: CRI-compatible container inspection +crictl images +crictl pods +crictl ps +crictl logs + +# nerdctl: Docker-compatible CLI for containerd +nerdctl run -it alpine sh +nerdctl build -t myapp . +``` diff --git a/container-labs/buildkit/README.md b/container-labs/buildkit/README.md new file mode 100644 index 0000000..2bb0036 --- /dev/null +++ b/container-labs/buildkit/README.md @@ -0,0 +1,5 @@ +# buildkit + +Labs for buildkit. + +> Labs will be added here. See [../README.md](../README.md) for context. diff --git a/container-labs/container-security/README.md b/container-labs/container-security/README.md new file mode 100644 index 0000000..02c1ad7 --- /dev/null +++ b/container-labs/container-security/README.md @@ -0,0 +1,5 @@ +# container-security + +Labs for container-security. + +> Labs will be added here. See [../README.md](../README.md) for context. diff --git a/container-labs/containerd/README.md b/container-labs/containerd/README.md new file mode 100644 index 0000000..ace28a5 --- /dev/null +++ b/container-labs/containerd/README.md @@ -0,0 +1,5 @@ +# containerd + +Labs for containerd. + +> Labs will be added here. See [../README.md](../README.md) for context. diff --git a/container-labs/docker/README.md b/container-labs/docker/README.md new file mode 100644 index 0000000..8ec414e --- /dev/null +++ b/container-labs/docker/README.md @@ -0,0 +1,5 @@ +# docker + +Labs for docker. + +> Labs will be added here. See [../README.md](../README.md) for context. diff --git a/container-labs/images/README.md b/container-labs/images/README.md new file mode 100644 index 0000000..53678aa --- /dev/null +++ b/container-labs/images/README.md @@ -0,0 +1,5 @@ +# images + +Labs for images. + +> Labs will be added here. See [../README.md](../README.md) for context. diff --git a/container-labs/multi-stage-builds/README.md b/container-labs/multi-stage-builds/README.md new file mode 100644 index 0000000..8e5dbe4 --- /dev/null +++ b/container-labs/multi-stage-builds/README.md @@ -0,0 +1,17 @@ +# Multi-Stage Builds + +Multi-stage builds reduce image size and improve security by separating the build +environment from the runtime environment. + +## Examples + +- [go-app/](go-app/) — A minimal Go application using distroless runtime + +## Key Benefits + +- Final image contains only what is needed to run the application +- Build tools and source code do not end up in the production image +- Reduces attack surface +- Reduces image pull time and storage + +> See [../README.md](../README.md) for container lab context. diff --git a/container-labs/multi-stage-builds/go-app/.dockerignore b/container-labs/multi-stage-builds/go-app/.dockerignore new file mode 100644 index 0000000..c859852 --- /dev/null +++ b/container-labs/multi-stage-builds/go-app/.dockerignore @@ -0,0 +1,6 @@ +# Docker ignore file +bin/ +*.log +.env +.git/ +README.md diff --git a/container-labs/multi-stage-builds/go-app/Dockerfile b/container-labs/multi-stage-builds/go-app/Dockerfile new file mode 100644 index 0000000..b377303 --- /dev/null +++ b/container-labs/multi-stage-builds/go-app/Dockerfile @@ -0,0 +1,41 @@ +# syntax=docker/dockerfile:1 + +# ============================================================================= +# Stage 1: Build +# ============================================================================= +FROM golang:1.22-alpine AS builder + +# Install CA certificates (needed for HTTPS calls in the final image) +RUN apk add --no-cache ca-certificates + +WORKDIR /build + +# Copy go module files first for layer caching +COPY go.mod go.sum ./ +RUN go mod download + +# Copy source code +COPY . . + +# Build a statically linked binary +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ + go build -ldflags="-w -s" -o /app/server . + +# ============================================================================= +# Stage 2: Runtime +# Use distroless for a minimal, secure runtime image +# ============================================================================= +FROM gcr.io/distroless/static:nonroot + +# Copy CA certificates from builder +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ + +# Copy the binary +COPY --from=builder /app/server /server + +# Use non-root user (nonroot user is uid=65532 in distroless) +USER nonroot:nonroot + +EXPOSE 8080 + +ENTRYPOINT ["/server"] diff --git a/container-labs/multi-stage-builds/go-app/README.md b/container-labs/multi-stage-builds/go-app/README.md new file mode 100644 index 0000000..ced7717 --- /dev/null +++ b/container-labs/multi-stage-builds/go-app/README.md @@ -0,0 +1,46 @@ +# Multi-Stage Build: Go Application + +A secure, minimal Go application container using a multi-stage Dockerfile. + +## Overview + +This example demonstrates: + +- Multi-stage Docker builds to minimise image size +- Statically linked Go binary +- Distroless runtime image (no shell, no package manager) +- Non-root user +- CA certificate handling + +## Build + +```bash +docker build -t go-app:latest . +``` + +## Run + +```bash +docker run -p 8080:8080 go-app:latest +curl http://localhost:8080/health +``` + +## Why Distroless? + +Distroless images contain only the application and its runtime dependencies. +They do not include a shell, package manager, or other tools found in standard +Linux distributions. This significantly reduces the attack surface. + +## Image Size Comparison + +| Stage | Base Image | Approximate Size | +|-------------|-------------------------|-----------------| +| Builder | golang:1.22-alpine | ~300 MB | +| Final | distroless/static | ~3-5 MB | + +## Security Features + +- No shell in the final image +- Non-root user (`nonroot`, uid=65532) +- Statically linked binary (no dynamic dependencies) +- `-ldflags="-w -s"` strips debug symbols and DWARF info diff --git a/container-labs/multi-stage-builds/go-app/go-app b/container-labs/multi-stage-builds/go-app/go-app new file mode 100755 index 0000000..8ea1855 Binary files /dev/null and b/container-labs/multi-stage-builds/go-app/go-app differ diff --git a/container-labs/multi-stage-builds/go-app/go.mod b/container-labs/multi-stage-builds/go-app/go.mod new file mode 100644 index 0000000..ed70ded --- /dev/null +++ b/container-labs/multi-stage-builds/go-app/go.mod @@ -0,0 +1,3 @@ +module github.com/ferreiraad/cloud-native-lab/container-labs/multi-stage-builds/go-app + +go 1.22 diff --git a/container-labs/multi-stage-builds/go-app/go.sum b/container-labs/multi-stage-builds/go-app/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/container-labs/multi-stage-builds/go-app/main.go b/container-labs/multi-stage-builds/go-app/main.go new file mode 100644 index 0000000..44752fa --- /dev/null +++ b/container-labs/multi-stage-builds/go-app/main.go @@ -0,0 +1,21 @@ +package main + +import ( + "fmt" + "log" + "net/http" +) + +func main() { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "Hello from multi-stage build example!") + }) + http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + fmt.Fprintln(w, "ok") + }) + log.Println("Server listening on :8080") + if err := http.ListenAndServe(":8080", nil); err != nil { + log.Fatalf("server error: %v", err) + } +} diff --git a/container-labs/namespaces-and-cgroups/README.md b/container-labs/namespaces-and-cgroups/README.md new file mode 100644 index 0000000..08c8ba8 --- /dev/null +++ b/container-labs/namespaces-and-cgroups/README.md @@ -0,0 +1,5 @@ +# namespaces-and-cgroups + +Labs for namespaces-and-cgroups. + +> Labs will be added here. See [../README.md](../README.md) for context. diff --git a/container-labs/registries/README.md b/container-labs/registries/README.md new file mode 100644 index 0000000..c26984d --- /dev/null +++ b/container-labs/registries/README.md @@ -0,0 +1,5 @@ +# registries + +Labs for registries. + +> Labs will be added here. See [../README.md](../README.md) for context. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..354c83b --- /dev/null +++ b/docs/README.md @@ -0,0 +1,17 @@ +# Documentation + +Reference documentation, cheatsheets, templates, and notes for cloud-native topics. + +## Contents + +| Directory | Description | +|------------------------------------|--------------------------------------------| +| [templates/](templates/) | Lab README template and reusable templates | +| [cheatsheets/](cheatsheets/) | Quick-reference command cheatsheets | +| [troubleshooting/](troubleshooting/) | Kubernetes troubleshooting runbooks | +| [glossary/](glossary/) | Cloud-native terminology glossary | +| [architecture/](architecture/) | Architecture notes and diagrams | +| [diagrams/](diagrams/) | Diagram source files | +| [notes/](notes/) | Study notes | +| [commands/](commands/) | Useful command references | +| [resources/](resources/) | External resource links | diff --git a/docs/architecture/README.md b/docs/architecture/README.md new file mode 100644 index 0000000..db2dd51 --- /dev/null +++ b/docs/architecture/README.md @@ -0,0 +1,5 @@ +# architecture + +Documentation and content for architecture. + +> Content will be added here as the repository evolves. diff --git a/docs/cheatsheets/docker.md b/docs/cheatsheets/docker.md new file mode 100644 index 0000000..4c50a26 --- /dev/null +++ b/docs/cheatsheets/docker.md @@ -0,0 +1,99 @@ +# Docker Cheatsheet + +A quick reference for common Docker commands. + +## Images + +```bash +# Pull an image +docker pull nginx:alpine + +# List images +docker images +docker image ls + +# Build an image +docker build -t myapp:latest . +docker build -t myapp:latest -f Dockerfile.prod . + +# Remove image +docker rmi myapp:latest +docker image prune -a # Remove unused images +``` + +## Containers + +```bash +# Run a container +docker run nginx +docker run -d nginx # Detached +docker run -it busybox /bin/sh # Interactive +docker run --name myapp -p 8080:80 nginx # Named with port mapping +docker run --rm busybox echo hello # Remove on exit +docker run -e VAR=value nginx # Environment variable + +# List containers +docker ps # Running containers +docker ps -a # All containers + +# Stop and remove +docker stop +docker rm +docker stop $(docker ps -q) # Stop all + +# Logs +docker logs +docker logs -f # Follow + +# Execute into container +docker exec -it /bin/sh +``` + +## Volumes + +```bash +docker volume create myvolume +docker volume ls +docker run -v myvolume:/data nginx +docker run -v $(pwd):/app nginx # Bind mount +``` + +## Networks + +```bash +docker network ls +docker network create mynet +docker run --network mynet nginx +docker network inspect mynet +``` + +## Dockerfile Tips + +```dockerfile +# Use specific tags, not latest +FROM nginx:1.27-alpine + +# Reduce layers +RUN apt-get update && apt-get install -y pkg1 pkg2 \ + && rm -rf /var/lib/apt/lists/* + +# Use non-root user +RUN adduser -D appuser +USER appuser + +# Copy only what is needed +COPY --chown=appuser:appuser app /app + +# Use ENTRYPOINT + CMD pattern +ENTRYPOINT ["nginx"] +CMD ["-g", "daemon off;"] +``` + +## System + +```bash +docker system df # Disk usage +docker system prune # Remove unused resources +docker stats # Live resource usage +docker info # System information +``` diff --git a/docs/cheatsheets/go.md b/docs/cheatsheets/go.md new file mode 100644 index 0000000..af3f708 --- /dev/null +++ b/docs/cheatsheets/go.md @@ -0,0 +1,106 @@ +# Go Cheatsheet + +A quick reference for Go commands and patterns used in this repository. + +## Module Management + +```bash +# Initialize a module +go mod init github.com/example/mymodule + +# Add dependencies +go get k8s.io/client-go@v0.30.2 + +# Tidy dependencies +go mod tidy + +# Verify dependencies +go mod verify + +# List dependencies +go list -m all +``` + +## Building + +```bash +# Build binary +go build -o bin/myapp . + +# Build for Linux (cross-compile) +GOOS=linux GOARCH=amd64 go build -o bin/myapp . + +# Run directly +go run main.go +go run main.go --flag value +``` + +## Testing + +```bash +# Run all tests +go test ./... + +# Run with verbose output +go test -v ./... + +# Run specific test +go test -run TestMyFunction ./... + +# Run with coverage +go test -coverprofile=coverage.out ./... +go tool cover -html=coverage.out + +# Run benchmarks +go test -bench=. ./... +``` + +## Code Quality + +```bash +# Format code +gofmt -w . + +# Check formatting +gofmt -l . + +# Run vet +go vet ./... + +# Run staticcheck +staticcheck ./... +``` + +## Common Patterns + +```go +// Error handling +result, err := someFunction() +if err != nil { + return fmt.Errorf("someFunction: %w", err) +} + +// Context usage +ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) +defer cancel() + +// Defer cleanup +f, err := os.Open("file.txt") +if err != nil { + return err +} +defer f.Close() +``` + +## Kubernetes Client Pattern + +```go +// Build config from kubeconfig +config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) + +// Create clientset +clientset, err := kubernetes.NewForConfig(config) + +// List Pods +pods, err := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{}) +``` diff --git a/docs/cheatsheets/kubectl.md b/docs/cheatsheets/kubectl.md new file mode 100644 index 0000000..6b6633b --- /dev/null +++ b/docs/cheatsheets/kubectl.md @@ -0,0 +1,210 @@ +# kubectl Cheatsheet + +A practical reference for common `kubectl` commands. + +## Contexts and Namespaces + +```bash +# List contexts +kubectl config get-contexts + +# Switch context +kubectl config use-context + +# Set default namespace for current context +kubectl config set-context --current --namespace= + +# View current context +kubectl config current-context +``` + +## Pods + +```bash +# List Pods +kubectl get pods -n +kubectl get pods --all-namespaces + +# Get Pod details +kubectl describe pod -n + +# Get Pod logs +kubectl logs -n +kubectl logs -c -n +kubectl logs -f -n # follow +kubectl logs --previous -n # previous container + +# Execute into a Pod +kubectl exec -it -n -- /bin/sh +kubectl exec -it -c -n -- /bin/bash + +# Create a temporary debug Pod +kubectl run debug --image=busybox --rm -it --restart=Never -- /bin/sh +``` + +## Deployments + +```bash +# Create a Deployment +kubectl create deployment --image= --replicas=3 + +# Get Deployment status +kubectl get deployment -n +kubectl rollout status deployment/ -n + +# Scale +kubectl scale deployment --replicas=5 -n + +# Update image +kubectl set image deployment/ = -n + +# Rollout history +kubectl rollout history deployment/ -n + +# Rollback +kubectl rollout undo deployment/ -n +kubectl rollout undo deployment/ --to-revision=2 -n + +# Pause and resume rollout +kubectl rollout pause deployment/ -n +kubectl rollout resume deployment/ -n +``` + +## Services + +```bash +# Expose a Deployment +kubectl expose deployment --port=80 --type=ClusterIP -n + +# Get Services +kubectl get service -n +kubectl get endpoints -n + +# Port forward +kubectl port-forward service/ 8080:80 -n +kubectl port-forward pod/ 8080:80 -n +``` + +## ConfigMaps + +```bash +# Create ConfigMap +kubectl create configmap --from-literal=key=value +kubectl create configmap --from-file= + +# Get ConfigMap +kubectl get configmap -o yaml -n +kubectl describe configmap -n +``` + +## Secrets + +```bash +# Create Secret +kubectl create secret generic --from-literal=key=value +kubectl create secret generic --from-file= +kubectl create secret tls --cert= --key= + +# Get Secret (decoded) +kubectl get secret -o jsonpath='{.data.}' -n | base64 -d +``` + +## Jobs and CronJobs + +```bash +# Create Job +kubectl create job --image= + +# Run job from CronJob +kubectl create job --from=cronjob/ -n + +# List Jobs +kubectl get jobs -n +kubectl get cronjobs -n +``` + +## Labels and Annotations + +```bash +# Add a label +kubectl label pod key=value -n + +# Remove a label +kubectl label pod key- -n + +# Select by label +kubectl get pods -l app= -n +kubectl get pods -l 'env in (staging,production)' -n + +# Add annotation +kubectl annotate pod description="my pod" -n +``` + +## Resource Usage + +```bash +# Node resource usage +kubectl top nodes + +# Pod resource usage +kubectl get pods -n +kubectl top pods -n +``` + +## Events + +```bash +kubectl get events -n +kubectl get events -n --sort-by='.lastTimestamp' +kubectl get events -n --field-selector reason=Failed +``` + +## JSONPath + +```bash +# Get a specific field +kubectl get pod -o jsonpath='{.status.podIP}' + +# Get multiple fields +kubectl get pods -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.phase}{"\n"}{end}' + +# Custom columns +kubectl get pods -o custom-columns=NAME:.metadata.name,STATUS:.status.phase,NODE:.spec.nodeName +``` + +## Dry-run and Generate YAML + +```bash +# Dry run a create command +kubectl create deployment myapp --image=nginx --dry-run=client -o yaml + +# Generate Pod YAML +kubectl run mypod --image=nginx --restart=Never --dry-run=client -o yaml > pod.yaml + +# Generate ConfigMap YAML +kubectl create configmap myconfig --from-literal=key=value --dry-run=client -o yaml + +# Apply with dry-run +kubectl apply -f manifest.yaml --dry-run=client +kubectl apply -f manifest.yaml --dry-run=server +``` + +## Useful Aliases + +```bash +alias k=kubectl +alias kns='kubectl config set-context --current --namespace' +alias kctx='kubectl config use-context' +``` + +## Autocompletion + +```bash +# Bash +source <(kubectl completion bash) +echo "source <(kubectl completion bash)" >> ~/.bashrc + +# Zsh +source <(kubectl completion zsh) +echo "source <(kubectl completion zsh)" >> ~/.zshrc +``` diff --git a/docs/cheatsheets/linux.md b/docs/cheatsheets/linux.md new file mode 100644 index 0000000..cab2f58 --- /dev/null +++ b/docs/cheatsheets/linux.md @@ -0,0 +1,102 @@ +# Linux Cheatsheet + +A quick reference for common Linux commands useful in cloud-native environments. + +## File Navigation + +```bash +pwd # Print working directory +ls -la # List files with details +cd /path/to/dir # Change directory +find / -name "*.yaml" # Find files by name +find . -type f -mmin -5 # Files modified in last 5 minutes +``` + +## Text Processing + +```bash +cat file.txt # Display file +grep "pattern" file.txt # Search in file +grep -r "pattern" . # Recursive search +awk '{print $1}' file # Print first column +sed 's/old/new/g' file # Replace text +sort file.txt # Sort lines +uniq file.txt # Remove duplicates +wc -l file.txt # Count lines +head -n 10 file.txt # First 10 lines +tail -f file.txt # Follow file changes +``` + +## Permissions + +```bash +ls -la # View permissions +chmod 644 file.txt # Set file permissions +chmod +x script.sh # Make executable +chown user:group file # Change ownership +stat file.txt # Detailed file info +``` + +## Processes + +```bash +ps aux # List all processes +top # Interactive process viewer +htop # Enhanced process viewer +kill -9 # Force kill process +pkill # Kill by name +jobs # List background jobs +bg / fg # Background/foreground job +``` + +## Networking + +```bash +ip addr # Network interfaces +ip route # Routing table +ss -tlnp # Listening TCP ports +ss -ulnp # Listening UDP ports +curl http://host:port # HTTP request +curl -v https://host # Verbose HTTP request +dig hostname # DNS lookup +nslookup hostname # DNS lookup +traceroute hostname # Trace route +ping -c 4 hostname # Ping +netstat -tlnp # Network connections (legacy) +``` + +## Storage + +```bash +df -h # Disk usage +du -sh /path # Directory size +mount # Show mounted filesystems +lsblk # List block devices +fdisk -l # Disk partitions +``` + +## Users and Groups + +```bash +whoami # Current user +id # User and group IDs +groups # User groups +useradd # Add user +usermod -aG group user # Add user to group +passwd # Change password +su - # Switch user +sudo command # Run as root +``` + +## systemd + +```bash +systemctl status # Service status +systemctl start # Start service +systemctl stop # Stop service +systemctl enable # Enable on boot +systemctl disable # Disable on boot +systemctl restart # Restart service +journalctl -u # View service logs +journalctl -f # Follow all logs +``` diff --git a/docs/commands/README.md b/docs/commands/README.md new file mode 100644 index 0000000..499ce95 --- /dev/null +++ b/docs/commands/README.md @@ -0,0 +1,5 @@ +# commands + +Documentation and content for commands. + +> Content will be added here as the repository evolves. diff --git a/docs/diagrams/README.md b/docs/diagrams/README.md new file mode 100644 index 0000000..36c5972 --- /dev/null +++ b/docs/diagrams/README.md @@ -0,0 +1,5 @@ +# diagrams + +Documentation and content for diagrams. + +> Content will be added here as the repository evolves. diff --git a/docs/glossary/cloud-native-glossary.md b/docs/glossary/cloud-native-glossary.md new file mode 100644 index 0000000..8bf4516 --- /dev/null +++ b/docs/glossary/cloud-native-glossary.md @@ -0,0 +1,157 @@ +# Cloud Native Glossary + +Key terms used in cloud-native engineering and this repository. + +## C + +**CKA (Certified Kubernetes Administrator)** +A professional certification from the Linux Foundation that validates the ability +to install, configure, and manage Kubernetes clusters. + +**CKAD (Certified Kubernetes Application Developer)** +A professional certification from the Linux Foundation that validates the ability +to design, deploy, and configure cloud-native applications on Kubernetes. + +**CKS (Certified Kubernetes Security Specialist)** +A professional certification from the Linux Foundation focused on Kubernetes security. + +**CNCF (Cloud Native Computing Foundation)** +An open-source software foundation that hosts and promotes cloud-native projects +including Kubernetes, Prometheus, Envoy, and many others. + +**ConfigMap** +A Kubernetes object for storing non-sensitive configuration data as key-value pairs. + +**Container** +A lightweight, portable unit of software that packages application code and dependencies. + +**CRD (Custom Resource Definition)** +A Kubernetes API extension that allows users to define custom resource types. + +**CNI (Container Network Interface)** +A specification for network plugins that provide networking for container runtimes. + +## D + +**DaemonSet** +A Kubernetes workload that ensures a Pod runs on every (or selected) node in the cluster. + +**Deployment** +A Kubernetes object that manages a set of identical Pods and supports rolling updates and rollbacks. + +## E + +**etcd** +A distributed key-value store used by Kubernetes to store all cluster state. + +## G + +**GitOps** +An operational framework that applies DevOps practices to infrastructure automation, +using Git as the source of truth for declarative infrastructure and application definitions. + +## H + +**Helm** +The package manager for Kubernetes. Helm uses charts to define, install and upgrade Kubernetes applications. + +## I + +**Ingress** +A Kubernetes API object that provides HTTP and HTTPS routing from outside the cluster to Services. + +## J + +**Job** +A Kubernetes workload that creates one or more Pods to run a task to completion. + +## K + +**kubectl** +The command-line interface for communicating with a Kubernetes API server. + +**Kubernetes** +An open-source container orchestration platform for automating deployment, scaling, +and management of containerised applications. + +**Kustomize** +A Kubernetes-native configuration management tool that uses overlays to customise +YAML manifests without templating. + +## N + +**Namespace** +A virtual cluster within a Kubernetes cluster, used for resource isolation and access control. + +**NetworkPolicy** +A Kubernetes object that controls network traffic between Pods and external endpoints. + +## O + +**OCI (Open Container Initiative)** +A set of open standards for container image formats and container runtime specifications. + +**Operator** +A software extension to Kubernetes that uses custom resources to manage applications. +Operators encode operational knowledge as code. + +**OpenTelemetry** +A CNCF project providing a vendor-agnostic API, SDK, and tools for distributed tracing, +metrics, and logs. + +## P + +**PersistentVolume (PV)** +A piece of storage provisioned in the cluster. + +**PersistentVolumeClaim (PVC)** +A request for storage by a user. + +**Pod** +The smallest deployable unit in Kubernetes. A Pod contains one or more containers. + +**Prometheus** +A CNCF graduated monitoring and alerting toolkit. + +## R + +**RBAC (Role-Based Access Control)** +A Kubernetes mechanism for controlling access to the API based on roles and bindings. + +**ReplicaSet** +A Kubernetes object that maintains a stable set of replica Pods. + +## S + +**Secret** +A Kubernetes object for storing sensitive data such as passwords and tokens. +Note: Secrets are base64-encoded, not encrypted at rest by default. + +**Service** +A Kubernetes object that provides a stable network endpoint for accessing a set of Pods. + +**ServiceAccount** +A Kubernetes object that provides an identity for Pods to interact with the API server. + +**StatefulSet** +A Kubernetes workload for managing stateful applications with stable network identities +and persistent storage. + +## T + +**Taint** +A property on a node that repels Pods unless the Pod has a matching Toleration. + +**Toleration** +A Pod property that allows scheduling onto nodes with matching Taints. + +## V + +**Volume** +A directory in a Pod that containers can access. Volumes may be backed by various +storage types (emptyDir, hostPath, PVC, ConfigMap, Secret, etc.). + +--- + +> This glossary covers key terms relevant to this repository. +> For the comprehensive CNCF glossary, see [glossary.cncf.io](https://glossary.cncf.io/). diff --git a/docs/notes/README.md b/docs/notes/README.md new file mode 100644 index 0000000..ad59cea --- /dev/null +++ b/docs/notes/README.md @@ -0,0 +1,5 @@ +# notes + +Documentation and content for notes. + +> Content will be added here as the repository evolves. diff --git a/docs/resources/README.md b/docs/resources/README.md new file mode 100644 index 0000000..0ddcd3f --- /dev/null +++ b/docs/resources/README.md @@ -0,0 +1,5 @@ +# resources + +Documentation and content for resources. + +> Content will be added here as the repository evolves. diff --git a/docs/templates/lab-readme-template.md b/docs/templates/lab-readme-template.md new file mode 100644 index 0000000..7cfe586 --- /dev/null +++ b/docs/templates/lab-readme-template.md @@ -0,0 +1,84 @@ +# Lab Title + +## Objective + +What this lab achieves. One or two sentences. + +## Scenario + +The context or problem that this lab addresses. Explain why someone would +need to perform these steps in a real-world scenario. + +## Learning Outcomes + +- Outcome 1 +- Outcome 2 +- Outcome 3 + +## Prerequisites + +- Tool or knowledge requirement 1 +- Tool or knowledge requirement 2 + +## Files + +| File | Description | +|-----------------|-------------------------------| +| namespace.yaml | Creates the lab namespace | +| resources.yaml | Kubernetes manifests | +| validate.sh | Validates the lab | +| cleanup.sh | Removes all lab resources | + +## Instructions + +Step-by-step instructions. Use code blocks for commands. + +```bash +kubectl apply -f namespace.yaml +kubectl apply -f resources.yaml +bash validate.sh +``` + +## Validation + +Commands to confirm the expected state. + +```bash +kubectl get -n +``` + +## Expected Result + +Describe what success looks like. + +## Troubleshooting + +Common issues and how to resolve them. + +- **Problem:** Description of issue + - **Solution:** How to fix it + +## Cleanup + +```bash +bash cleanup.sh +``` + +## Additional Challenges + +1. Challenge 1 +2. Challenge 2 +3. Challenge 3 + +## Key Commands + +```bash +# Command 1 — description +# Command 2 — description +``` + +## Lessons Learned + +- Key takeaway 1 +- Key takeaway 2 +- Key takeaway 3 diff --git a/docs/troubleshooting/kubernetes.md b/docs/troubleshooting/kubernetes.md new file mode 100644 index 0000000..4e10089 --- /dev/null +++ b/docs/troubleshooting/kubernetes.md @@ -0,0 +1,105 @@ +# Kubernetes Troubleshooting + +Common Kubernetes issues and debugging steps. + +## Pod Not Starting + +### CrashLoopBackOff + +The container starts and immediately exits. + +```bash +# Check logs (current and previous) +kubectl logs -n +kubectl logs --previous -n + +# Check events +kubectl describe pod -n +``` + +Common causes: +- Application crash on startup (check application logs) +- Missing environment variables or ConfigMaps +- Missing Secrets +- Volume mount errors +- Wrong command or entry point + +### Pending Pod + +The Pod is not scheduled. + +```bash +kubectl describe pod -n +kubectl get events -n --sort-by='.lastTimestamp' +kubectl describe node +``` + +Common causes: +- Insufficient resources (CPU/memory): check node resource usage +- Taints and tolerations mismatch +- NodeSelector or affinity not satisfied +- PVC not bound + +### ImagePullBackOff + +Kubernetes cannot pull the container image. + +```bash +kubectl describe pod -n +``` + +Common causes: +- Wrong image name or tag +- Image registry authentication required +- Registry unreachable from cluster + +## Service Not Reachable + +```bash +# Check Service and endpoints +kubectl get service -n +kubectl get endpoints -n + +# Verify selector labels match Pod labels +kubectl get pods -l -n + +# Test connectivity from inside the cluster +kubectl run debug --image=busybox --rm -it --restart=Never -- wget -qO- http://. +``` + +Common causes: +- Service selector does not match Pod labels +- Wrong targetPort (check containerPort in Pod spec) +- NetworkPolicy blocking traffic + +## Node Not Ready + +```bash +kubectl describe node +kubectl get node -o yaml +journalctl -u kubelet -n 100 # On the node +``` + +Common causes: +- kubelet not running +- Disk pressure +- Memory pressure +- Network unreachable + +## General Debugging + +```bash +# Get all resources in a namespace +kubectl get all -n + +# Watch for changes +kubectl get pods -n -w + +# Check resource usage +kubectl top pods -n +kubectl top nodes + +# Port forward for local testing +kubectl port-forward pod/ 8080:80 -n +kubectl port-forward service/ 8080:80 -n +``` diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..6a9c5cb --- /dev/null +++ b/examples/README.md @@ -0,0 +1,22 @@ +# Examples + +Sample applications demonstrating common cloud-native patterns. + +## Applications + +| Directory | Description | +|---------------------------------------------------|----------------------------------| +| [sample-web-application/](sample-web-application/) | Simple web server example | +| [sample-api/](sample-api/) | REST API example | +| [sample-worker/](sample-worker/) | Background worker example | + +## Purpose + +These examples are designed to: + +- Demonstrate Kubernetes manifest patterns +- Provide targets for observability and security labs +- Show how to apply probes, resource limits, and security contexts +- Demonstrate Helm and Kustomize deployments + +> These examples are for learning purposes only. Do not use directly in production. diff --git a/examples/sample-api/README.md b/examples/sample-api/README.md new file mode 100644 index 0000000..9ba86c5 --- /dev/null +++ b/examples/sample-api/README.md @@ -0,0 +1,4 @@ +# sample-api + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/examples/sample-web-application/README.md b/examples/sample-web-application/README.md new file mode 100644 index 0000000..4a6b904 --- /dev/null +++ b/examples/sample-web-application/README.md @@ -0,0 +1,4 @@ +# sample-web-application + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/examples/sample-worker/README.md b/examples/sample-worker/README.md new file mode 100644 index 0000000..3cf982c --- /dev/null +++ b/examples/sample-worker/README.md @@ -0,0 +1,4 @@ +# sample-worker + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/gitops-labs/README.md b/gitops-labs/README.md new file mode 100644 index 0000000..e31abaa --- /dev/null +++ b/gitops-labs/README.md @@ -0,0 +1,90 @@ +# GitOps Labs + +Hands-on exercises for GitOps practices using Argo CD and Flux. + +## What Is GitOps? + +GitOps is an operational framework that applies DevOps practices to infrastructure +and application delivery. The core principles are: + +1. **Desired state in Git**: All configuration is declarative and stored in Git +2. **Single source of truth**: Git is the authoritative source for cluster state +3. **Automated reconciliation**: A GitOps agent continuously reconciles actual state with desired state +4. **Pull-based deployment**: The agent pulls changes from Git, rather than having CI push to the cluster + +## Key Concepts + +### Desired State + +Everything that should exist in the cluster is defined declaratively in Git as YAML. +The GitOps agent ensures the cluster matches this state at all times. + +### Reconciliation + +The GitOps agent periodically polls Git for changes and applies them to the cluster. +If someone manually changes a resource in the cluster, the agent reverts the change. + +### Pull-Based Deployment + +Traditional CI/CD pushes changes to the cluster using credentials. +GitOps uses pull-based deployment: the agent in the cluster pulls from Git. +This means the cluster network does not need to be externally accessible. + +### Environment Promotion + +Changes flow between environments (development → staging → production) by +updating Git branches or directories that correspond to each environment. + +### Repository Structure Patterns + +Common patterns: + +- **Mono-repo**: all environments in one repository +- **Multi-repo**: separate repositories for each environment or application +- **App-of-apps**: a parent Argo CD Application manages child Applications + +### Drift Detection + +GitOps tools detect and alert when the cluster state diverges from the desired state in Git. + +## Tools + +### Argo CD + +[Argo CD](https://argo-cd.readthedocs.io/) is a CNCF graduated GitOps tool for Kubernetes. + +```bash +# Install Argo CD (see argocd/ directory for instructions) +kubectl create namespace argocd +kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml +``` + +### Flux CD + +[Flux](https://fluxcd.io/) is a CNCF graduated GitOps toolkit for Kubernetes. + +```bash +# Install Flux CLI +flux install +flux check +``` + +## Secrets Considerations + +Secrets in GitOps require special handling because Git should not contain unencrypted secrets. + +Options: +- **Sealed Secrets** (Bitnami): encrypt secrets with a cluster-specific key before committing +- **External Secrets Operator**: sync secrets from an external secret manager (Vault, AWS, GCP) +- **SOPS**: encrypt files with GPG, age, or cloud KMS + +> Do not commit unencrypted secrets to Git, even in private repositories. + +## Directories + +| Directory | Description | +|---------------------------------------------|-------------------------------| +| [argocd/](argocd/) | Argo CD examples | +| [flux/](flux/) | Flux examples | +| [repository-patterns/](repository-patterns/) | Mono-repo and multi-repo patterns | +| [promotion-strategies/](promotion-strategies/) | Environment promotion examples | diff --git a/gitops-labs/argocd/README.md b/gitops-labs/argocd/README.md new file mode 100644 index 0000000..dee497d --- /dev/null +++ b/gitops-labs/argocd/README.md @@ -0,0 +1,4 @@ +# argocd + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/gitops-labs/flux/README.md b/gitops-labs/flux/README.md new file mode 100644 index 0000000..d0caba0 --- /dev/null +++ b/gitops-labs/flux/README.md @@ -0,0 +1,4 @@ +# flux + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/gitops-labs/promotion-strategies/README.md b/gitops-labs/promotion-strategies/README.md new file mode 100644 index 0000000..833e3dd --- /dev/null +++ b/gitops-labs/promotion-strategies/README.md @@ -0,0 +1,4 @@ +# promotion-strategies + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/gitops-labs/repository-patterns/README.md b/gitops-labs/repository-patterns/README.md new file mode 100644 index 0000000..f502d20 --- /dev/null +++ b/gitops-labs/repository-patterns/README.md @@ -0,0 +1,4 @@ +# repository-patterns + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/go-labs/README.md b/go-labs/README.md new file mode 100644 index 0000000..3a64867 --- /dev/null +++ b/go-labs/README.md @@ -0,0 +1,50 @@ +# Go Labs + +Go code in this repository is focused on cloud-native and Kubernetes-related tooling. + +## Purpose + +Go examples in this repository cover: + +- **Kubernetes API clients** — using client-go to interact with the Kubernetes API +- **Controllers** — implementing the controller pattern with informers and work queues +- **Operators** — building Operators using controller-runtime or kubebuilder +- **Admission webhooks** — validating and mutating admission webhooks +- **Metrics exporters** — exposing Prometheus metrics from applications +- **CLI utilities** — command-line tools for cloud-native automation using cobra +- **Cloud-native automation** — scripts and tools for managing Kubernetes resources + +## Structure + +| Directory | Description | +|----------------------------------------------|----------------------------------------| +| [fundamentals/](fundamentals/) | Go language fundamentals | +| [cli-tools/](cli-tools/) | CLI tools using cobra or flag | +| [concurrency/](concurrency/) | Go concurrency patterns | +| [testing/](testing/) | Testing patterns and tools | +| [kubernetes-client/](kubernetes-client/) | Kubernetes API client examples | +| [controllers/](controllers/) | Controller pattern examples | +| [admission-webhooks/](admission-webhooks/) | Admission webhook examples | +| [metrics-exporter/](metrics-exporter/) | Prometheus metrics exporter examples | +| [operators/](operators/) | Operator examples | + +## Getting Started + +```bash +# Prerequisites +go version # Go ≥ 1.22 required + +# Run the list-pods example +cd kubernetes-client/list-pods +go run main.go --namespace default +``` + +## Go Version Policy + +Use Go ≥ 1.22. Each module has its own `go.mod` specifying the minimum version. + +## Operators Note + +Complete Operator implementations may be extracted into their own dedicated repositories +as they grow. The `operators/` directory here provides foundational examples only. +See [operators/README.md](operators/README.md) for details. diff --git a/go-labs/admission-webhooks/README.md b/go-labs/admission-webhooks/README.md new file mode 100644 index 0000000..3008448 --- /dev/null +++ b/go-labs/admission-webhooks/README.md @@ -0,0 +1,18 @@ +# Admission Webhooks + +Go examples for Kubernetes admission webhooks. + +## Types of Admission Webhooks + +- **Validating**: validate resources and reject invalid requests +- **Mutating**: modify resources before they are persisted + +## Planned Content + +- Simple validating webhook +- Mutating webhook to inject labels +- Webhook server setup and TLS +- Testing webhooks locally +- Deploying with cert-manager + +> Labs will be added here. diff --git a/go-labs/cli-tools/README.md b/go-labs/cli-tools/README.md new file mode 100644 index 0000000..14b38e7 --- /dev/null +++ b/go-labs/cli-tools/README.md @@ -0,0 +1,5 @@ +# cli-tools + +Documentation and content for cli-tools. + +> Content will be added here as the repository evolves. diff --git a/go-labs/concurrency/README.md b/go-labs/concurrency/README.md new file mode 100644 index 0000000..98678e5 --- /dev/null +++ b/go-labs/concurrency/README.md @@ -0,0 +1,5 @@ +# concurrency + +Documentation and content for concurrency. + +> Content will be added here as the repository evolves. diff --git a/go-labs/controllers/README.md b/go-labs/controllers/README.md new file mode 100644 index 0000000..5a8006e --- /dev/null +++ b/go-labs/controllers/README.md @@ -0,0 +1,14 @@ +# Controllers + +Go examples for implementing the Kubernetes controller pattern. + +## Planned Content + +- Informer and work queue basics +- Controller reconciliation loop +- Event filtering +- Rate limiting and retries +- Testing controllers with envtest + +> Complete controller implementations may be extracted into dedicated repositories. +> See [../../kubernetes-labs/controllers/](../../kubernetes-labs/controllers/) for YAML context. diff --git a/go-labs/fundamentals/README.md b/go-labs/fundamentals/README.md new file mode 100644 index 0000000..a5ab5d1 --- /dev/null +++ b/go-labs/fundamentals/README.md @@ -0,0 +1,15 @@ +# Go Fundamentals + +Introductory Go exercises for understanding the language before building Kubernetes tooling. + +## Planned Topics + +- Types, variables, and constants +- Functions and methods +- Structs and interfaces +- Error handling +- Goroutines and channels +- Context and cancellation +- Testing with `testing` package + +> Labs will be added here. diff --git a/go-labs/kubernetes-client/list-pods/Makefile b/go-labs/kubernetes-client/list-pods/Makefile new file mode 100644 index 0000000..5c8b71b --- /dev/null +++ b/go-labs/kubernetes-client/list-pods/Makefile @@ -0,0 +1,39 @@ +# list-pods Makefile + +BINARY := bin/list-pods +MAIN := main.go + +.DEFAULT_GOAL := help + +.PHONY: help +help: ## Show this help + @awk 'BEGIN {FS = ":.*##"} /^[a-zA-Z_-]+:.*?##/ { printf " %-15s %s\n", $$1, $$2 }' $(MAKEFILE_LIST) + +.PHONY: build +build: ## Build the binary + @mkdir -p bin + go build -o $(BINARY) $(MAIN) + +.PHONY: run +run: ## Run with default flags + go run $(MAIN) + +.PHONY: test +test: ## Run tests + go test ./... + +.PHONY: fmt +fmt: ## Format Go code + gofmt -w . + +.PHONY: vet +vet: ## Run go vet + go vet ./... + +.PHONY: clean +clean: ## Remove build artefacts + rm -rf bin/ + +.PHONY: tidy +tidy: ## Run go mod tidy + go mod tidy diff --git a/go-labs/kubernetes-client/list-pods/README.md b/go-labs/kubernetes-client/list-pods/README.md new file mode 100644 index 0000000..e665554 --- /dev/null +++ b/go-labs/kubernetes-client/list-pods/README.md @@ -0,0 +1,66 @@ +# list-pods — Kubernetes Pod Lister + +A small Go CLI tool that uses the Kubernetes Go client (client-go) to +list Pods in a namespace and display their name, phase, and node. + +## Usage + +```bash +# List Pods in the default namespace +go run main.go + +# List Pods in a specific namespace +go run main.go --namespace kube-system + +# List Pods across all namespaces +go run main.go --namespace "" + +# Use a specific kubeconfig +go run main.go --kubeconfig /path/to/kubeconfig --namespace default +``` + +## Building + +```bash +make build +# Binary: ./bin/list-pods +``` + +## Running Tests + +```bash +make test +``` + +## Prerequisites + +- Go ≥ 1.22 +- A running Kubernetes cluster +- A valid kubeconfig (the tool uses `~/.kube/config` by default) + +## Output + +```text +NAMESPACE NAME PHASE NODE +default nginx-deployment-7d4f8d9b9c-abc12 Running worker-1 +default nginx-deployment-7d4f8d9b9c-def34 Running worker-2 +kube-system coredns-787d4945fb-gh567 Running control-plane +``` + +## Flags + +| Flag | Default | Description | +|---------------|-------------------|------------------------------| +| `--namespace` | `default` | Namespace to list Pods from | +| `--kubeconfig`| `~/.kube/config` | Path to kubeconfig file | + +## Key Concepts + +- Uses `k8s.io/client-go` for Kubernetes API access +- Reads the active kubeconfig using `clientcmd.BuildConfigFromFlags` +- Lists Pods using the typed clientset +- Displays formatted output with tabwriter + +## Notes + +This example intentionally avoids unnecessary abstractions to keep the code clear. diff --git a/go-labs/kubernetes-client/list-pods/go.mod b/go-labs/kubernetes-client/list-pods/go.mod new file mode 100644 index 0000000..0c77342 --- /dev/null +++ b/go-labs/kubernetes-client/list-pods/go.mod @@ -0,0 +1,50 @@ +module github.com/ferreiraad/cloud-native-lab/go-labs/kubernetes-client/list-pods + +go 1.22.0 + +toolchain go1.24.13 + +require ( + k8s.io/apimachinery v0.30.2 + k8s.io/client-go v0.30.2 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.3 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/imdario/mergo v0.3.6 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/net v0.23.0 // indirect + golang.org/x/oauth2 v0.10.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/term v0.18.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.3.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.30.2 // indirect + k8s.io/klog/v2 v2.120.1 // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect +) diff --git a/go-labs/kubernetes-client/list-pods/go.sum b/go-labs/kubernetes-client/list-pods/go.sum new file mode 100644 index 0000000..a5e2394 --- /dev/null +++ b/go-labs/kubernetes-client/list-pods/go.sum @@ -0,0 +1,152 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= +github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= +github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE= +github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= +golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= +golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.30.2 h1:+ZhRj+28QT4UOH+BKznu4CBgPWgkXO7XAvMcMl0qKvI= +k8s.io/api v0.30.2/go.mod h1:ULg5g9JvOev2dG0u2hig4Z7tQ2hHIuS+m8MNZ+X6EmI= +k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg= +k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/client-go v0.30.2 h1:sBIVJdojUNPDU/jObC+18tXWcTJVcwyqS9diGdWHk50= +k8s.io/client-go v0.30.2/go.mod h1:JglKSWULm9xlJLx4KCkfLLQ7XwtlbflV6uFFSHTMgVs= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/go-labs/kubernetes-client/list-pods/main.go b/go-labs/kubernetes-client/list-pods/main.go new file mode 100644 index 0000000..7e8b795 --- /dev/null +++ b/go-labs/kubernetes-client/list-pods/main.go @@ -0,0 +1,70 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + "path/filepath" + "text/tabwriter" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/util/homedir" +) + +func main() { + var ( + kubeconfig string + namespace string + ) + + defaultKubeconfig := "" + if home := homedir.HomeDir(); home != "" { + defaultKubeconfig = filepath.Join(home, ".kube", "config") + } + + flag.StringVar(&kubeconfig, "kubeconfig", defaultKubeconfig, "path to kubeconfig file") + flag.StringVar(&namespace, "namespace", "default", "namespace to list pods from (empty string lists all namespaces)") + flag.Parse() + + config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) + if err != nil { + fmt.Fprintf(os.Stderr, "error building kubeconfig: %v\n", err) + os.Exit(1) + } + + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + fmt.Fprintf(os.Stderr, "error creating Kubernetes client: %v\n", err) + os.Exit(1) + } + + pods, err := clientset.CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{}) + if err != nil { + fmt.Fprintf(os.Stderr, "error listing pods: %v\n", err) + os.Exit(1) + } + + if len(pods.Items) == 0 { + if namespace == "" { + fmt.Println("No pods found in any namespace.") + } else { + fmt.Printf("No pods found in namespace %q.\n", namespace) + } + return + } + + w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) + fmt.Fprintln(w, "NAMESPACE\tNAME\tPHASE\tNODE") + for _, pod := range pods.Items { + fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", + pod.Namespace, + pod.Name, + string(pod.Status.Phase), + pod.Spec.NodeName, + ) + } + w.Flush() +} diff --git a/go-labs/metrics-exporter/README.md b/go-labs/metrics-exporter/README.md new file mode 100644 index 0000000..e67693c --- /dev/null +++ b/go-labs/metrics-exporter/README.md @@ -0,0 +1,12 @@ +# Metrics Exporter + +Go examples for exposing Prometheus metrics from applications. + +## Planned Content + +- Custom Prometheus counters, gauges, and histograms +- HTTP metrics endpoint (/metrics) +- Kubernetes resource metrics collection +- Integration with ServiceMonitor + +> Labs will be added here. diff --git a/go-labs/operators/README.md b/go-labs/operators/README.md new file mode 100644 index 0000000..2a65738 --- /dev/null +++ b/go-labs/operators/README.md @@ -0,0 +1,23 @@ +# Operators + +Go examples for building Kubernetes Operators. + +> **Note:** Complete Operator implementations may be extracted into their own dedicated +> repositories as they grow. This directory provides foundational patterns and examples only. + +## Planned Content + +- Operator SDK walkthrough +- CRD scaffolding +- Reconciliation loop +- Status subresources +- Finalisers +- Leader election +- Testing with envtest +- Packaging with Operator Lifecycle Manager (OLM) + +## Related Resources + +- [kubernetes-labs/operators/](../../kubernetes-labs/operators/) +- [Operator SDK Documentation](https://sdk.operatorframework.io/) +- [kubebuilder Book](https://book.kubebuilder.io/) diff --git a/go-labs/testing/README.md b/go-labs/testing/README.md new file mode 100644 index 0000000..143acef --- /dev/null +++ b/go-labs/testing/README.md @@ -0,0 +1,5 @@ +# testing + +Documentation and content for testing. + +> Content will be added here as the repository evolves. diff --git a/kubernetes-labs/README.md b/kubernetes-labs/README.md new file mode 100644 index 0000000..8add358 --- /dev/null +++ b/kubernetes-labs/README.md @@ -0,0 +1,38 @@ +# Kubernetes Labs + +General-purpose Kubernetes exercises covering core concepts, workloads, configuration, +networking, storage, security, observability, and scheduling. + +## Structure + +| Directory | Topics | +|--------------------------|-----------------------------------------------------------| +| [core-concepts/](core-concepts/) | Pods, namespaces, labels, annotations | +| [workloads/](workloads/) | Deployments, DaemonSets, StatefulSets, Jobs | +| [configuration/](configuration/) | ConfigMaps, Secrets, environment variables | +| [networking/](networking/) | Services, Ingress, NetworkPolicy, Gateway API | +| [storage/](storage/) | PVs, PVCs, StorageClasses, volumes | +| [security/](security/) | RBAC, security contexts, pod security | +| [observability/](observability/) | Probes, logging, metrics, debugging | +| [scheduling/](scheduling/) | Affinity, taints, topology spread | +| [troubleshooting/](troubleshooting/) | Debugging patterns | +| [custom-resources/](custom-resources/) | CRDs and custom resources | +| [controllers/](controllers/) | Controller pattern | +| [operators/](operators/) | Operator development | +| [helm/](helm/) | Helm charts and exercises | +| [kustomize/](kustomize/) | Kustomize bases and overlays | +| [clusters/](clusters/) | Local cluster setup (Kind, Minikube, k3d) | + +## Getting Started + +```bash +# Create a local Kind cluster +make cluster-create + +# Run a starter lab +cd configuration/config-maps/pod-with-config-map +kubectl apply -f namespace.yaml +kubectl apply -f config-map.yaml +kubectl apply -f pod.yaml +bash validate.sh +``` diff --git a/kubernetes-labs/clusters/k3d/README.md b/kubernetes-labs/clusters/k3d/README.md new file mode 100644 index 0000000..11113f2 --- /dev/null +++ b/kubernetes-labs/clusters/k3d/README.md @@ -0,0 +1,20 @@ +# k3d + +[k3d](https://k3d.io/) creates k3s clusters inside Docker. It is a lightweight alternative +to Kind and Minikube. + +## Quick Start + +```bash +k3d cluster create cloud-native-lab --agents 2 +kubectl get nodes +``` + +## Deleting + +```bash +k3d cluster delete cloud-native-lab +``` + +> This repository uses Kind as the default cluster. k3d examples will be added +> for specific labs that benefit from k3s features. diff --git a/kubernetes-labs/clusters/kind/README.md b/kubernetes-labs/clusters/kind/README.md new file mode 100644 index 0000000..1583677 --- /dev/null +++ b/kubernetes-labs/clusters/kind/README.md @@ -0,0 +1,95 @@ +# Kind Cluster Configuration + +[Kind (Kubernetes in Docker)](https://kind.sigs.k8s.io/) creates local Kubernetes clusters +using Docker containers as nodes. It is the recommended local environment for this repository. + +## Prerequisites + +- Docker ≥ 24.x +- Kind ≥ 0.23 +- kubectl ≥ 1.29 + +## Create a Cluster + +```bash +# Using the repository Makefile (recommended) +make cluster-create + +# Or directly +kind create cluster --name cloud-native-lab --config kind-config.yaml +``` + +## Delete a Cluster + +```bash +make cluster-delete + +# Or directly +kind delete cluster --name cloud-native-lab +``` + +## Cluster Configuration + +The default `kind-config.yaml` creates a three-node cluster: + +- 1 control-plane node with ingress-ready port mappings +- 2 worker nodes + +Port mappings: + +| Host Port | Cluster Port | Purpose | +|-----------|--------------|---------------------| +| 8080 | 80 | HTTP Ingress | +| 8443 | 443 | HTTPS Ingress | + +## Multi-Node Cluster Example + +The default configuration already includes 2 worker nodes. +To add more workers, edit `kind-config.yaml`: + +```yaml +nodes: + - role: control-plane + - role: worker + - role: worker + - role: worker # Add as many as needed +``` + +## Network Policies + +Kind uses `kindnet` by default, which **does not** enforce NetworkPolicy. + +To use NetworkPolicy enforcement, disable the default CNI and install Calico or Cilium: + +```yaml +# Add to kind-config.yaml under the cluster spec +networking: + disableDefaultCNI: true +``` + +Then install Calico: + +```bash +kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml +``` + +## Useful Commands + +```bash +# List all clusters +kind get clusters + +# Get kubeconfig +kind get kubeconfig --name cloud-native-lab + +# Load a local image into the cluster +kind load docker-image myimage:latest --name cloud-native-lab + +# Get cluster nodes +kubectl get nodes +``` + +## Minikube and k3d + +See the [minikube/](../minikube/) and [k3d/](../k3d/) directories for +alternative local cluster configurations. diff --git a/kubernetes-labs/clusters/kind/kind-config.yaml b/kubernetes-labs/clusters/kind/kind-config.yaml new file mode 100644 index 0000000..0f40bf1 --- /dev/null +++ b/kubernetes-labs/clusters/kind/kind-config.yaml @@ -0,0 +1,21 @@ +--- +apiVersion: kind.x-k8s.io/v1alpha4 +kind: Cluster +name: cloud-native-lab +nodes: + - role: control-plane + kubeadmConfigPatches: + - | + kind: InitConfiguration + nodeRegistration: + kubeletExtraArgs: + node-labels: "ingress-ready=true" + extraPortMappings: + - containerPort: 80 + hostPort: 8080 + protocol: TCP + - containerPort: 443 + hostPort: 8443 + protocol: TCP + - role: worker + - role: worker diff --git a/kubernetes-labs/clusters/minikube/README.md b/kubernetes-labs/clusters/minikube/README.md new file mode 100644 index 0000000..88fbde8 --- /dev/null +++ b/kubernetes-labs/clusters/minikube/README.md @@ -0,0 +1,20 @@ +# Minikube + +[Minikube](https://minikube.sigs.k8s.io/) is an alternative for running Kubernetes locally. + +## Quick Start + +```bash +minikube start --driver=docker --cpus=4 --memory=8192 +kubectl get nodes +``` + +## Stopping and Deleting + +```bash +minikube stop +minikube delete +``` + +> This repository uses Kind as the default cluster. Minikube examples will be +> added for specific labs that benefit from Minikube features. diff --git a/kubernetes-labs/configuration/config-maps/pod-with-config-map/README.md b/kubernetes-labs/configuration/config-maps/pod-with-config-map/README.md new file mode 100644 index 0000000..a23bed1 --- /dev/null +++ b/kubernetes-labs/configuration/config-maps/pod-with-config-map/README.md @@ -0,0 +1,86 @@ +# Lab: Pod with ConfigMap + +## Objective + +Create a ConfigMap and expose its values as environment variables in a Pod. + +## Scenario + +You need to configure an application with non-sensitive settings such as +the application environment, log level, and connection limits. + +## Learning Outcomes + +- Create a ConfigMap from literal values +- Expose ConfigMap values as environment variables using `envFrom` and `env` +- Verify that environment variables are available inside the container +- Clean up ConfigMap and Pod resources + +## Prerequisites + +- A running Kubernetes cluster (Kind or Minikube) +- `kubectl` configured and working + +## Files + +| File | Description | +|------------------|--------------------------------------| +| namespace.yaml | Creates the lab namespace | +| config-map.yaml | ConfigMap with application settings | +| pod.yaml | Pod that consumes the ConfigMap | +| validate.sh | Validates the lab | +| cleanup.sh | Removes all lab resources | + +## Instructions + +```bash +kubectl apply -f namespace.yaml +kubectl apply -f config-map.yaml +kubectl apply -f pod.yaml +bash validate.sh +``` + +## Validation + +```bash +kubectl get configmap app-settings -n k8s-config-lab +kubectl get pod config-reader -n k8s-config-lab +kubectl logs config-reader -n k8s-config-lab +``` + +## Expected Result + +The Pod logs show the ConfigMap values printed as environment variables. + +## Troubleshooting + +- If the Pod fails: `kubectl describe pod config-reader -n k8s-config-lab` +- Verify the ConfigMap exists: `kubectl get cm -n k8s-config-lab` +- Check that the ConfigMap name in the Pod spec matches exactly + +## Cleanup + +```bash +bash cleanup.sh +``` + +## Additional Challenges + +1. Mount the ConfigMap as a file in a volume +2. Update a ConfigMap key and observe how the application picks it up +3. Add a second ConfigMap with different settings + +## Key Commands + +```bash +kubectl create configmap --from-literal=key=value +kubectl get configmap -o yaml +kubectl describe configmap +``` + +## Lessons Learned + +- `envFrom.configMapRef` injects all keys as environment variables +- `env.valueFrom.configMapKeyRef` selects individual keys +- ConfigMap updates do not automatically restart Pods using `env` injection +- Volume-mounted ConfigMaps refresh periodically without Pod restart diff --git a/kubernetes-labs/configuration/config-maps/pod-with-config-map/cleanup.sh b/kubernetes-labs/configuration/config-maps/pod-with-config-map/cleanup.sh new file mode 100755 index 0000000..0c82992 --- /dev/null +++ b/kubernetes-labs/configuration/config-maps/pod-with-config-map/cleanup.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail + +NAMESPACE="k8s-config-lab" + +echo "==> Cleaning up pod-with-config-map lab..." +kubectl delete namespace "${NAMESPACE}" --ignore-not-found=true +echo "==> Cleanup complete." diff --git a/kubernetes-labs/configuration/config-maps/pod-with-config-map/config-map.yaml b/kubernetes-labs/configuration/config-maps/pod-with-config-map/config-map.yaml new file mode 100644 index 0000000..ed46e9a --- /dev/null +++ b/kubernetes-labs/configuration/config-maps/pod-with-config-map/config-map.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: app-settings + namespace: k8s-config-lab +data: + APP_ENV: "staging" + LOG_LEVEL: "info" + MAX_CONNECTIONS: "50" + FEATURE_FLAG_DARK_MODE: "true" diff --git a/kubernetes-labs/configuration/config-maps/pod-with-config-map/namespace.yaml b/kubernetes-labs/configuration/config-maps/pod-with-config-map/namespace.yaml new file mode 100644 index 0000000..6a4d616 --- /dev/null +++ b/kubernetes-labs/configuration/config-maps/pod-with-config-map/namespace.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: k8s-config-lab + labels: + purpose: kubernetes-lab diff --git a/kubernetes-labs/configuration/config-maps/pod-with-config-map/pod.yaml b/kubernetes-labs/configuration/config-maps/pod-with-config-map/pod.yaml new file mode 100644 index 0000000..94b9869 --- /dev/null +++ b/kubernetes-labs/configuration/config-maps/pod-with-config-map/pod.yaml @@ -0,0 +1,42 @@ +--- +apiVersion: v1 +kind: Pod +metadata: + name: config-reader + namespace: k8s-config-lab + labels: + app: config-reader +spec: + containers: + - name: reader + image: busybox:1.36 + command: + - /bin/sh + - -c + - | + echo "=== Environment variables from ConfigMap ===" + echo "APP_ENV=${APP_ENV}" + echo "LOG_LEVEL=${LOG_LEVEL}" + echo "MAX_CONNECTIONS=${MAX_CONNECTIONS}" + echo "FEATURE_FLAG_DARK_MODE=${FEATURE_FLAG_DARK_MODE}" + echo "=== Done ===" + sleep 3600 + envFrom: + - configMapRef: + name: app-settings + resources: + requests: + cpu: 10m + memory: 16Mi + limits: + cpu: 50m + memory: 32Mi + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + capabilities: + drop: + - ALL + restartPolicy: Never diff --git a/kubernetes-labs/configuration/config-maps/pod-with-config-map/validate.sh b/kubernetes-labs/configuration/config-maps/pod-with-config-map/validate.sh new file mode 100755 index 0000000..21d07ef --- /dev/null +++ b/kubernetes-labs/configuration/config-maps/pod-with-config-map/validate.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail + +NAMESPACE="k8s-config-lab" +POD_NAME="config-reader" +CONFIGMAP_NAME="app-settings" + +echo "==> Validating pod-with-config-map lab..." + +echo "--- Checking ConfigMap exists..." +kubectl get configmap "${CONFIGMAP_NAME}" --namespace="${NAMESPACE}" > /dev/null +echo " ConfigMap '${CONFIGMAP_NAME}' found." + +echo "--- Waiting for Pod..." +kubectl wait pod "${POD_NAME}" --namespace="${NAMESPACE}" --for=condition=Ready --timeout=60s 2>/dev/null || true + +echo "--- Checking Pod logs for ConfigMap values..." +LOGS=$(kubectl logs "${POD_NAME}" --namespace="${NAMESPACE}" 2>/dev/null || echo "") + +if echo "${LOGS}" | grep -q "APP_ENV=staging"; then + echo " APP_ENV value confirmed." +else + echo "WARNING: Could not verify APP_ENV=staging in Pod logs." +fi + +echo "" +echo "==> Validation complete." +kubectl get pod "${POD_NAME}" --namespace="${NAMESPACE}" diff --git a/kubernetes-labs/configuration/environment-variables/README.md b/kubernetes-labs/configuration/environment-variables/README.md new file mode 100644 index 0000000..189f6f3 --- /dev/null +++ b/kubernetes-labs/configuration/environment-variables/README.md @@ -0,0 +1,5 @@ +# environment-variables + +Labs for environment-variables (part of configuration). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/configuration/resource-requirements/README.md b/kubernetes-labs/configuration/resource-requirements/README.md new file mode 100644 index 0000000..7793fa5 --- /dev/null +++ b/kubernetes-labs/configuration/resource-requirements/README.md @@ -0,0 +1,5 @@ +# resource-requirements + +Labs for resource-requirements (part of configuration). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/configuration/secrets/README.md b/kubernetes-labs/configuration/secrets/README.md new file mode 100644 index 0000000..85c0ce6 --- /dev/null +++ b/kubernetes-labs/configuration/secrets/README.md @@ -0,0 +1,5 @@ +# secrets + +Labs for secrets (part of configuration). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/configuration/service-accounts/README.md b/kubernetes-labs/configuration/service-accounts/README.md new file mode 100644 index 0000000..b75e0d7 --- /dev/null +++ b/kubernetes-labs/configuration/service-accounts/README.md @@ -0,0 +1,5 @@ +# service-accounts + +Labs for service-accounts (part of configuration). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/controllers/README.md b/kubernetes-labs/controllers/README.md new file mode 100644 index 0000000..b4d46c8 --- /dev/null +++ b/kubernetes-labs/controllers/README.md @@ -0,0 +1,13 @@ +# Controllers + +This directory will contain examples of the Kubernetes controller pattern. + +Controllers watch resources and reconcile actual state towards desired state. + +## Planned Content + +- Simple controller walkthrough +- Informer and work queue examples (in Go, see go-labs/) +- Controller testing with envtest + +> Full controller implementations are in [../../go-labs/controllers/](../../go-labs/controllers/). diff --git a/kubernetes-labs/core-concepts/README.md b/kubernetes-labs/core-concepts/README.md new file mode 100644 index 0000000..e60c542 --- /dev/null +++ b/kubernetes-labs/core-concepts/README.md @@ -0,0 +1,13 @@ +# Core Concepts + +Labs covering fundamental Kubernetes concepts: Pods, namespaces, labels, annotations, and resource management. + +## Planned Labs + +- Basic Pod lifecycle +- Labels and selectors +- Annotations +- Namespaces +- Resource quota and limit ranges + +> Labs will be added here. See [../README.md](../README.md) for the overall structure. diff --git a/kubernetes-labs/custom-resources/crds/README.md b/kubernetes-labs/custom-resources/crds/README.md new file mode 100644 index 0000000..75fa5cf --- /dev/null +++ b/kubernetes-labs/custom-resources/crds/README.md @@ -0,0 +1,5 @@ +# crds + +Labs for crds (part of custom-resources). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/custom-resources/examples/README.md b/kubernetes-labs/custom-resources/examples/README.md new file mode 100644 index 0000000..3bf321e --- /dev/null +++ b/kubernetes-labs/custom-resources/examples/README.md @@ -0,0 +1,5 @@ +# examples + +Labs for examples (part of custom-resources). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/helm/charts/sample-web/Chart.yaml b/kubernetes-labs/helm/charts/sample-web/Chart.yaml new file mode 100644 index 0000000..05438a2 --- /dev/null +++ b/kubernetes-labs/helm/charts/sample-web/Chart.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: v2 +name: sample-web +description: A sample Helm chart for a web application +type: application +version: 0.1.0 +appVersion: "1.27.0" +keywords: + - web + - nginx + - sample +maintainers: + - name: cloud-native-lab +home: https://github.com/ferreiraad/cloud-native-lab diff --git a/kubernetes-labs/helm/charts/sample-web/README.md b/kubernetes-labs/helm/charts/sample-web/README.md new file mode 100644 index 0000000..5583a44 --- /dev/null +++ b/kubernetes-labs/helm/charts/sample-web/README.md @@ -0,0 +1,47 @@ +# sample-web Helm Chart + +A simple Helm chart for deploying an nginx web server. + +## Installation + +```bash +# From the chart directory +helm install my-web ./kubernetes-labs/helm/charts/sample-web + +# With custom values +helm install my-web ./kubernetes-labs/helm/charts/sample-web --set replicaCount=3 --set image.tag=1.27-alpine + +# In a specific namespace +helm install my-web ./kubernetes-labs/helm/charts/sample-web --namespace helm-lab --create-namespace +``` + +## Linting + +```bash +helm lint ./kubernetes-labs/helm/charts/sample-web +``` + +## Upgrading + +```bash +helm upgrade my-web ./kubernetes-labs/helm/charts/sample-web --set image.tag=1.27-alpine +``` + +## Uninstalling + +```bash +helm uninstall my-web +helm uninstall my-web --namespace helm-lab +``` + +## Values + +| Parameter | Description | Default | +|-----------------------|---------------------------------|-----------------| +| `replicaCount` | Number of replicas | `2` | +| `image.repository` | Image repository | `nginx` | +| `image.tag` | Image tag | `1.27-alpine` | +| `service.type` | Service type | `ClusterIP` | +| `service.port` | Service port | `80` | +| `resources.requests` | Resource requests | See values.yaml | +| `resources.limits` | Resource limits | See values.yaml | diff --git a/kubernetes-labs/helm/charts/sample-web/templates/_helpers.tpl b/kubernetes-labs/helm/charts/sample-web/templates/_helpers.tpl new file mode 100644 index 0000000..e2eef4e --- /dev/null +++ b/kubernetes-labs/helm/charts/sample-web/templates/_helpers.tpl @@ -0,0 +1,47 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "sample-web.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "sample-web.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart label. +*/}} +{{- define "sample-web.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels. +*/}} +{{- define "sample-web.labels" -}} +helm.sh/chart: {{ include "sample-web.chart" . }} +{{ include "sample-web.selectorLabels" . }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels. +*/}} +{{- define "sample-web.selectorLabels" -}} +app.kubernetes.io/name: {{ include "sample-web.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/kubernetes-labs/helm/charts/sample-web/templates/deployment.yaml b/kubernetes-labs/helm/charts/sample-web/templates/deployment.yaml new file mode 100644 index 0000000..4d4b1f6 --- /dev/null +++ b/kubernetes-labs/helm/charts/sample-web/templates/deployment.yaml @@ -0,0 +1,35 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "sample-web.fullname" . }} + labels: + {{- include "sample-web.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "sample-web.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "sample-web.selectorLabels" . | nindent 8 }} + spec: + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 80 + protocol: TCP + livenessProbe: + {{- toYaml .Values.livenessProbe | nindent 12 }} + readinessProbe: + {{- toYaml .Values.readinessProbe | nindent 12 }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} diff --git a/kubernetes-labs/helm/charts/sample-web/templates/service.yaml b/kubernetes-labs/helm/charts/sample-web/templates/service.yaml new file mode 100644 index 0000000..66b69c4 --- /dev/null +++ b/kubernetes-labs/helm/charts/sample-web/templates/service.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "sample-web.fullname" . }} + labels: + {{- include "sample-web.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + selector: + {{- include "sample-web.selectorLabels" . | nindent 4 }} + ports: + - protocol: TCP + port: {{ .Values.service.port }} + targetPort: {{ .Values.service.targetPort }} diff --git a/kubernetes-labs/helm/charts/sample-web/values.yaml b/kubernetes-labs/helm/charts/sample-web/values.yaml new file mode 100644 index 0000000..6189ab2 --- /dev/null +++ b/kubernetes-labs/helm/charts/sample-web/values.yaml @@ -0,0 +1,53 @@ +--- +replicaCount: 2 + +image: + repository: nginx + tag: "1.27-alpine" + pullPolicy: IfNotPresent + +service: + type: ClusterIP + port: 80 + targetPort: 80 + +resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 100m + memory: 128Mi + +livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 5 + periodSeconds: 10 + failureThreshold: 3 + +readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 3 + periodSeconds: 5 + failureThreshold: 3 + +securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE + +podSecurityContext: + runAsNonRoot: false + +nameOverride: "" +fullnameOverride: "" + +labels: {} +annotations: {} diff --git a/kubernetes-labs/helm/exercises/README.md b/kubernetes-labs/helm/exercises/README.md new file mode 100644 index 0000000..bd53d38 --- /dev/null +++ b/kubernetes-labs/helm/exercises/README.md @@ -0,0 +1,5 @@ +# exercises + +Labs for exercises (part of helm). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/kustomize/README.md b/kubernetes-labs/kustomize/README.md new file mode 100644 index 0000000..7c2364d --- /dev/null +++ b/kubernetes-labs/kustomize/README.md @@ -0,0 +1,50 @@ +# Kustomize + +[Kustomize](https://kustomize.io/) provides template-free customisation of Kubernetes YAML. +It uses a base-and-overlay pattern to manage environment-specific variations. + +## Structure + +```text +kustomize/ +├── bases/ +│ └── sample-web/ # Base configuration (environment-agnostic) +├── overlays/ +│ ├── development/ # Development overlay (1 replica, dev image) +│ └── production/ # Production overlay (3 replicas, prod image, more resources) +└── exercises/ +``` + +## Previewing Output + +```bash +# Preview development overlay +kubectl kustomize kubernetes-labs/kustomize/overlays/development + +# Preview production overlay +kubectl kustomize kubernetes-labs/kustomize/overlays/production +``` + +## Applying + +```bash +# Apply development overlay +kubectl apply -k kubernetes-labs/kustomize/overlays/development + +# Apply production overlay +kubectl apply -k kubernetes-labs/kustomize/overlays/production +``` + +## What Changes Between Overlays + +| Setting | Development | Production | +|-----------------|---------------|------------------| +| Namespace | kustomize-dev | kustomize-prod | +| Replicas | 1 | 3 | +| Image tag | 1.27-alpine | 1.26-alpine | +| CPU request | 50m | 100m | +| Memory request | 64Mi | 128Mi | +| Label | env=development | env=production | + +> **Note:** These are example values for demonstration purposes only. +> Do not use production values directly from this repository. diff --git a/kubernetes-labs/kustomize/bases/sample-web/deployment.yaml b/kubernetes-labs/kustomize/bases/sample-web/deployment.yaml new file mode 100644 index 0000000..1b680f4 --- /dev/null +++ b/kubernetes-labs/kustomize/bases/sample-web/deployment.yaml @@ -0,0 +1,36 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: sample-web + labels: + app: sample-web +spec: + replicas: 1 + selector: + matchLabels: + app: sample-web + template: + metadata: + labels: + app: sample-web + spec: + containers: + - name: web + image: nginx:1.27-alpine + ports: + - containerPort: 80 + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 100m + memory: 128Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE diff --git a/kubernetes-labs/kustomize/bases/sample-web/kustomization.yaml b/kubernetes-labs/kustomize/bases/sample-web/kustomization.yaml new file mode 100644 index 0000000..0ea9fa0 --- /dev/null +++ b/kubernetes-labs/kustomize/bases/sample-web/kustomization.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - deployment.yaml + - service.yaml diff --git a/kubernetes-labs/kustomize/bases/sample-web/service.yaml b/kubernetes-labs/kustomize/bases/sample-web/service.yaml new file mode 100644 index 0000000..e2630a6 --- /dev/null +++ b/kubernetes-labs/kustomize/bases/sample-web/service.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: sample-web-svc + labels: + app: sample-web +spec: + selector: + app: sample-web + ports: + - port: 80 + targetPort: 80 + type: ClusterIP diff --git a/kubernetes-labs/kustomize/exercises/README.md b/kubernetes-labs/kustomize/exercises/README.md new file mode 100644 index 0000000..65e8916 --- /dev/null +++ b/kubernetes-labs/kustomize/exercises/README.md @@ -0,0 +1,5 @@ +# exercises + +Labs for exercises (part of kustomize). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/kustomize/overlays/development/kustomization.yaml b/kubernetes-labs/kustomize/overlays/development/kustomization.yaml new file mode 100644 index 0000000..e633c7d --- /dev/null +++ b/kubernetes-labs/kustomize/overlays/development/kustomization.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: kustomize-dev + +resources: + - ../../bases/sample-web + +commonLabels: + environment: development + +replicas: + - name: sample-web + count: 1 + +images: + - name: nginx + newTag: "1.27-alpine" diff --git a/kubernetes-labs/kustomize/overlays/production/kustomization.yaml b/kubernetes-labs/kustomize/overlays/production/kustomization.yaml new file mode 100644 index 0000000..1e86aaa --- /dev/null +++ b/kubernetes-labs/kustomize/overlays/production/kustomization.yaml @@ -0,0 +1,37 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: kustomize-prod + +resources: + - ../../bases/sample-web + +commonLabels: + environment: production + +replicas: + - name: sample-web + count: 3 + +images: + - name: nginx + newTag: "1.26-alpine" + +patches: + - patch: |- + - op: replace + path: /spec/template/spec/containers/0/resources/requests/cpu + value: "100m" + - op: replace + path: /spec/template/spec/containers/0/resources/requests/memory + value: "128Mi" + - op: replace + path: /spec/template/spec/containers/0/resources/limits/cpu + value: "200m" + - op: replace + path: /spec/template/spec/containers/0/resources/limits/memory + value: "256Mi" + target: + kind: Deployment + name: sample-web diff --git a/kubernetes-labs/networking/dns/README.md b/kubernetes-labs/networking/dns/README.md new file mode 100644 index 0000000..65d781d --- /dev/null +++ b/kubernetes-labs/networking/dns/README.md @@ -0,0 +1,5 @@ +# dns + +Labs for dns (part of networking). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/networking/gateway-api/README.md b/kubernetes-labs/networking/gateway-api/README.md new file mode 100644 index 0000000..8ecbae4 --- /dev/null +++ b/kubernetes-labs/networking/gateway-api/README.md @@ -0,0 +1,5 @@ +# gateway-api + +Labs for gateway-api (part of networking). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/networking/ingress/README.md b/kubernetes-labs/networking/ingress/README.md new file mode 100644 index 0000000..1c69816 --- /dev/null +++ b/kubernetes-labs/networking/ingress/README.md @@ -0,0 +1,5 @@ +# ingress + +Labs for ingress (part of networking). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/networking/network-policies/default-deny/README.md b/kubernetes-labs/networking/network-policies/default-deny/README.md new file mode 100644 index 0000000..040ca2e --- /dev/null +++ b/kubernetes-labs/networking/network-policies/default-deny/README.md @@ -0,0 +1,109 @@ +# Lab: Default Deny NetworkPolicy + +## Objective + +Create a default-deny NetworkPolicy and then selectively allow traffic +between specific Pods. + +## Scenario + +You need to implement network segmentation in a namespace. First apply a +default-deny policy to block all ingress traffic, then add an allow policy +to permit only traffic from a specific client Pod to the backend. + +## Learning Outcomes + +- Understand how NetworkPolicy controls Pod-to-Pod communication +- Apply a default-deny ingress policy to a namespace +- Create an allow policy using label selectors +- Test allowed and denied traffic using ephemeral Pods +- Understand CNI enforcement requirements + +## Prerequisites + +- A running Kubernetes cluster with a CNI that supports NetworkPolicy + (e.g., Calico, Cilium, Weave Net) +- `kubectl` configured and working + +> **Warning:** NetworkPolicy enforcement depends entirely on the CNI plugin. +> Kind uses kindnet by default, which **does not** enforce NetworkPolicy. +> To test NetworkPolicy enforcement, use Kind with Calico or Cilium, +> or use a cloud-managed cluster. + +## Files + +| File | Description | +|----------------------|------------------------------------------| +| namespace.yaml | Creates the lab namespace | +| pods.yaml | Backend Pod, client Pod, and Service | +| default-deny.yaml | Default-deny ingress NetworkPolicy | +| allow-client.yaml | Allow policy for the client Pod | +| validate.sh | Validation instructions | +| cleanup.sh | Removes all lab resources | + +## Instructions + +```bash +kubectl apply -f namespace.yaml +kubectl apply -f pods.yaml + +# Test connectivity (should succeed before policies are applied) +kubectl exec client-pod -n k8s-netpol -- wget -qO- http://backend-svc --timeout=3 + +# Apply default-deny policy +kubectl apply -f default-deny.yaml + +# Test again (should fail if CNI enforces NetworkPolicy) +kubectl exec client-pod -n k8s-netpol -- wget -qO- http://backend-svc --timeout=3 || echo "Blocked (expected)" + +# Apply allow policy +kubectl apply -f allow-client.yaml + +# Test again (should succeed) +kubectl exec client-pod -n k8s-netpol -- wget -qO- http://backend-svc --timeout=3 +``` + +## Validation + +```bash +kubectl get networkpolicy -n k8s-netpol +kubectl describe networkpolicy default-deny -n k8s-netpol +kubectl describe networkpolicy allow-client -n k8s-netpol +``` + +## Expected Result + +- Traffic is blocked after default-deny policy is applied +- Traffic is allowed after allow-client policy is applied +- Policies are visible with `kubectl get networkpolicy` + +## Troubleshooting + +- If traffic is not blocked: verify that the CNI enforces NetworkPolicy +- If the client cannot reach the backend with the allow policy: check Pod labels match the selector + +## Cleanup + +```bash +bash cleanup.sh +``` + +## Additional Challenges + +1. Add an egress default-deny policy +2. Allow only traffic on port 80 +3. Use a `namespaceSelector` to allow traffic from a different namespace + +## Key Commands + +```bash +kubectl get networkpolicy -n +kubectl describe networkpolicy -n +``` + +## Lessons Learned + +- A default-deny policy must be explicitly created; the default is to allow all traffic +- NetworkPolicy enforcement requires a compatible CNI +- Policies are additive: if any policy allows traffic, it is permitted +- Empty `podSelector` applies the policy to all Pods in the namespace diff --git a/kubernetes-labs/networking/network-policies/default-deny/allow-client.yaml b/kubernetes-labs/networking/network-policies/default-deny/allow-client.yaml new file mode 100644 index 0000000..122f8ef --- /dev/null +++ b/kubernetes-labs/networking/network-policies/default-deny/allow-client.yaml @@ -0,0 +1,20 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-client + namespace: k8s-netpol +spec: + podSelector: + matchLabels: + role: server + policyTypes: + - Ingress + ingress: + - from: + - podSelector: + matchLabels: + role: client + ports: + - protocol: TCP + port: 80 diff --git a/kubernetes-labs/networking/network-policies/default-deny/cleanup.sh b/kubernetes-labs/networking/network-policies/default-deny/cleanup.sh new file mode 100755 index 0000000..b9d2239 --- /dev/null +++ b/kubernetes-labs/networking/network-policies/default-deny/cleanup.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail + +NAMESPACE="k8s-netpol" + +echo "==> Cleaning up default-deny NetworkPolicy lab..." +kubectl delete namespace "${NAMESPACE}" --ignore-not-found=true +echo "==> Cleanup complete." diff --git a/kubernetes-labs/networking/network-policies/default-deny/default-deny.yaml b/kubernetes-labs/networking/network-policies/default-deny/default-deny.yaml new file mode 100644 index 0000000..1259548 --- /dev/null +++ b/kubernetes-labs/networking/network-policies/default-deny/default-deny.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny + namespace: k8s-netpol +spec: + podSelector: {} + policyTypes: + - Ingress diff --git a/kubernetes-labs/networking/network-policies/default-deny/namespace.yaml b/kubernetes-labs/networking/network-policies/default-deny/namespace.yaml new file mode 100644 index 0000000..bb7635f --- /dev/null +++ b/kubernetes-labs/networking/network-policies/default-deny/namespace.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: k8s-netpol + labels: + purpose: kubernetes-lab diff --git a/kubernetes-labs/networking/network-policies/default-deny/pods.yaml b/kubernetes-labs/networking/network-policies/default-deny/pods.yaml new file mode 100644 index 0000000..58baff3 --- /dev/null +++ b/kubernetes-labs/networking/network-policies/default-deny/pods.yaml @@ -0,0 +1,83 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: backend + namespace: k8s-netpol + labels: + app: backend +spec: + replicas: 1 + selector: + matchLabels: + app: backend + template: + metadata: + labels: + app: backend + role: server + spec: + containers: + - name: web + image: nginx:1.27-alpine + ports: + - containerPort: 80 + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 100m + memory: 128Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE +--- +apiVersion: v1 +kind: Service +metadata: + name: backend-svc + namespace: k8s-netpol +spec: + selector: + app: backend + ports: + - port: 80 + targetPort: 80 +--- +apiVersion: v1 +kind: Pod +metadata: + name: client-pod + namespace: k8s-netpol + labels: + app: client + role: client +spec: + containers: + - name: client + image: busybox:1.36 + command: + - /bin/sh + - -c + - sleep 3600 + resources: + requests: + cpu: 10m + memory: 16Mi + limits: + cpu: 50m + memory: 32Mi + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + capabilities: + drop: + - ALL + restartPolicy: Always diff --git a/kubernetes-labs/networking/network-policies/default-deny/validate.sh b/kubernetes-labs/networking/network-policies/default-deny/validate.sh new file mode 100755 index 0000000..86cd264 --- /dev/null +++ b/kubernetes-labs/networking/network-policies/default-deny/validate.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +NAMESPACE="k8s-netpol" + +echo "==> Validating default-deny NetworkPolicy lab..." + +echo "--- Checking Pods are running..." +kubectl wait pod client-pod --namespace="${NAMESPACE}" --for=condition=Ready --timeout=60s + +kubectl rollout status deployment/backend --namespace="${NAMESPACE}" --timeout=60s + +echo "--- Checking NetworkPolicies..." +kubectl get networkpolicy --namespace="${NAMESPACE}" + +NP_COUNT=$(kubectl get networkpolicy --namespace="${NAMESPACE}" --no-headers | wc -l) +if [ "${NP_COUNT}" -lt 1 ]; then + echo "WARNING: No NetworkPolicies found in namespace." +fi + +echo "" +echo "==> Validation complete." +echo "" +echo "NOTE: Actual NetworkPolicy enforcement depends on the CNI." +echo " Kind with kindnet does NOT enforce NetworkPolicy." +echo " Use Calico or Cilium to test enforcement." +echo "" +echo "Manual test (connectivity before policies):" +echo " kubectl exec client-pod -n ${NAMESPACE} -- wget -qO- http://backend-svc --timeout=3" diff --git a/kubernetes-labs/networking/services/README.md b/kubernetes-labs/networking/services/README.md new file mode 100644 index 0000000..e120a04 --- /dev/null +++ b/kubernetes-labs/networking/services/README.md @@ -0,0 +1,5 @@ +# services + +Labs for services (part of networking). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/observability/debugging/README.md b/kubernetes-labs/observability/debugging/README.md new file mode 100644 index 0000000..c41b610 --- /dev/null +++ b/kubernetes-labs/observability/debugging/README.md @@ -0,0 +1,5 @@ +# debugging + +Labs for debugging (part of observability). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/observability/logging/README.md b/kubernetes-labs/observability/logging/README.md new file mode 100644 index 0000000..04a5e99 --- /dev/null +++ b/kubernetes-labs/observability/logging/README.md @@ -0,0 +1,5 @@ +# logging + +Labs for logging (part of observability). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/observability/metrics/README.md b/kubernetes-labs/observability/metrics/README.md new file mode 100644 index 0000000..9c1b4b2 --- /dev/null +++ b/kubernetes-labs/observability/metrics/README.md @@ -0,0 +1,5 @@ +# metrics + +Labs for metrics (part of observability). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/observability/probes/README.md b/kubernetes-labs/observability/probes/README.md new file mode 100644 index 0000000..6ece793 --- /dev/null +++ b/kubernetes-labs/observability/probes/README.md @@ -0,0 +1,5 @@ +# probes + +Labs for probes (part of observability). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/operators/README.md b/kubernetes-labs/operators/README.md new file mode 100644 index 0000000..632086b --- /dev/null +++ b/kubernetes-labs/operators/README.md @@ -0,0 +1,20 @@ +# Operators + +This directory contains notes and starter examples for Kubernetes Operators. + +> **Note:** Complete Operator implementations may be extracted into their own dedicated +> repositories as they grow. This directory provides foundational examples and documentation. + +## What Is an Operator? + +A Kubernetes Operator is an application-specific controller that extends Kubernetes +to manage complex stateful applications. Operators encode operational knowledge +as code. + +## Planned Content + +- CRD and controller overview +- Operator SDK walkthrough +- Sample Operator structure + +See [../../go-labs/operators/](../../go-labs/operators/) for Go implementation examples. diff --git a/kubernetes-labs/scheduling/affinity/README.md b/kubernetes-labs/scheduling/affinity/README.md new file mode 100644 index 0000000..d9bd8d4 --- /dev/null +++ b/kubernetes-labs/scheduling/affinity/README.md @@ -0,0 +1,5 @@ +# affinity + +Labs for affinity (part of scheduling). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/scheduling/labels-and-selectors/README.md b/kubernetes-labs/scheduling/labels-and-selectors/README.md new file mode 100644 index 0000000..79d4f2f --- /dev/null +++ b/kubernetes-labs/scheduling/labels-and-selectors/README.md @@ -0,0 +1,5 @@ +# labels-and-selectors + +Labs for labels-and-selectors (part of scheduling). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/scheduling/node-selectors/README.md b/kubernetes-labs/scheduling/node-selectors/README.md new file mode 100644 index 0000000..def777b --- /dev/null +++ b/kubernetes-labs/scheduling/node-selectors/README.md @@ -0,0 +1,5 @@ +# node-selectors + +Labs for node-selectors (part of scheduling). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/scheduling/taints-and-tolerations/README.md b/kubernetes-labs/scheduling/taints-and-tolerations/README.md new file mode 100644 index 0000000..9157e0b --- /dev/null +++ b/kubernetes-labs/scheduling/taints-and-tolerations/README.md @@ -0,0 +1,5 @@ +# taints-and-tolerations + +Labs for taints-and-tolerations (part of scheduling). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/scheduling/topology-spread/README.md b/kubernetes-labs/scheduling/topology-spread/README.md new file mode 100644 index 0000000..a272298 --- /dev/null +++ b/kubernetes-labs/scheduling/topology-spread/README.md @@ -0,0 +1,5 @@ +# topology-spread + +Labs for topology-spread (part of scheduling). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/security/admission-control/README.md b/kubernetes-labs/security/admission-control/README.md new file mode 100644 index 0000000..396da87 --- /dev/null +++ b/kubernetes-labs/security/admission-control/README.md @@ -0,0 +1,5 @@ +# admission-control + +Labs for admission-control (part of security). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/security/pod-security/README.md b/kubernetes-labs/security/pod-security/README.md new file mode 100644 index 0000000..641160c --- /dev/null +++ b/kubernetes-labs/security/pod-security/README.md @@ -0,0 +1,5 @@ +# pod-security + +Labs for pod-security (part of security). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/security/rbac/README.md b/kubernetes-labs/security/rbac/README.md new file mode 100644 index 0000000..db8ec14 --- /dev/null +++ b/kubernetes-labs/security/rbac/README.md @@ -0,0 +1,5 @@ +# rbac + +Labs for rbac (part of security). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/security/secrets-management/README.md b/kubernetes-labs/security/secrets-management/README.md new file mode 100644 index 0000000..346c1b6 --- /dev/null +++ b/kubernetes-labs/security/secrets-management/README.md @@ -0,0 +1,5 @@ +# secrets-management + +Labs for secrets-management (part of security). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/security/security-contexts/README.md b/kubernetes-labs/security/security-contexts/README.md new file mode 100644 index 0000000..d96aa8f --- /dev/null +++ b/kubernetes-labs/security/security-contexts/README.md @@ -0,0 +1,5 @@ +# security-contexts + +Labs for security-contexts (part of security). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/storage/persistent-volume-claims/basic-pvc/README.md b/kubernetes-labs/storage/persistent-volume-claims/basic-pvc/README.md new file mode 100644 index 0000000..ae40398 --- /dev/null +++ b/kubernetes-labs/storage/persistent-volume-claims/basic-pvc/README.md @@ -0,0 +1,97 @@ +# Lab: Basic PVC + +## Objective + +Create a PersistentVolumeClaim and mount it in a Pod. + +## Scenario + +You need to provide persistent storage to an application Pod so that +data survives container restarts. + +## Learning Outcomes + +- Create a PersistentVolumeClaim +- Mount a PVC as a volume in a Pod +- Verify data persists across Pod restarts +- Understand the difference between static and dynamic provisioning + +## Prerequisites + +- A running Kubernetes cluster with a default StorageClass +- `kubectl` configured and working + +> **Note:** Dynamic provisioning requires a default StorageClass. +> Kind provides a `standard` StorageClass by default. +> If you are using a different cluster, check: `kubectl get storageclass` + +## Files + +| File | Description | +|---------------|------------------------------------| +| namespace.yaml | Creates the lab namespace | +| pvc.yaml | PersistentVolumeClaim manifest | +| pod.yaml | Pod that mounts the PVC | +| validate.sh | Validates the lab | +| cleanup.sh | Removes all lab resources | + +## Instructions + +```bash +kubectl apply -f namespace.yaml +kubectl apply -f pvc.yaml +kubectl apply -f pod.yaml +bash validate.sh +``` + +## Validation + +```bash +kubectl get pvc -n k8s-storage +kubectl get pod storage-writer -n k8s-storage +kubectl exec storage-writer -n k8s-storage -- cat /data/hello.txt +``` + +## Expected Result + +- PVC is bound +- Pod is running +- Data written to `/data/hello.txt` persists in the volume + +## Troubleshooting + +- If the PVC is `Pending`, there may be no available StorageClass or PV + - Check: `kubectl get storageclass` + - Check: `kubectl describe pvc data-claim -n k8s-storage` +- If the Pod is `Pending`, the PVC may not be bound yet + +## Cleanup + +```bash +bash cleanup.sh +``` + +> **Note:** Deleting the namespace will also delete the PVC and any associated PV +> that was dynamically provisioned. Data will be lost. + +## Additional Challenges + +1. Write data to the volume, delete the Pod, recreate it, and verify data persists +2. Create a PVC with `ReadWriteMany` access mode (requires a supported StorageClass) +3. Create a PV manually and bind a PVC to it statically + +## Key Commands + +```bash +kubectl get pvc -n +kubectl get pv +kubectl describe pvc -n +kubectl get storageclass +``` + +## Lessons Learned + +- Dynamic provisioning creates a PV automatically when a PVC is created +- The StorageClass determines the type and behaviour of the PV +- PVCs use AccessModes: `ReadWriteOnce`, `ReadWriteMany`, `ReadOnlyMany` +- Data survives Pod restarts but is lost if the PVC is deleted diff --git a/kubernetes-labs/storage/persistent-volume-claims/basic-pvc/cleanup.sh b/kubernetes-labs/storage/persistent-volume-claims/basic-pvc/cleanup.sh new file mode 100755 index 0000000..75d02f9 --- /dev/null +++ b/kubernetes-labs/storage/persistent-volume-claims/basic-pvc/cleanup.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -euo pipefail + +NAMESPACE="k8s-storage" + +echo "==> Cleaning up basic-pvc lab..." +echo "WARNING: This will delete the PVC and any dynamically provisioned PV." +kubectl delete namespace "${NAMESPACE}" --ignore-not-found=true +echo "==> Cleanup complete." diff --git a/kubernetes-labs/storage/persistent-volume-claims/basic-pvc/namespace.yaml b/kubernetes-labs/storage/persistent-volume-claims/basic-pvc/namespace.yaml new file mode 100644 index 0000000..1f88a89 --- /dev/null +++ b/kubernetes-labs/storage/persistent-volume-claims/basic-pvc/namespace.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: k8s-storage + labels: + purpose: kubernetes-lab diff --git a/kubernetes-labs/storage/persistent-volume-claims/basic-pvc/pod.yaml b/kubernetes-labs/storage/persistent-volume-claims/basic-pvc/pod.yaml new file mode 100644 index 0000000..c3c1039 --- /dev/null +++ b/kubernetes-labs/storage/persistent-volume-claims/basic-pvc/pod.yaml @@ -0,0 +1,43 @@ +--- +apiVersion: v1 +kind: Pod +metadata: + name: storage-writer + namespace: k8s-storage + labels: + app: storage-writer +spec: + containers: + - name: writer + image: busybox:1.36 + command: + - /bin/sh + - -c + - | + echo "Writing data to persistent volume..." + echo "Hello from PVC at $(date)" > /data/hello.txt + echo "Data written successfully." + cat /data/hello.txt + sleep 3600 + volumeMounts: + - name: data-volume + mountPath: /data + resources: + requests: + cpu: 10m + memory: 16Mi + limits: + cpu: 50m + memory: 32Mi + securityContext: + allowPrivilegeEscalation: false + runAsNonRoot: true + runAsUser: 1000 + capabilities: + drop: + - ALL + volumes: + - name: data-volume + persistentVolumeClaim: + claimName: data-claim + restartPolicy: Always diff --git a/kubernetes-labs/storage/persistent-volume-claims/basic-pvc/pvc.yaml b/kubernetes-labs/storage/persistent-volume-claims/basic-pvc/pvc.yaml new file mode 100644 index 0000000..806235c --- /dev/null +++ b/kubernetes-labs/storage/persistent-volume-claims/basic-pvc/pvc.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: data-claim + namespace: k8s-storage +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 100Mi diff --git a/kubernetes-labs/storage/persistent-volume-claims/basic-pvc/validate.sh b/kubernetes-labs/storage/persistent-volume-claims/basic-pvc/validate.sh new file mode 100755 index 0000000..cd8aa3e --- /dev/null +++ b/kubernetes-labs/storage/persistent-volume-claims/basic-pvc/validate.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail + +NAMESPACE="k8s-storage" +POD_NAME="storage-writer" +PVC_NAME="data-claim" + +echo "==> Validating basic-pvc lab..." + +echo "--- Checking PVC status..." +PVC_STATUS=$(kubectl get pvc "${PVC_NAME}" --namespace="${NAMESPACE}" -o jsonpath='{.status.phase}') + +if [ "${PVC_STATUS}" != "Bound" ]; then + echo "ERROR: PVC '${PVC_NAME}' is not bound (status: ${PVC_STATUS})" + echo " Check: kubectl describe pvc ${PVC_NAME} -n ${NAMESPACE}" + exit 1 +fi +echo " PVC is bound." + +echo "--- Waiting for Pod to be ready..." +kubectl wait pod "${POD_NAME}" --namespace="${NAMESPACE}" --for=condition=Ready --timeout=60s + +echo "--- Verifying data in volume..." +OUTPUT=$(kubectl exec "${POD_NAME}" --namespace="${NAMESPACE}" -- cat /data/hello.txt 2>/dev/null || echo "") + +if echo "${OUTPUT}" | grep -q "Hello"; then + echo " Data found in persistent volume." +else + echo "WARNING: Could not verify data in volume." +fi + +echo "" +echo "==> Validation passed." +kubectl get pvc "${PVC_NAME}" --namespace="${NAMESPACE}" +kubectl get pod "${POD_NAME}" --namespace="${NAMESPACE}" diff --git a/kubernetes-labs/storage/persistent-volumes/README.md b/kubernetes-labs/storage/persistent-volumes/README.md new file mode 100644 index 0000000..07b463c --- /dev/null +++ b/kubernetes-labs/storage/persistent-volumes/README.md @@ -0,0 +1,5 @@ +# persistent-volumes + +Labs for persistent-volumes (part of storage). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/storage/storage-classes/README.md b/kubernetes-labs/storage/storage-classes/README.md new file mode 100644 index 0000000..41df64f --- /dev/null +++ b/kubernetes-labs/storage/storage-classes/README.md @@ -0,0 +1,5 @@ +# storage-classes + +Labs for storage-classes (part of storage). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/storage/volumes/README.md b/kubernetes-labs/storage/volumes/README.md new file mode 100644 index 0000000..4739b07 --- /dev/null +++ b/kubernetes-labs/storage/volumes/README.md @@ -0,0 +1,5 @@ +# volumes + +Labs for volumes (part of storage). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/troubleshooting/README.md b/kubernetes-labs/troubleshooting/README.md new file mode 100644 index 0000000..83a6340 --- /dev/null +++ b/kubernetes-labs/troubleshooting/README.md @@ -0,0 +1,28 @@ +# Troubleshooting + +Labs and runbooks for debugging common Kubernetes issues. + +## Common Debugging Steps + +```bash +# Pod not starting +kubectl describe pod -n +kubectl logs -n +kubectl get events -n --sort-by='.lastTimestamp' + +# Service not reachable +kubectl get endpoints -n +kubectl get pods -l -n + +# Node issues +kubectl describe node +kubectl get node -o yaml +``` + +## Planned Labs + +- CrashLoopBackOff debugging +- Pending Pod debugging +- Service connectivity issues +- Node not ready scenarios +- Resource pressure (CPU/memory) diff --git a/kubernetes-labs/workloads/cron-jobs/scheduled-task/README.md b/kubernetes-labs/workloads/cron-jobs/scheduled-task/README.md new file mode 100644 index 0000000..c423594 --- /dev/null +++ b/kubernetes-labs/workloads/cron-jobs/scheduled-task/README.md @@ -0,0 +1,106 @@ +# Lab: Scheduled Task (Job and CronJob) + +## Objective + +Create a Kubernetes Job and a CronJob to run scheduled batch tasks. + +## Scenario + +You need to run a one-time data processing job, and then schedule it to +run automatically every hour using a CronJob. + +## Learning Outcomes + +- Understand the difference between a Job and a CronJob +- Configure restart policies for batch workloads +- Understand concurrency policies for CronJobs +- Configure successful and failed job history limits +- Monitor job completion and review logs + +## Prerequisites + +- A running Kubernetes cluster +- `kubectl` configured and working + +## Files + +| File | Description | +|-----------------|----------------------------------| +| namespace.yaml | Creates the lab namespace | +| job.yaml | One-time Job manifest | +| cronjob.yaml | CronJob manifest (hourly) | +| validate.sh | Validates the lab | +| cleanup.sh | Removes all lab resources | + +## Instructions + +```bash +kubectl apply -f namespace.yaml +kubectl apply -f job.yaml +kubectl apply -f cronjob.yaml +bash validate.sh +``` + +## Restart Policies + +Jobs support the following restart policies: + +- `Never` — creates a new Pod on failure (generates many Pods on repeated failures) +- `OnFailure` — restarts the container in the same Pod on failure + +Use `Never` when you want separate failure logs. Use `OnFailure` when container state is not important. + +## Concurrency Policies + +CronJobs support three concurrency policies: + +- `Allow` (default) — multiple job instances can run concurrently +- `Forbid` — skips the new run if a previous job is still running +- `Replace` — cancels the current job and starts a new one + +Choose `Forbid` when jobs must not overlap and `Replace` when a new run should always supersede the old one. + +## History Limits + +- `successfulJobsHistoryLimit` — number of completed jobs to retain (default: 3) +- `failedJobsHistoryLimit` — number of failed jobs to retain (default: 1) + +Set these to control how many completed Pods remain for log inspection. + +## Validation + +```bash +kubectl get jobs -n k8s-batch +kubectl get cronjobs -n k8s-batch +kubectl get pods -n k8s-batch +kubectl logs -l job-name=data-processor -n k8s-batch +``` + +## Cleanup + +```bash +bash cleanup.sh +``` + +## Additional Challenges + +1. Trigger a CronJob manually: `kubectl create job --from=cronjob/scheduled-processor manual-run -n k8s-batch` +2. Suspend a CronJob: `kubectl patch cronjob scheduled-processor -p '{"spec":{"suspend":true}}' -n k8s-batch` +3. Configure `activeDeadlineSeconds` to set a maximum job duration + +## Key Commands + +```bash +kubectl get jobs -n +kubectl get cronjobs -n +kubectl logs -l job-name= -n +kubectl create job --from=cronjob/ -n +kubectl delete job -n +``` + +## Lessons Learned + +- Jobs create one or more Pods to complete a task; they do not use Deployments +- CronJobs create Jobs on a schedule using standard cron syntax +- Always set appropriate history limits to avoid stale Pod accumulation +- Use `activeDeadlineSeconds` to prevent runaway jobs diff --git a/kubernetes-labs/workloads/cron-jobs/scheduled-task/cleanup.sh b/kubernetes-labs/workloads/cron-jobs/scheduled-task/cleanup.sh new file mode 100755 index 0000000..d192ce0 --- /dev/null +++ b/kubernetes-labs/workloads/cron-jobs/scheduled-task/cleanup.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail + +NAMESPACE="k8s-batch" + +echo "==> Cleaning up scheduled-task lab..." +kubectl delete namespace "${NAMESPACE}" --ignore-not-found=true +echo "==> Cleanup complete." diff --git a/kubernetes-labs/workloads/cron-jobs/scheduled-task/cronjob.yaml b/kubernetes-labs/workloads/cron-jobs/scheduled-task/cronjob.yaml new file mode 100644 index 0000000..a8f50cc --- /dev/null +++ b/kubernetes-labs/workloads/cron-jobs/scheduled-task/cronjob.yaml @@ -0,0 +1,48 @@ +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: scheduled-processor + namespace: k8s-batch + labels: + app: scheduled-processor +spec: + schedule: "0 * * * *" + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 1 + startingDeadlineSeconds: 60 + jobTemplate: + spec: + activeDeadlineSeconds: 120 + template: + metadata: + labels: + app: scheduled-processor + spec: + containers: + - name: processor + image: busybox:1.36 + command: + - /bin/sh + - -c + - | + echo "Scheduled processing run at $(date)" + sleep 5 + echo "Done." + resources: + requests: + cpu: 50m + memory: 32Mi + limits: + cpu: 100m + memory: 64Mi + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + capabilities: + drop: + - ALL + restartPolicy: OnFailure diff --git a/kubernetes-labs/workloads/cron-jobs/scheduled-task/job.yaml b/kubernetes-labs/workloads/cron-jobs/scheduled-task/job.yaml new file mode 100644 index 0000000..7b9f668 --- /dev/null +++ b/kubernetes-labs/workloads/cron-jobs/scheduled-task/job.yaml @@ -0,0 +1,45 @@ +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: data-processor + namespace: k8s-batch + labels: + app: data-processor +spec: + completions: 1 + parallelism: 1 + backoffLimit: 3 + activeDeadlineSeconds: 120 + template: + metadata: + labels: + app: data-processor + spec: + containers: + - name: processor + image: busybox:1.36 + command: + - /bin/sh + - -c + - | + echo "Starting data processing job..." + echo "Processing batch at $(date)" + sleep 5 + echo "Batch processing complete." + resources: + requests: + cpu: 50m + memory: 32Mi + limits: + cpu: 100m + memory: 64Mi + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + capabilities: + drop: + - ALL + restartPolicy: Never diff --git a/kubernetes-labs/workloads/cron-jobs/scheduled-task/namespace.yaml b/kubernetes-labs/workloads/cron-jobs/scheduled-task/namespace.yaml new file mode 100644 index 0000000..c0780fb --- /dev/null +++ b/kubernetes-labs/workloads/cron-jobs/scheduled-task/namespace.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: k8s-batch + labels: + purpose: kubernetes-lab diff --git a/kubernetes-labs/workloads/cron-jobs/scheduled-task/validate.sh b/kubernetes-labs/workloads/cron-jobs/scheduled-task/validate.sh new file mode 100755 index 0000000..dae2eeb --- /dev/null +++ b/kubernetes-labs/workloads/cron-jobs/scheduled-task/validate.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail + +NAMESPACE="k8s-batch" +JOB_NAME="data-processor" +CRONJOB_NAME="scheduled-processor" + +echo "==> Validating scheduled-task lab..." + +echo "--- Waiting for Job to complete..." +kubectl wait job/"${JOB_NAME}" --namespace="${NAMESPACE}" --for=condition=complete --timeout=120s + +echo "--- Checking CronJob exists..." +kubectl get cronjob "${CRONJOB_NAME}" --namespace="${NAMESPACE}" > /dev/null + +echo "--- Job logs:" +kubectl logs -l job-name="${JOB_NAME}" --namespace="${NAMESPACE}" || true + +echo "" +echo "==> Validation passed." +kubectl get jobs --namespace="${NAMESPACE}" +kubectl get cronjobs --namespace="${NAMESPACE}" diff --git a/kubernetes-labs/workloads/daemon-sets/README.md b/kubernetes-labs/workloads/daemon-sets/README.md new file mode 100644 index 0000000..6baaf1c --- /dev/null +++ b/kubernetes-labs/workloads/daemon-sets/README.md @@ -0,0 +1,5 @@ +# daemon-sets + +Labs for daemon-sets (part of workloads). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/workloads/deployments/web-deployment/README.md b/kubernetes-labs/workloads/deployments/web-deployment/README.md new file mode 100644 index 0000000..d87e80b --- /dev/null +++ b/kubernetes-labs/workloads/deployments/web-deployment/README.md @@ -0,0 +1,112 @@ +# Lab: Web Deployment + +## Objective + +Create a Deployment with three replicas of a web server, expose it with a ClusterIP Service, +and practise rolling updates and rollbacks. + +## Scenario + +Deploy a lightweight nginx web server with health checks, expose it via a Service, +and practice scaling, updating, and rolling back the Deployment. + +## Learning Outcomes + +- Create a Deployment with multiple replicas +- Configure liveness and readiness probes +- Expose a Deployment using a ClusterIP Service +- Validate rollout status and endpoints +- Scale a Deployment +- Update the image and trigger a rolling update +- Roll back to a previous revision + +## Prerequisites + +- A running Kubernetes cluster +- `kubectl` configured and working + +## Files + +| File | Description | +|------------------|-------------------------------| +| namespace.yaml | Creates the lab namespace | +| deployment.yaml | nginx Deployment | +| service.yaml | ClusterIP Service | +| validate.sh | Validates the lab | +| cleanup.sh | Removes all lab resources | + +## Instructions + +```bash +kubectl apply -f namespace.yaml +kubectl apply -f deployment.yaml +kubectl apply -f service.yaml +bash validate.sh +``` + +## Validation + +```bash +kubectl rollout status deployment/web-server -n k8s-workloads +kubectl get pods -l app=web-server -n k8s-workloads +kubectl get service web-server-svc -n k8s-workloads +kubectl get endpoints web-server-svc -n k8s-workloads +``` + +## Scaling + +```bash +kubectl scale deployment web-server --replicas=5 -n k8s-workloads +kubectl get pods -n k8s-workloads -w +``` + +## Updating the Image + +```bash +kubectl set image deployment/web-server web=nginx:1.27-alpine -n k8s-workloads +kubectl rollout status deployment/web-server -n k8s-workloads +``` + +## Rolling Back + +```bash +kubectl rollout history deployment/web-server -n k8s-workloads +kubectl rollout undo deployment/web-server -n k8s-workloads +kubectl rollout status deployment/web-server -n k8s-workloads +``` + +## Expected Result + +- 3 replicas running and ready +- Service has 3 endpoints +- Rolling update completes successfully +- Rollback restores the previous revision + +## Troubleshooting + +- If Pods are `Pending`: check node resources or `kubectl describe pod` +- If probes fail: verify the path and port in the probe spec +- If endpoints are empty: verify the Service selector matches Pod labels + +## Cleanup + +```bash +bash cleanup.sh +``` + +## Key Commands + +```bash +kubectl rollout status deployment/ -n +kubectl rollout history deployment/ -n +kubectl rollout undo deployment/ -n +kubectl scale deployment/ --replicas= -n +kubectl set image deployment/ = -n +``` + +## Lessons Learned + +- Deployments manage ReplicaSets and enable rolling updates +- Probes ensure traffic is only sent to healthy, ready Pods +- `rollout history` shows available revisions for rollback +- Scaling is immediate; rolling updates are controlled by strategy settings diff --git a/kubernetes-labs/workloads/deployments/web-deployment/cleanup.sh b/kubernetes-labs/workloads/deployments/web-deployment/cleanup.sh new file mode 100755 index 0000000..5d854df --- /dev/null +++ b/kubernetes-labs/workloads/deployments/web-deployment/cleanup.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail + +NAMESPACE="k8s-workloads" + +echo "==> Cleaning up web-deployment lab..." +kubectl delete namespace "${NAMESPACE}" --ignore-not-found=true +echo "==> Cleanup complete." diff --git a/kubernetes-labs/workloads/deployments/web-deployment/deployment.yaml b/kubernetes-labs/workloads/deployments/web-deployment/deployment.yaml new file mode 100644 index 0000000..a1e31cf --- /dev/null +++ b/kubernetes-labs/workloads/deployments/web-deployment/deployment.yaml @@ -0,0 +1,56 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: web-server + namespace: k8s-workloads + labels: + app: web-server +spec: + replicas: 3 + selector: + matchLabels: + app: web-server + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + template: + metadata: + labels: + app: web-server + spec: + containers: + - name: web + image: nginx:1.27-alpine + ports: + - containerPort: 80 + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 100m + memory: 128Mi + livenessProbe: + httpGet: + path: / + port: 80 + initialDelaySeconds: 5 + periodSeconds: 10 + failureThreshold: 3 + readinessProbe: + httpGet: + path: / + port: 80 + initialDelaySeconds: 3 + periodSeconds: 5 + failureThreshold: 3 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE diff --git a/kubernetes-labs/workloads/deployments/web-deployment/namespace.yaml b/kubernetes-labs/workloads/deployments/web-deployment/namespace.yaml new file mode 100644 index 0000000..d5126c6 --- /dev/null +++ b/kubernetes-labs/workloads/deployments/web-deployment/namespace.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: k8s-workloads + labels: + purpose: kubernetes-lab diff --git a/kubernetes-labs/workloads/deployments/web-deployment/service.yaml b/kubernetes-labs/workloads/deployments/web-deployment/service.yaml new file mode 100644 index 0000000..1887c72 --- /dev/null +++ b/kubernetes-labs/workloads/deployments/web-deployment/service.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: web-server-svc + namespace: k8s-workloads + labels: + app: web-server +spec: + selector: + app: web-server + ports: + - protocol: TCP + port: 80 + targetPort: 80 + type: ClusterIP diff --git a/kubernetes-labs/workloads/deployments/web-deployment/validate.sh b/kubernetes-labs/workloads/deployments/web-deployment/validate.sh new file mode 100755 index 0000000..c148726 --- /dev/null +++ b/kubernetes-labs/workloads/deployments/web-deployment/validate.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -euo pipefail + +NAMESPACE="k8s-workloads" +DEPLOYMENT="web-server" +SERVICE="web-server-svc" + +echo "==> Validating web-deployment lab..." + +echo "--- Waiting for rollout..." +kubectl rollout status deployment/"${DEPLOYMENT}" --namespace="${NAMESPACE}" --timeout=120s + +echo "--- Checking replica count..." +READY=$(kubectl get deployment "${DEPLOYMENT}" --namespace="${NAMESPACE}" -o jsonpath='{.status.readyReplicas}') + +if [ "${READY}" -lt 3 ]; then + echo "ERROR: Expected 3 ready replicas, got ${READY}" + exit 1 +fi + +echo "--- Checking Service..." +kubectl get service "${SERVICE}" --namespace="${NAMESPACE}" > /dev/null + +echo "--- Checking endpoints..." +ENDPOINTS=$(kubectl get endpoints "${SERVICE}" --namespace="${NAMESPACE}" -o jsonpath='{.subsets[*].addresses}') + +if [ -z "${ENDPOINTS}" ]; then + echo "ERROR: Service '${SERVICE}' has no endpoints." + exit 1 +fi + +echo "" +echo "==> Validation passed." +kubectl get deployment "${DEPLOYMENT}" --namespace="${NAMESPACE}" +kubectl get service "${SERVICE}" --namespace="${NAMESPACE}" +kubectl get endpoints "${SERVICE}" --namespace="${NAMESPACE}" diff --git a/kubernetes-labs/workloads/jobs/README.md b/kubernetes-labs/workloads/jobs/README.md new file mode 100644 index 0000000..565b1c4 --- /dev/null +++ b/kubernetes-labs/workloads/jobs/README.md @@ -0,0 +1,5 @@ +# jobs + +Labs for jobs (part of workloads). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/workloads/pods/README.md b/kubernetes-labs/workloads/pods/README.md new file mode 100644 index 0000000..7ee5e24 --- /dev/null +++ b/kubernetes-labs/workloads/pods/README.md @@ -0,0 +1,5 @@ +# pods + +Labs for pods (part of workloads). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/workloads/replica-sets/README.md b/kubernetes-labs/workloads/replica-sets/README.md new file mode 100644 index 0000000..5274933 --- /dev/null +++ b/kubernetes-labs/workloads/replica-sets/README.md @@ -0,0 +1,5 @@ +# replica-sets + +Labs for replica-sets (part of workloads). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/kubernetes-labs/workloads/stateful-sets/README.md b/kubernetes-labs/workloads/stateful-sets/README.md new file mode 100644 index 0000000..8fbf6a9 --- /dev/null +++ b/kubernetes-labs/workloads/stateful-sets/README.md @@ -0,0 +1,5 @@ +# stateful-sets + +Labs for stateful-sets (part of workloads). + +> Labs will be added here. See the parent [README](../README.md) for context. diff --git a/linux-labs/README.md b/linux-labs/README.md new file mode 100644 index 0000000..ec9f814 --- /dev/null +++ b/linux-labs/README.md @@ -0,0 +1,121 @@ +# Linux Labs + +Hands-on exercises for Linux administration skills relevant to cloud-native environments. + +## Learning Roadmap + +| Area | Topics | +|-----------------------------|-------------------------------------------------| +| [shell/](shell/) | Shell navigation, scripting, text processing | +| [files-and-permissions/](files-and-permissions/) | Filesystem, permissions, ownership | +| [processes/](processes/) | Processes, signals, job control | +| [systemd/](systemd/) | Services, units, journal | +| [networking/](networking/) | IP, DNS, ports, HTTP, troubleshooting | +| [storage/](storage/) | Filesystems, mounts, LVM, disk tools | +| [users-and-groups/](users-and-groups/) | User management, sudo, PAM | +| [package-management/](package-management/) | apt, yum, dnf, snap | +| [security/](security/) | AppArmor, SELinux, capabilities, firewall | +| [troubleshooting/](troubleshooting/) | Debugging techniques and tools | + +## Why Linux for Cloud-Native? + +Kubernetes runs on Linux. Understanding the OS foundation helps with: + +- Debugging container and node issues +- Understanding namespaces and cgroups (the foundation of containers) +- Configuring networking, storage and security +- Reading and writing effective shell scripts +- Understanding systemd services in nodes + +## Topics + +### Shell Navigation + +```bash +pwd, ls, cd, find, grep, awk, sed, cut, tr, sort, uniq, wc +cat, head, tail, less, more +cp, mv, rm, mkdir, touch, ln +``` + +### Text Processing + +```bash +grep -r "pattern" . +awk '{print $1, $3}' file.txt +sed 's/old/new/g' file.txt +cut -d: -f1 /etc/passwd +sort -k2 -n file.txt | uniq -c +``` + +### Permissions + +```bash +ls -la +chmod 755 script.sh +chmod +x script.sh +chown user:group file +umask 022 +``` + +### Processes + +```bash +ps aux +top / htop +kill -9 PID +pkill process-name +pgrep process-name +``` + +### Services (systemd) + +```bash +systemctl status nginx +systemctl start/stop/restart nginx +systemctl enable/disable nginx +journalctl -u nginx -f +journalctl --since "1 hour ago" +``` + +### Networking + +```bash +ip addr show +ip route show +ss -tlnp +netstat -tlnp +curl -v http://host +dig hostname +nslookup hostname +traceroute hostname +``` + +### Storage + +```bash +df -h +du -sh /path +lsblk +fdisk -l +mount /dev/sdb1 /mnt/data +``` + +### Users and Groups + +```bash +useradd -m username +usermod -aG sudo username +passwd username +groups username +id username +``` + +### Troubleshooting + +```bash +dmesg | tail +journalctl -xe +strace -p PID +lsof -p PID +tcpdump -i eth0 port 80 +``` diff --git a/linux-labs/files-and-permissions/README.md b/linux-labs/files-and-permissions/README.md new file mode 100644 index 0000000..f167435 --- /dev/null +++ b/linux-labs/files-and-permissions/README.md @@ -0,0 +1,5 @@ +# files-and-permissions + +Labs for files-and-permissions. + +> Labs will be added here. See [../README.md](../README.md) for context. diff --git a/linux-labs/networking/README.md b/linux-labs/networking/README.md new file mode 100644 index 0000000..89d34c2 --- /dev/null +++ b/linux-labs/networking/README.md @@ -0,0 +1,5 @@ +# networking + +Labs for networking. + +> Labs will be added here. See [../README.md](../README.md) for context. diff --git a/linux-labs/package-management/README.md b/linux-labs/package-management/README.md new file mode 100644 index 0000000..8291647 --- /dev/null +++ b/linux-labs/package-management/README.md @@ -0,0 +1,5 @@ +# package-management + +Labs for package-management. + +> Labs will be added here. See [../README.md](../README.md) for context. diff --git a/linux-labs/processes/README.md b/linux-labs/processes/README.md new file mode 100644 index 0000000..93ddd72 --- /dev/null +++ b/linux-labs/processes/README.md @@ -0,0 +1,5 @@ +# processes + +Labs for processes. + +> Labs will be added here. See [../README.md](../README.md) for context. diff --git a/linux-labs/security/README.md b/linux-labs/security/README.md new file mode 100644 index 0000000..cc792fe --- /dev/null +++ b/linux-labs/security/README.md @@ -0,0 +1,5 @@ +# security + +Labs for security. + +> Labs will be added here. See [../README.md](../README.md) for context. diff --git a/linux-labs/shell/README.md b/linux-labs/shell/README.md new file mode 100644 index 0000000..7a47cf2 --- /dev/null +++ b/linux-labs/shell/README.md @@ -0,0 +1,5 @@ +# shell + +Labs for shell. + +> Labs will be added here. See [../README.md](../README.md) for context. diff --git a/linux-labs/storage/README.md b/linux-labs/storage/README.md new file mode 100644 index 0000000..ea770ad --- /dev/null +++ b/linux-labs/storage/README.md @@ -0,0 +1,5 @@ +# storage + +Labs for storage. + +> Labs will be added here. See [../README.md](../README.md) for context. diff --git a/linux-labs/systemd/README.md b/linux-labs/systemd/README.md new file mode 100644 index 0000000..81eaff9 --- /dev/null +++ b/linux-labs/systemd/README.md @@ -0,0 +1,5 @@ +# systemd + +Labs for systemd. + +> Labs will be added here. See [../README.md](../README.md) for context. diff --git a/linux-labs/troubleshooting/README.md b/linux-labs/troubleshooting/README.md new file mode 100644 index 0000000..c4d712e --- /dev/null +++ b/linux-labs/troubleshooting/README.md @@ -0,0 +1,5 @@ +# troubleshooting + +Labs for troubleshooting. + +> Labs will be added here. See [../README.md](../README.md) for context. diff --git a/linux-labs/users-and-groups/README.md b/linux-labs/users-and-groups/README.md new file mode 100644 index 0000000..a716e2e --- /dev/null +++ b/linux-labs/users-and-groups/README.md @@ -0,0 +1,5 @@ +# users-and-groups + +Labs for users-and-groups. + +> Labs will be added here. See [../README.md](../README.md) for context. diff --git a/networking-labs/README.md b/networking-labs/README.md new file mode 100644 index 0000000..db3bec7 --- /dev/null +++ b/networking-labs/README.md @@ -0,0 +1,129 @@ +# Networking Labs + +Hands-on exercises for networking concepts relevant to cloud-native environments. + +## Learning Roadmap + +| Area | Topics | +|--------------------------------------|---------------------------------------------| +| [tcp-ip/](tcp-ip/) | TCP/IP, OSI model, ports, sockets | +| [dns/](dns/) | DNS resolution, CoreDNS, Kubernetes DNS | +| [load-balancing/](load-balancing/) | Layer 4 and Layer 7 load balancing | +| [ingress/](ingress/) | Kubernetes Ingress, Ingress controllers | +| [gateway-api/](gateway-api/) | Gateway API, HTTPRoute, GatewayClass | +| [cni/](cni/) | CNI plugins, Calico, Cilium, Flannel | +| [service-mesh/](service-mesh/) | Istio, Linkerd, Envoy | +| [troubleshooting/](troubleshooting/) | Debugging network issues | + +## Networking Concepts + +### TCP/IP + +- IP addressing (IPv4 and IPv6) +- Subnets and CIDR notation +- TCP handshake and connection states +- UDP + +### Ports and Sockets + +```bash +# List listening ports +ss -tlnp # TCP listening +ss -ulnp # UDP listening +netstat -tlnp # Legacy alternative +``` + +### DNS + +```bash +# DNS lookup +dig example.com +nslookup example.com +dig @8.8.8.8 example.com + +# Kubernetes DNS +dig web-svc.default.svc.cluster.local +nslookup web-svc.mynamespace.svc.cluster.local +``` + +### HTTP and HTTPS + +```bash +curl -v http://host +curl -k https://host # Skip TLS verification +curl -H "Host: example.com" http://ip # Custom Host header +curl -I http://host # Headers only +wget -qO- http://host # Simple GET +``` + +### Proxies + +```bash +export HTTP_PROXY=http://proxy:3128 +export HTTPS_PROXY=http://proxy:3128 +export NO_PROXY=localhost,127.0.0.1,10.0.0.0/8 +``` + +### Load Balancers + +- Layer 4 (TCP/UDP): routes based on IP and port +- Layer 7 (HTTP): routes based on URL path, headers, etc. + +### Kubernetes Services + +```bash +kubectl get service -n +kubectl get endpoints -n +kubectl describe service -n +``` + +### Ingress + +```bash +kubectl get ingress -n +kubectl describe ingress -n +``` + +### Gateway API + +```bash +kubectl get gateway -n +kubectl get httproute -n +``` + +### CNI + +The Container Network Interface (CNI) provides networking for containers: + +- **Flannel**: simple overlay network +- **Calico**: network policy enforcement, BGP +- **Cilium**: eBPF-based, advanced observability and security +- **Weave Net**: peer-to-peer network + +### Service Mesh + +A service mesh provides: +- Mutual TLS (mTLS) between services +- Traffic management (retries, circuit breaking) +- Observability (distributed tracing, metrics) + +Popular implementations: Istio, Linkerd, Consul Connect + +### Troubleshooting Tools + +```bash +ip addr # Network interfaces +ip route # Routing table +ss -tlnp # Listening ports +tcpdump -i eth0 port 80 # Capture traffic +curl -v http://host # HTTP test +dig hostname # DNS lookup +traceroute hostname # Route tracing +kubectl get endpoints # Service endpoints +kubectl exec -- nslookup # DNS from inside Pod +kubectl port-forward # Port forward to local machine +``` + +> **Note:** Not all tools are available in every container image. +> Use a debug Pod with a full toolset when needed: +> `kubectl run debug --image=nicolaka/netshoot --rm -it --restart=Never -- bash` diff --git a/networking-labs/cni/README.md b/networking-labs/cni/README.md new file mode 100644 index 0000000..d2d7a76 --- /dev/null +++ b/networking-labs/cni/README.md @@ -0,0 +1,5 @@ +# cni + +Labs for cni. + +> Labs will be added here. See [../README.md](../README.md) for context. diff --git a/networking-labs/dns/README.md b/networking-labs/dns/README.md new file mode 100644 index 0000000..c84bebe --- /dev/null +++ b/networking-labs/dns/README.md @@ -0,0 +1,5 @@ +# dns + +Labs for dns. + +> Labs will be added here. See [../README.md](../README.md) for context. diff --git a/networking-labs/gateway-api/README.md b/networking-labs/gateway-api/README.md new file mode 100644 index 0000000..b1b4fc9 --- /dev/null +++ b/networking-labs/gateway-api/README.md @@ -0,0 +1,5 @@ +# gateway-api + +Labs for gateway-api. + +> Labs will be added here. See [../README.md](../README.md) for context. diff --git a/networking-labs/ingress/README.md b/networking-labs/ingress/README.md new file mode 100644 index 0000000..a6880e5 --- /dev/null +++ b/networking-labs/ingress/README.md @@ -0,0 +1,5 @@ +# ingress + +Labs for ingress. + +> Labs will be added here. See [../README.md](../README.md) for context. diff --git a/networking-labs/load-balancing/README.md b/networking-labs/load-balancing/README.md new file mode 100644 index 0000000..1400864 --- /dev/null +++ b/networking-labs/load-balancing/README.md @@ -0,0 +1,5 @@ +# load-balancing + +Labs for load-balancing. + +> Labs will be added here. See [../README.md](../README.md) for context. diff --git a/networking-labs/service-mesh/README.md b/networking-labs/service-mesh/README.md new file mode 100644 index 0000000..e5b47b1 --- /dev/null +++ b/networking-labs/service-mesh/README.md @@ -0,0 +1,5 @@ +# service-mesh + +Labs for service-mesh. + +> Labs will be added here. See [../README.md](../README.md) for context. diff --git a/networking-labs/tcp-ip/README.md b/networking-labs/tcp-ip/README.md new file mode 100644 index 0000000..243aaad --- /dev/null +++ b/networking-labs/tcp-ip/README.md @@ -0,0 +1,5 @@ +# tcp-ip + +Labs for tcp-ip. + +> Labs will be added here. See [../README.md](../README.md) for context. diff --git a/networking-labs/troubleshooting/README.md b/networking-labs/troubleshooting/README.md new file mode 100644 index 0000000..c4d712e --- /dev/null +++ b/networking-labs/troubleshooting/README.md @@ -0,0 +1,5 @@ +# troubleshooting + +Labs for troubleshooting. + +> Labs will be added here. See [../README.md](../README.md) for context. diff --git a/observability-labs/README.md b/observability-labs/README.md new file mode 100644 index 0000000..ca847fd --- /dev/null +++ b/observability-labs/README.md @@ -0,0 +1,127 @@ +# Observability Labs + +Hands-on exercises for observability in cloud-native environments. + +## Overview + +Observability encompasses three pillars: **metrics**, **logs**, and **traces**. +In modern cloud-native systems, a fourth concern — **alerting** — connects +these signals to actionable responses. + +## Learning Roadmap + +| Area | Technology | +|------------------------------------------|-------------------------| +| [prometheus/](prometheus/) | Metrics collection | +| [grafana/](grafana/) | Metrics visualisation | +| [opentelemetry/](opentelemetry/) | Traces, metrics, logs | +| [logging/](logging/) | Log aggregation | +| [metrics/](metrics/) | Application metrics | +| [tracing/](tracing/) | Distributed tracing | +| [alerting/](alerting/) | Alerting rules | + +## Concepts + +### Liveness vs Readiness vs Startup Probes + +These are Kubernetes health checks, not application-level observability: + +- **Liveness probe**: Is the container still alive? If it fails, the container restarts. +- **Readiness probe**: Is the container ready to serve traffic? If it fails, it is removed from Service endpoints. +- **Startup probe**: Has the container finished starting? Protects slow-starting apps from premature liveness failures. + +```yaml +livenessProbe: + httpGet: + path: /healthz + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 10 + +readinessProbe: + httpGet: + path: /ready + port: 8080 + initialDelaySeconds: 3 + periodSeconds: 5 + +startupProbe: + httpGet: + path: /healthz + port: 8080 + failureThreshold: 30 + periodSeconds: 2 +``` + +### Application Metrics + +Application metrics are custom counters, gauges, and histograms exposed by the application. +Common format: Prometheus exposition format (text-based). + +Example endpoint: `GET /metrics` + +```text +# HELP http_requests_total Total number of HTTP requests +# TYPE http_requests_total counter +http_requests_total{method="GET",status="200"} 1234 +``` + +### Infrastructure Metrics + +Metrics collected from nodes, Pods, and cluster components: + +- Node Exporter: CPU, memory, disk, network on nodes +- kube-state-metrics: Kubernetes object state +- cAdvisor: Container resource usage + +### Logs + +In Kubernetes, container logs are written to stdout and stderr: + +```bash +kubectl logs -n +kubectl logs -f --tail=100 -n +``` + +For centralised log aggregation, use: +- Loki + Promtail (with Grafana) +- Elasticsearch + Fluentd/Fluent Bit (EFK stack) +- Cloud-provider native logging + +### Traces + +Distributed tracing shows the path of a request across services. + +Key concepts: +- **Span**: a single operation (e.g., a DB query or HTTP call) +- **Trace**: a tree of spans representing an end-to-end request +- **Context propagation**: passing trace headers between services (W3C TraceContext) + +Popular tools: Jaeger, Zipkin, Tempo (Grafana) + +## Prometheus + +[Prometheus](https://prometheus.io/) is a CNCF graduated project for monitoring and alerting. + +```bash +# Query example +rate(http_requests_total[5m]) +up{job="kubernetes-nodes"} +``` + +## Grafana + +[Grafana](https://grafana.com/) provides dashboards and visualisation for metrics from Prometheus, +Loki, Tempo, and other data sources. + +## OpenTelemetry + +[OpenTelemetry](https://opentelemetry.io/) (OTel) is a CNCF project providing vendor-neutral +APIs, SDKs, and tools for collecting telemetry data (traces, metrics, logs). + +- **SDK**: instrument your application code +- **Collector**: receive, process, and export telemetry +- **OTLP**: OpenTelemetry Protocol for data transport + +> A large observability stack (Prometheus operator, Grafana, Loki, Tempo) will not be +> installed automatically. Labs will provide step-by-step instructions when needed. diff --git a/observability-labs/alerting/README.md b/observability-labs/alerting/README.md new file mode 100644 index 0000000..3c5ac3c --- /dev/null +++ b/observability-labs/alerting/README.md @@ -0,0 +1,4 @@ +# alerting + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/observability-labs/grafana/README.md b/observability-labs/grafana/README.md new file mode 100644 index 0000000..1ad4cd0 --- /dev/null +++ b/observability-labs/grafana/README.md @@ -0,0 +1,4 @@ +# grafana + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/observability-labs/logging/README.md b/observability-labs/logging/README.md new file mode 100644 index 0000000..89993c8 --- /dev/null +++ b/observability-labs/logging/README.md @@ -0,0 +1,4 @@ +# logging + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/observability-labs/metrics/README.md b/observability-labs/metrics/README.md new file mode 100644 index 0000000..acb716a --- /dev/null +++ b/observability-labs/metrics/README.md @@ -0,0 +1,4 @@ +# metrics + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/observability-labs/opentelemetry/README.md b/observability-labs/opentelemetry/README.md new file mode 100644 index 0000000..36359f1 --- /dev/null +++ b/observability-labs/opentelemetry/README.md @@ -0,0 +1,4 @@ +# opentelemetry + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/observability-labs/prometheus/README.md b/observability-labs/prometheus/README.md new file mode 100644 index 0000000..5823939 --- /dev/null +++ b/observability-labs/prometheus/README.md @@ -0,0 +1,4 @@ +# prometheus + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/observability-labs/tracing/README.md b/observability-labs/tracing/README.md new file mode 100644 index 0000000..bfaeca4 --- /dev/null +++ b/observability-labs/tracing/README.md @@ -0,0 +1,4 @@ +# tracing + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/roadmap.md b/roadmap.md new file mode 100644 index 0000000..473bb21 --- /dev/null +++ b/roadmap.md @@ -0,0 +1,99 @@ +# Roadmap + +This roadmap tracks the planned phases for the cloud-native-lab repository. +Status markers indicate current progress. Items are subject to change. + +## Phase 1: Repository Foundation + +**Status:** In Progress + +- [x] Repository structure and root configuration +- [x] EditorConfig, gitignore, linting configuration +- [x] Makefile with common targets +- [x] GitHub Actions workflows (markdown lint, shellcheck, YAML lint, Kubernetes validation, Go CI) +- [x] Issue and pull request templates +- [x] Security and contribution documentation +- [x] Kind cluster configuration +- [x] Prerequisite check script +- [x] Initial CKAD lab structure +- [x] Initial Kubernetes general labs +- [x] Documentation templates and cheatsheets +- [ ] Complete CKAD lab stubs for all sub-domains + +## Phase 2: CKAD Preparation + +**Status:** Planned + +- [ ] Application Design and Build exercises (multi-container, init containers, volumes, Jobs, CronJobs) +- [ ] Application Deployment exercises (rolling updates, blue/green, Helm) +- [ ] Application Observability and Maintenance (probes, debugging, logging) +- [ ] Application Environment, Configuration and Security (ConfigMaps, Secrets, SecurityContexts, ServiceAccounts) +- [ ] Services and Networking (Services, Ingress, NetworkPolicy) +- [ ] Timed exercises (imperative command practice) +- [ ] Mock exam scenarios +- [ ] Exam strategy and cheatsheet + +## Phase 3: CKA Preparation + +**Status:** Planned + +- [ ] Cluster architecture overview +- [ ] Cluster installation and configuration (kubeadm) +- [ ] Cluster maintenance (upgrades, backups) +- [ ] Networking (CNI, CoreDNS, kube-proxy) +- [ ] Storage (PV, PVC, storage classes, dynamic provisioning) +- [ ] Troubleshooting (node, workload, network debugging) +- [ ] Security basics (RBAC, certificates, kubeconfig) + +## Phase 4: Go and Kubernetes APIs + +**Status:** Planned + +- [ ] client-go basics (list, watch, informers) +- [ ] Work queues +- [ ] Controller pattern +- [ ] Admission webhooks (validating and mutating) +- [ ] Metrics exporters (Prometheus client) +- [ ] CLI utilities using cobra/urfave + +## Phase 5: Operators and Custom Resources + +**Status:** Planned + +- [ ] Custom Resource Definitions (CRDs) +- [ ] Reconciliation loop pattern +- [ ] Status subresources +- [ ] Finalisers +- [ ] Leader election +- [ ] Controller testing (envtest) +- [ ] Packaging and release process +- [ ] Operator lifecycle (OLM basics) + +## Phase 6: GitOps, Observability and Security + +**Status:** Planned + +- [ ] Argo CD installation and application management +- [ ] Flux CD installation and kustomization +- [ ] GitOps repository patterns +- [ ] Prometheus and Grafana stack +- [ ] OpenTelemetry collector and SDK +- [ ] Policy as code (OPA/Gatekeeper, Kyverno) +- [ ] Supply-chain security (Cosign, SBOM, Syft) +- [ ] Runtime security (Falco) + +## Phase 7: CKS Preparation + +**Status:** Planned + +- [ ] Cluster hardening +- [ ] System hardening +- [ ] Minimise microservice vulnerabilities +- [ ] Supply chain security +- [ ] Monitoring, logging and runtime security +- [ ] Mock exam scenarios + +--- + +> Items marked `[x]` are complete. Items marked `[ ]` are planned or in progress. +> This roadmap evolves with the repository. Check individual directory READMEs for current status. diff --git a/scripts/check-prerequisites.sh b/scripts/check-prerequisites.sh new file mode 100755 index 0000000..4fdd33f --- /dev/null +++ b/scripts/check-prerequisites.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +set -euo pipefail + +# check-prerequisites.sh +# Checks that required tools are installed for cloud-native-lab. +# Core tools: Docker, kubectl, Kind +# Optional tools: Helm, Kustomize, Go + +PASS=" ✔" +FAIL=" ✘" +WARN=" ⚠" + +CORE_FAILURES=0 + +print_header() { + echo "" + echo "===============================" + echo " Cloud Native Lab" + echo " Prerequisite Check" + echo "===============================" + echo "" +} + +check_tool() { + local name="$1" + local cmd="$2" + local version_cmd="$3" + local required="$4" + + if command -v "${cmd}" >/dev/null 2>&1; then + local version + version=$(eval "${version_cmd}" 2>/dev/null || echo "unknown") + echo "${PASS} ${name}: ${version}" + else + if [ "${required}" = "true" ]; then + echo "${FAIL} ${name}: NOT FOUND (required)" + CORE_FAILURES=$((CORE_FAILURES + 1)) + else + echo "${WARN} ${name}: NOT FOUND (optional)" + fi + fi +} + +print_header + +echo "Core tools (required):" +check_tool "Docker" "docker" "docker --version | head -1" "true" +check_tool "kubectl" "kubectl" "kubectl version --client --short 2>/dev/null | head -1" "true" +check_tool "Kind" "kind" "kind --version" "true" +check_tool "Git" "git" "git --version" "true" +check_tool "Bash" "bash" "bash --version | head -1" "true" + +echo "" +echo "Optional tools (required for specific labs):" +check_tool "Helm" "helm" "helm version --short" "false" +check_tool "Kustomize" "kustomize" "kustomize version --short" "false" +check_tool "Go" "go" "go version" "false" + +echo "" + +if [ "${CORE_FAILURES}" -gt 0 ]; then + echo "===============================" + echo "${FAIL} ${CORE_FAILURES} core tool(s) missing." + echo " Please install missing tools before running labs." + echo "===============================" + exit 1 +else + echo "===============================" + echo "${PASS} All core tools are available." + echo "===============================" +fi diff --git a/scripts/cleanup.sh b/scripts/cleanup.sh new file mode 100755 index 0000000..2d545de --- /dev/null +++ b/scripts/cleanup.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +set -euo pipefail + +# cleanup.sh +# Removes temporary local files and optionally deletes the Kind cluster. +# +# This script ONLY deletes: +# - Temporary log files +# - Go build coverage files +# - The Kind cluster created by this repository (when CLEANUP_CLUSTER=true) +# +# It does NOT delete any Kubernetes namespaces or lab resources. +# Use the cleanup.sh script in each lab directory to clean up lab resources. + +CLUSTER_NAME="${CLUSTER_NAME:-cloud-native-lab}" +CLEANUP_CLUSTER="${CLEANUP_CLUSTER:-false}" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +echo "==> Cleaning up local temporary files..." + +find "${REPO_ROOT}" -name '*.log' -not -path '*/.git/*' -delete 2>/dev/null || true +find "${REPO_ROOT}" -name 'coverage.txt' -not -path '*/.git/*' -delete 2>/dev/null || true +find "${REPO_ROOT}" -name 'coverage.html' -not -path '*/.git/*' -delete 2>/dev/null || true + +echo " Temporary files removed." + +if [ "${CLEANUP_CLUSTER}" = "true" ]; then + echo "" + echo "==> Deleting Kind cluster '${CLUSTER_NAME}'..." + CLUSTER_NAME="${CLUSTER_NAME}" bash "${SCRIPT_DIR}/delete-kind-cluster.sh" +else + echo "" + echo " Skipping cluster deletion." + echo " To also delete the Kind cluster, run:" + echo " CLEANUP_CLUSTER=true bash scripts/cleanup.sh" + echo " or:" + echo " make cluster-delete" +fi + +echo "" +echo "==> Cleanup complete." diff --git a/scripts/create-kind-cluster.sh b/scripts/create-kind-cluster.sh new file mode 100755 index 0000000..4871864 --- /dev/null +++ b/scripts/create-kind-cluster.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +set -euo pipefail + +# create-kind-cluster.sh +# Creates a local Kind cluster for cloud-native-lab. +# Usage: CLUSTER_NAME=my-cluster bash create-kind-cluster.sh + +CLUSTER_NAME="${CLUSTER_NAME:-cloud-native-lab}" +KIND_CONFIG="${KIND_CONFIG:-kubernetes-labs/clusters/kind/kind-config.yaml}" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +echo "==> Creating Kind cluster '${CLUSTER_NAME}'..." + +if ! command -v kind >/dev/null 2>&1; then + echo "ERROR: kind is not installed." + echo " Install from: https://kind.sigs.k8s.io/docs/user/quick-start/" + exit 1 +fi + +if ! command -v kubectl >/dev/null 2>&1; then + echo "ERROR: kubectl is not installed." + echo " Install from: https://kubernetes.io/docs/tasks/tools/" + exit 1 +fi + +if kind get clusters 2>/dev/null | grep -q "^${CLUSTER_NAME}$"; then + echo " Cluster '${CLUSTER_NAME}' already exists." + echo " Use 'kind delete cluster --name ${CLUSTER_NAME}' to delete it first." + exit 0 +fi + +CONFIG_FILE="${REPO_ROOT}/${KIND_CONFIG}" +if [ -f "${CONFIG_FILE}" ]; then + echo " Using config: ${CONFIG_FILE}" + kind create cluster --name "${CLUSTER_NAME}" --config "${CONFIG_FILE}" +else + echo " Config file not found: ${CONFIG_FILE}" + echo " Creating cluster with default settings..." + kind create cluster --name "${CLUSTER_NAME}" +fi + +echo "" +echo "==> Cluster '${CLUSTER_NAME}' created successfully." +echo "" +echo " kubectl cluster-info --context kind-${CLUSTER_NAME}" +echo " kubectl get nodes" diff --git a/scripts/delete-kind-cluster.sh b/scripts/delete-kind-cluster.sh new file mode 100755 index 0000000..a87577a --- /dev/null +++ b/scripts/delete-kind-cluster.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -euo pipefail + +# delete-kind-cluster.sh +# Deletes the local Kind cluster for cloud-native-lab. +# Usage: CLUSTER_NAME=my-cluster bash delete-kind-cluster.sh + +CLUSTER_NAME="${CLUSTER_NAME:-cloud-native-lab}" + +echo "==> Deleting Kind cluster '${CLUSTER_NAME}'..." + +if ! command -v kind >/dev/null 2>&1; then + echo "ERROR: kind is not installed." + exit 1 +fi + +if ! kind get clusters 2>/dev/null | grep -q "^${CLUSTER_NAME}$"; then + echo " Cluster '${CLUSTER_NAME}' does not exist. Nothing to delete." + exit 0 +fi + +kind delete cluster --name "${CLUSTER_NAME}" + +echo "" +echo "==> Cluster '${CLUSTER_NAME}' deleted." diff --git a/scripts/validate-manifests.sh b/scripts/validate-manifests.sh new file mode 100755 index 0000000..c2bf65d --- /dev/null +++ b/scripts/validate-manifests.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +set -euo pipefail + +# validate-manifests.sh +# Validates Kubernetes YAML manifests found in kubernetes-labs/ and certifications/. +# +# Validation strategy: +# 1. If kubeconform is installed, use it for schema validation. +# 2. If kubectl is available and a cluster is reachable, use server-side dry-run. +# 3. If only kubectl is available, use client-side dry-run. +# +# Limitations: +# - Client-side dry-run does not validate against a real API server. +# - CRDs and custom resources cannot be validated without a running cluster. +# - Excluded: .github/workflows/ (GitHub Actions YAML, not Kubernetes manifests). + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +SEARCH_DIRS=( + "kubernetes-labs" + "certifications" +) + +KUBERNETES_VERSION="${KUBERNETES_VERSION:-1.30.0}" +ERRORS=0 + +echo "==> Validating Kubernetes manifests..." +echo " Search directories: ${SEARCH_DIRS[*]}" +echo "" + +# Collect YAML files +mapfile -t YAML_FILES < <( + find "${SEARCH_DIRS[@]/#/${REPO_ROOT}/}" \ + -name '*.yaml' -o -name '*.yml' 2>/dev/null \ + | grep -v '.github' \ + | sort +) + +if [ "${#YAML_FILES[@]}" -eq 0 ]; then + echo " No YAML files found." + exit 0 +fi + +echo " Found ${#YAML_FILES[@]} YAML file(s)." +echo "" + +if command -v kubeconform >/dev/null 2>&1; then + echo "--- Using kubeconform (Kubernetes ${KUBERNETES_VERSION})..." + if ! kubeconform \ + -kubernetes-version "${KUBERNETES_VERSION}" \ + -strict \ + -summary \ + -ignore-missing-schemas \ + "${YAML_FILES[@]}"; then + ERRORS=$((ERRORS + 1)) + fi +elif command -v kubectl >/dev/null 2>&1; then + echo "--- kubeconform not found. Using kubectl dry-run..." + CLUSTER_AVAILABLE=false + if kubectl cluster-info >/dev/null 2>&1; then + CLUSTER_AVAILABLE=true + echo " Cluster is reachable. Using server-side dry-run." + else + echo " No cluster reachable. Using client-side dry-run." + echo " NOTE: Client-side validation is limited." + fi + + for file in "${YAML_FILES[@]}"; do + relative="${file#${REPO_ROOT}/}" + if [ "${CLUSTER_AVAILABLE}" = "true" ]; then + if ! kubectl apply -f "${file}" --dry-run=server 2>&1; then + echo " FAILED: ${relative}" + ERRORS=$((ERRORS + 1)) + else + echo " OK: ${relative}" + fi + else + if ! kubectl apply -f "${file}" --dry-run=client 2>&1; then + echo " FAILED: ${relative}" + ERRORS=$((ERRORS + 1)) + else + echo " OK: ${relative}" + fi + fi + done +else + echo " WARNING: Neither kubeconform nor kubectl found." + echo " Install kubeconform: https://github.com/yannh/kubeconform" + echo " Install kubectl: https://kubernetes.io/docs/tasks/tools/" + exit 0 +fi + +echo "" +if [ "${ERRORS}" -gt 0 ]; then + echo "==> Validation FAILED with ${ERRORS} error(s)." + exit 1 +else + echo "==> Validation passed." +fi diff --git a/security-labs/README.md b/security-labs/README.md new file mode 100644 index 0000000..ee326b5 --- /dev/null +++ b/security-labs/README.md @@ -0,0 +1,117 @@ +# Security Labs + +Hands-on exercises for cloud-native security practices. + +## Overview + +Security in cloud-native environments spans multiple layers: + +- Container image security +- Kubernetes cluster hardening +- Runtime security +- Supply chain security +- Secrets management +- Policy as code + +## Directories + +| Directory | Topics | +|-------------------------------------------------|-----------------------------------------------| +| [kubernetes-security/](kubernetes-security/) | RBAC, network policies, pod security | +| [image-scanning/](image-scanning/) | Trivy, Grype, Docker Scout | +| [software-supply-chain/](software-supply-chain/) | Cosign, SBOM, Syft, Sigstore | +| [policy-as-code/](policy-as-code/) | OPA/Gatekeeper, Kyverno | +| [secrets-management/](secrets-management/) | Vault, External Secrets, Sealed Secrets | +| [runtime-security/](runtime-security/) | Falco, seccomp, AppArmor | +| [threat-modelling/](threat-modelling/) | Threat modelling for Kubernetes | + +## Security Principles + +### Least Privilege + +Every component should have only the permissions it needs: + +```yaml +# Minimal ServiceAccount with no automounted token +apiVersion: v1 +kind: ServiceAccount +metadata: + name: minimal-sa +automountServiceAccountToken: false +``` + +### RBAC + +Avoid using `cluster-admin`. Create specific Roles and RoleBindings: + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: pod-reader + namespace: default +rules: + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "watch"] +``` + +### Non-Root Containers + +```yaml +securityContext: + runAsNonRoot: true + runAsUser: 1000 + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL +``` + +### Read-Only Root Filesystem + +```yaml +securityContext: + readOnlyRootFilesystem: true +``` + +Mount writable volumes for directories that need write access (e.g., `/tmp`). + +### Resource Limits + +Always set resource requests and limits to prevent resource exhaustion: + +```yaml +resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 200m + memory: 256Mi +``` + +### Supply-Chain Security + +- Sign container images with Cosign +- Generate and verify SBOMs (Software Bill of Materials) +- Scan images for vulnerabilities before deployment +- Use Sigstore for keyless signing + +### Image Scanning + +```bash +# Scan with Trivy +trivy image nginx:1.27-alpine + +# Scan with Grype +grype nginx:1.27-alpine +``` + +### Runtime Security with Falco + +[Falco](https://falco.org/) detects unexpected behaviour in containers at runtime +by monitoring system calls. + +> Security examples in this repository use safe defaults. +> All Pods follow the manifests in [../kubernetes-labs/security/](../kubernetes-labs/security/). diff --git a/security-labs/image-scanning/README.md b/security-labs/image-scanning/README.md new file mode 100644 index 0000000..2c09ee3 --- /dev/null +++ b/security-labs/image-scanning/README.md @@ -0,0 +1,4 @@ +# image-scanning + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/security-labs/kubernetes-security/README.md b/security-labs/kubernetes-security/README.md new file mode 100644 index 0000000..5bc3fe6 --- /dev/null +++ b/security-labs/kubernetes-security/README.md @@ -0,0 +1,4 @@ +# kubernetes-security + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/security-labs/policy-as-code/README.md b/security-labs/policy-as-code/README.md new file mode 100644 index 0000000..196b900 --- /dev/null +++ b/security-labs/policy-as-code/README.md @@ -0,0 +1,4 @@ +# policy-as-code + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/security-labs/runtime-security/README.md b/security-labs/runtime-security/README.md new file mode 100644 index 0000000..a5dc9fd --- /dev/null +++ b/security-labs/runtime-security/README.md @@ -0,0 +1,4 @@ +# runtime-security + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/security-labs/secrets-management/README.md b/security-labs/secrets-management/README.md new file mode 100644 index 0000000..02c55d3 --- /dev/null +++ b/security-labs/secrets-management/README.md @@ -0,0 +1,4 @@ +# secrets-management + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/security-labs/software-supply-chain/README.md b/security-labs/software-supply-chain/README.md new file mode 100644 index 0000000..f238db5 --- /dev/null +++ b/security-labs/software-supply-chain/README.md @@ -0,0 +1,4 @@ +# software-supply-chain + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/security-labs/threat-modelling/README.md b/security-labs/threat-modelling/README.md new file mode 100644 index 0000000..8481acd --- /dev/null +++ b/security-labs/threat-modelling/README.md @@ -0,0 +1,4 @@ +# threat-modelling + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..f32744a --- /dev/null +++ b/tests/README.md @@ -0,0 +1,28 @@ +# Tests + +Automated validation tests for repository content. + +## Directories + +| Directory | Description | +|--------------------------------------------|-------------------------------------------| +| [manifest-validation/](manifest-validation/) | Kubernetes manifest validation tests | +| [integration/](integration/) | Integration tests for lab exercises | +| [smoke/](smoke/) | Smoke tests for basic cluster health | + +## Running Tests + +```bash +# Validate all manifests +make validate + +# Run all available tests +make test +``` + +## Manifest Validation + +The `validate-manifests.sh` script in `scripts/` validates Kubernetes YAML files +using kubeconform (if installed) or kubectl dry-run. + +See [../scripts/validate-manifests.sh](../scripts/validate-manifests.sh) for details. diff --git a/tests/integration/README.md b/tests/integration/README.md new file mode 100644 index 0000000..e6c749a --- /dev/null +++ b/tests/integration/README.md @@ -0,0 +1,4 @@ +# integration + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/tests/manifest-validation/README.md b/tests/manifest-validation/README.md new file mode 100644 index 0000000..6bf07f4 --- /dev/null +++ b/tests/manifest-validation/README.md @@ -0,0 +1,4 @@ +# manifest-validation + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context. diff --git a/tests/smoke/README.md b/tests/smoke/README.md new file mode 100644 index 0000000..559c752 --- /dev/null +++ b/tests/smoke/README.md @@ -0,0 +1,4 @@ +# smoke + +> Content will be added here as the repository evolves. +> See [../README.md](../README.md) for context.