├── .dockerignore ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .tmuxinator.yml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── Vagrantfile ├── cmd └── k8s-tew │ ├── ceph.go │ ├── completion.go │ ├── configure.go │ ├── dashboard.go │ ├── deploy.go │ ├── environment.go │ ├── generate.go │ ├── initialize.go │ ├── main.go │ ├── node-add.go │ ├── node-list.go │ ├── node-remove.go │ └── run.go ├── conformance └── certified-kubernetes-color.svg ├── data ├── data.go └── templates │ ├── ceph │ ├── ceph.conf │ ├── client-admin.keyring │ ├── client.keyring │ └── monitor.keyring │ ├── k8s │ ├── config-map.yaml │ ├── credentials.yaml │ ├── cri │ │ └── containerd.toml │ ├── encryption-config.yaml │ ├── kube-proxy-configuration.yaml │ ├── kube-scheduler-configuration.yaml │ ├── kubeconfig.yaml │ ├── kubelet-configuration.yaml │ ├── lb │ │ └── gobetween.toml │ ├── manifests │ │ ├── etcd.yaml │ │ ├── gobetween.yaml │ │ ├── kube-apiserver.yaml │ │ ├── kube-controller-manager.yaml │ │ ├── kube-proxy.yaml │ │ ├── kube-scheduler.yaml │ │ └── virtual-ip.yaml │ ├── service-account.yaml │ └── setup │ │ ├── backup │ │ └── velero.yaml │ │ ├── dns │ │ └── coredns.yaml │ │ ├── ingress │ │ └── letsencrypt-cluster-issuer.yaml │ │ ├── kubelet-setup.yaml │ │ ├── logging │ │ └── efk.yaml │ │ ├── management │ │ └── kubernetes-dashboard.yaml │ │ ├── miscellaneous │ │ └── wordpress.yaml │ │ ├── monitoring │ │ ├── alert-manager.yaml │ │ ├── grafana-dashboards.yaml │ │ ├── grafana.yaml │ │ ├── kube-state-metrics.yaml │ │ ├── metrics-server.yaml │ │ ├── node-exporter.yaml │ │ ├── prometheus-alerts.yaml │ │ ├── prometheus-rules.yaml │ │ └── prometheus.yaml │ │ ├── networking │ │ ├── calico.yaml │ │ ├── cert-manager.yaml │ │ ├── metallb.yaml │ │ └── nginx-ingress.yaml │ │ └── storage │ │ ├── ceph-csi.yaml │ │ ├── ceph-secrets.yaml │ │ └── ceph-setup.yaml │ └── system │ ├── environment.sh │ ├── k8s-tew.service │ └── k8s-tew.sh ├── docs ├── .nojekyll ├── Makefile ├── _build │ ├── doctrees │ │ ├── about.doctree │ │ ├── environment.pickle │ │ ├── features.doctree │ │ ├── index.doctree │ │ ├── installation.doctree │ │ ├── quickstart.doctree │ │ ├── setups.doctree │ │ ├── troubleshooting.doctree │ │ └── usage.doctree │ └── html │ │ ├── .buildinfo │ │ ├── .nojekyll │ │ ├── _sources │ │ ├── about.rst.txt │ │ ├── features.rst.txt │ │ ├── index.rst.txt │ │ ├── installation.rst.txt │ │ ├── quickstart.rst.txt │ │ ├── setups.rst.txt │ │ ├── troubleshooting.rst.txt │ │ └── usage.rst.txt │ │ ├── _static │ │ ├── _sphinx_javascript_frameworks_compat.js │ │ ├── basic.css │ │ ├── css │ │ │ ├── badge_only.css │ │ │ ├── fonts │ │ │ │ ├── Roboto-Slab-Bold.woff │ │ │ │ ├── Roboto-Slab-Bold.woff2 │ │ │ │ ├── Roboto-Slab-Regular.woff │ │ │ │ ├── Roboto-Slab-Regular.woff2 │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ ├── fontawesome-webfont.svg │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ ├── fontawesome-webfont.woff │ │ │ │ ├── fontawesome-webfont.woff2 │ │ │ │ ├── lato-bold-italic.woff │ │ │ │ ├── lato-bold-italic.woff2 │ │ │ │ ├── lato-bold.woff │ │ │ │ ├── lato-bold.woff2 │ │ │ │ ├── lato-normal-italic.woff │ │ │ │ ├── lato-normal-italic.woff2 │ │ │ │ ├── lato-normal.woff │ │ │ │ └── lato-normal.woff2 │ │ │ └── theme.css │ │ ├── doctools.js │ │ ├── documentation_options.js │ │ ├── file.png │ │ ├── jquery.js │ │ ├── js │ │ │ ├── badge_only.js │ │ │ └── theme.js │ │ ├── language_data.js │ │ ├── logo.svg │ │ ├── minus.png │ │ ├── plus.png │ │ ├── pygments.css │ │ ├── searchtools.js │ │ └── sphinx_highlight.js │ │ ├── about.html │ │ ├── features.html │ │ ├── genindex.html │ │ ├── index.html │ │ ├── installation.html │ │ ├── objects.inv │ │ ├── quickstart.html │ │ ├── search.html │ │ ├── searchindex.js │ │ ├── setups.html │ │ ├── troubleshooting.html │ │ └── usage.html ├── _static │ └── .gitkeep ├── _templates │ └── .gitkeep ├── about.rst ├── conf.py ├── eval ├── features.rst ├── index.html ├── index.rst ├── installation.rst ├── quickstart.rst ├── setups.rst ├── troubleshooting.rst └── usage.rst ├── go.mod ├── go.sum ├── logo.svg ├── pkg ├── ceph │ └── ceph.go ├── config │ ├── asset_config.go │ ├── asset_directory.go │ ├── asset_file.go │ ├── command.go │ ├── config.go │ ├── features.go │ ├── images.go │ ├── internal_config.go │ ├── labels.go │ ├── logger_config.go │ ├── node.go │ ├── server_config.go │ └── versions.go ├── container │ ├── image │ │ ├── converter │ │ │ ├── converter.go │ │ │ └── layers.go │ │ ├── manifest │ │ │ ├── config.go │ │ │ ├── file_system_layers.go │ │ │ ├── history.go │ │ │ └── manifest.go │ │ └── storage │ │ │ └── storage.go │ └── pods.go ├── deployment │ ├── deployment.go │ └── node_deployment.go ├── download │ └── download.go ├── generate │ └── generate.go ├── k8s │ └── k8s.go ├── pki │ └── pki.go ├── servers │ ├── server.go │ ├── server_wrapper.go │ └── servers.go ├── utils │ ├── checksums.go │ ├── constants.go │ ├── limiter.go │ ├── logger.go │ ├── progress.go │ ├── tasks.go │ ├── templates.go │ └── utils.go └── version │ └── version.go └── setup ├── centos-multi-node └── Makefile ├── centos-single-node └── Makefile ├── checks ├── backup.sh └── ceph.sh ├── docker ├── Dockerfile └── Makefile ├── local └── Makefile ├── multipass └── Makefile ├── ubuntu-multi-node └── Makefile └── ubuntu-single-node └── Makefile /.dockerignore: -------------------------------------------------------------------------------- 1 | setup 2 | vendor 3 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Makefile CI 2 | 3 | on: 4 | push: 5 | tags: ['*'] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - name: Make 13 | run: make 14 | - name: Create release 15 | id: create_release 16 | uses: actions/create-release@v1 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | with: 20 | tag_name: ${{ github.ref }} 21 | release_name: Release ${{ github.ref }} 22 | body: | 23 | Release ${{ github.ref }} 24 | draft: false 25 | prerelease: false 26 | - name: Upload release asset 27 | id: upload-release-asset 28 | uses: actions/upload-release-asset@v1 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | with: 32 | upload_url: ${{ steps.create_release.outputs.upload_url }} 33 | asset_path: ./k8s-tew 34 | asset_name: k8s-tew 35 | asset_content_type: application/octet-stream 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vagrant 2 | assets 3 | tags 4 | utils/a_utils-packr.go 5 | embedded 6 | bin 7 | vendor 8 | .env 9 | /k8s-tew 10 | sandbox 11 | setup/docker/files 12 | -------------------------------------------------------------------------------- /.tmuxinator.yml: -------------------------------------------------------------------------------- 1 | name: k8s-tew 2 | on_project_start: export PATH=$(pwd):$PATH 3 | 4 | windows: 5 | - editor: 6 | layout: main-horizontal 7 | panes: 8 | - vi 9 | - make watch-and-compile 10 | - shell: 11 | - sandbox-command: 12 | - cd setup/sandbox/vbox-single 13 | - sandbox-environment: 14 | - cd setup/sandbox/vbox-single 15 | - source <(k8s-tew environment) 16 | - k9s: 17 | - cd setup/sandbox/vbox-single 18 | - source <(k8s-tew environment) 19 | - k9s 20 | - documentation-update: 21 | - make watch-and-update-documentation 22 | - documentation-server: 23 | - (cd docs/_build/html && python -m http.server 8080) 24 | - port-forwarding-80: 25 | - make forward-80 26 | - port-forwarding-443: 27 | - make forward-443 28 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG GO_VERSION=1.22.2 2 | 3 | FROM golang:${GO_VERSION}-alpine AS builder 4 | 5 | RUN apk add --update --no-cache ca-certificates make git curl mercurial build-base 6 | 7 | ARG PACKAGE=github.com/darxkies/k8s-tew 8 | ARG WORKING_DIRECTORY=/go/src/${PACKAGE}/ 9 | 10 | ENV GO111MODULE=on 11 | 12 | RUN echo ${WORKING_DIRECOTORY} 13 | RUN mkdir -p ${WORKING_DIRECTORY} 14 | 15 | WORKDIR ${WORKING_DIRECTORY} 16 | 17 | COPY go.mod go.sum ${WORKING_DIRECTORY} 18 | 19 | RUN go mod download 20 | 21 | RUN git config --global --add safe.directory /go/src/github.com/darxkies/k8s-tew 22 | 23 | CMD ["make", "build-binaries"] 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BUILD_IMAGE = darxkies/k8s-tew-build 2 | VERSION = $(shell git describe --tags) 3 | PACKAGE = github.com/darxkies/k8s-tew 4 | 5 | compile: 6 | docker buildx build --ulimit memlock=-1:-1 -t $(BUILD_IMAGE) . 7 | docker run --rm --ulimit memlock=-1:-1 -v $$(pwd):/go/src/$(PACKAGE) $(BUILD_IMAGE) 8 | 9 | build-binaries: 10 | CGO_ENABLED=0 go build -ldflags "-X ${PACKAGE}/pkg/version.Version=${VERSION} -s -w" -o k8s-tew ${PACKAGE}/cmd/k8s-tew 11 | 12 | watch-and-compile: 13 | go get github.com/cespare/reflex 14 | reflex -r '\.(go|yaml)$$' -R '^vendor' -R '^setup' -R '^pkg/utils/a_utils-packr\.go$$' make build-binaries 15 | 16 | watch-and-update-documentation: 17 | (cd docs && reflex -r '\.rst' -R "^_build" make clean html) 18 | 19 | clean: 20 | sudo rm -Rf bin vendor 21 | 22 | .PHONY: build clean 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

Kubernetes v1.23

4 | 5 | # Kubernetes - The Easier Way (k8s-tew) 6 | 7 | [![Go Report Card](https://goreportcard.com/badge/github.com/darxkies/k8s-tew)](https://goreportcard.com/report/github.com/darxkies/k8s-tew) 8 | [![GitHub release](https://img.shields.io/github/tag/darxkies/k8s-tew.svg)](https://github.com/darxkies/k8s-tew/releases/latest) 9 | [![Downloads](https://img.shields.io/github/downloads/darxkies/k8s-tew/total)](https://github.com/darxkies/k8s-tew/releases/latest) 10 | ![GitHub](https://img.shields.io/github/license/darxkies/k8s-tew.svg) 11 | 12 | k8s-tew is a CLI tool to install a [Kubernetes](https://kubernetes.io/) Cluster (local, single-node, multi-node or HA-cluster) on Bare Metal. It installs the most essential components needed by a cluster such as networking, storage, monitoring, logging, backuping/restoring and so on. Besides that, k8s-tew is also a supervisor that starts all cluster components on each node, once it setup the nodes. 13 | 14 | ## TL;DR 15 | 16 | [![k8s-tew](https://img.youtube.com/vi/53qQa5EkBTU/0.jpg)](https://www.youtube.com/watch?v=53qQa5EkBTU) 17 | 18 | # Documentation 19 | 20 | The project documentation can be found here: [https://darxkies.github.io/k8s-tew](https://darxkies.github.io/k8s-tew) 21 | 22 | # Caveats 23 | 24 | - The local setup uses for ingress the ports 80, 443 so they need to be free on the host. It also turns swapping off which is a requirement for kubelet. 25 | - On CentOS nodes the firewall and SELinux are disabled to not interfere with Kubernetes. 26 | 27 | # Feedback 28 | 29 | - E-Mail: darxkies@gmail.com 30 | -------------------------------------------------------------------------------- /cmd/k8s-tew/completion.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var completionShell string 12 | 13 | var completionCmd = &cobra.Command{ 14 | Use: "completion", 15 | Short: "Generates bash completion scripts", 16 | Long: `To load bash completion run 17 | 18 | . <(k8s-tew completion) 19 | 20 | or 21 | 22 | . <(k8s-tew completion bash) 23 | 24 | and for zsh run 25 | 26 | . <(k8s-tew completion zsh) 27 | 28 | To configure your bash shell to load completions for each session add to your bashrc 29 | 30 | # ~/.bashrc or ~/.profile 31 | . <(k8s-tew completion) 32 | `, 33 | RunE: func(cmd *cobra.Command, args []string) error { 34 | if len(args) > 1 { 35 | return errors.New("Please specify only the shell: bash or zsh") 36 | } 37 | 38 | if len(args) == 0 || args[0] == "bash" { 39 | return RootCmd.GenBashCompletion(os.Stdout) 40 | 41 | } else if args[0] == "zsh" { 42 | return RootCmd.GenZshCompletion(os.Stdout) 43 | 44 | } 45 | 46 | return fmt.Errorf("Unknown shell '%s'", args[0]) 47 | }, 48 | ValidArgs: []string{"bash", "zsh"}, 49 | } 50 | 51 | func init() { 52 | RootCmd.AddCommand(completionCmd) 53 | } 54 | -------------------------------------------------------------------------------- /cmd/k8s-tew/deploy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "path" 6 | 7 | "github.com/darxkies/k8s-tew/pkg/deployment" 8 | "github.com/darxkies/k8s-tew/pkg/utils" 9 | 10 | log "github.com/sirupsen/logrus" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | var identityFile string 15 | var commandRetries uint 16 | var skipSetup bool 17 | var skipUpload bool 18 | var skipRestart bool 19 | var skipStorageSetup bool 20 | var skipMonitoringSetup bool 21 | var skipLoggingSetup bool 22 | var skipBackupSetup bool 23 | var skipShowcaseSetup bool 24 | var skipIngressSetup bool 25 | var forceUpload bool 26 | var importImages bool 27 | var wait uint 28 | 29 | var deployCmd = &cobra.Command{ 30 | Use: "deploy", 31 | Short: "Deploy assets to a remote cluster", 32 | Long: "Deploy assets to a remote cluster", 33 | Run: func(cmd *cobra.Command, args []string) { 34 | if error := bootstrap(false); error != nil { 35 | log.WithFields(log.Fields{"error": error}).Error("Failed initializing") 36 | 37 | os.Exit(-1) 38 | } 39 | 40 | _deployment := deployment.NewDeployment(_config, identityFile, importImages, forceUpload, parallel, commandRetries, skipSetup, skipUpload, skipRestart, skipStorageSetup, skipMonitoringSetup, skipLoggingSetup, skipBackupSetup, skipShowcaseSetup, skipIngressSetup, wait) 41 | 42 | utils.SetProgressSteps(_deployment.Steps() + 1) 43 | 44 | utils.ShowProgress() 45 | 46 | if error := _deployment.Deploy(); error != nil { 47 | log.WithFields(log.Fields{"error": error}).Error("Failed deploying") 48 | 49 | os.Exit(-2) 50 | } 51 | 52 | utils.HideProgress() 53 | 54 | log.Info("Done") 55 | }, 56 | } 57 | 58 | func init() { 59 | deployCmd.Flags().StringVarP(&identityFile, "identity-file", "i", path.Join(os.Getenv("HOME"), ".ssh/id_rsa"), "SSH identity file") 60 | deployCmd.Flags().UintVarP(&commandRetries, "command-retries", "r", 1200, "The number of command retries during the setup") 61 | deployCmd.Flags().BoolVar(&skipSetup, "skip-setup", false, "Skip setup steps") 62 | deployCmd.Flags().BoolVar(&skipUpload, "skip-upload", false, "Skip upload steps") 63 | deployCmd.Flags().BoolVar(&skipRestart, "skip-restart", false, "Skip restart steps") 64 | deployCmd.Flags().BoolVar(&skipStorageSetup, "skip-storage-setup", false, "Skip storage setup and all other feature setup steps") 65 | deployCmd.Flags().BoolVar(&skipMonitoringSetup, "skip-monitoring-setup", false, "Skip monitoring setup") 66 | deployCmd.Flags().BoolVar(&skipLoggingSetup, "skip-logging-setup", false, "Skip logging setup") 67 | deployCmd.Flags().BoolVar(&skipBackupSetup, "skip-backup-setup", false, "Skip backup setup") 68 | deployCmd.Flags().BoolVar(&skipShowcaseSetup, "skip-showcase-setup", false, "Skip showcase setup") 69 | deployCmd.Flags().BoolVar(&skipIngressSetup, "skip-ingress-setup", false, "Skip ingress setup") 70 | deployCmd.Flags().BoolVar(&importImages, "import-images", false, "Install images") 71 | deployCmd.Flags().BoolVar(¶llel, "parallel", false, "Run steps in parallel") 72 | deployCmd.Flags().BoolVar(&forceUpload, "force-upload", false, "Files are uploaded without checking if they are already installed") 73 | deployCmd.Flags().UintVar(&wait, "wait", 0, "Wait for all cluster relevant pods to be ready and jobs to be completed. The parameter reflects the number of seconds in which the pods have to run stable.") 74 | RootCmd.AddCommand(deployCmd) 75 | } 76 | -------------------------------------------------------------------------------- /cmd/k8s-tew/environment.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/darxkies/k8s-tew/pkg/utils" 9 | 10 | log "github.com/sirupsen/logrus" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | var environmentCmd = &cobra.Command{ 15 | Use: "environment", 16 | Short: "Set environment variables", 17 | Long: `To set the environment variables run 18 | 19 | . <(k8s-tew environment) 20 | `, 21 | Run: func(cmd *cobra.Command, args []string) { 22 | // Load config and check the rights 23 | if error := bootstrap(false); error != nil { 24 | log.WithFields(log.Fields{"error": error}).Error("Failed initializing") 25 | 26 | os.Exit(-1) 27 | } 28 | 29 | checkShell := func(name string) bool { 30 | command := fmt.Sprintf("/proc/%d/exe -c 'echo $%s_VERSION'", os.Getppid(), strings.ToUpper(name)) 31 | 32 | output, error := utils.RunCommandWithOutput(command) 33 | 34 | return error == nil && len(strings.TrimSpace(output)) > 0 35 | } 36 | 37 | shell := "" 38 | 39 | // Figure out if bash or zsh 40 | if checkShell("bash") { 41 | shell = "bash" 42 | } else if checkShell("zsh") { 43 | shell = "zsh" 44 | } 45 | 46 | outputBashCompletion := func(binaryName string) { 47 | if len(shell) == 0 { 48 | return 49 | } 50 | 51 | binary := _config.GetFullLocalAssetFilename(binaryName) 52 | 53 | command := fmt.Sprintf("%s completion %s", binary, shell) 54 | 55 | content, error := utils.RunCommandWithOutput(command) 56 | if error != nil { 57 | return 58 | } 59 | 60 | fmt.Println(content) 61 | } 62 | 63 | outputBashCompletion(utils.BinaryK8sTew) 64 | outputBashCompletion(utils.BinaryKubectl) 65 | outputBashCompletion(utils.BinaryHelm) 66 | outputBashCompletion(utils.BinaryVelero) 67 | outputBashCompletion(utils.BinaryCrictl) 68 | 69 | kubeConfig := _config.GetFullLocalAssetFilename(utils.KubeconfigAdmin) 70 | 71 | if !utils.FileExists(kubeConfig) { 72 | kubeConfig = "" 73 | } 74 | 75 | content, error := utils.ApplyTemplate("Environment", utils.GetTemplate(utils.TemplateEnvironment), struct { 76 | CurrentPath string 77 | K8STEWPath string 78 | K8SPath string 79 | EtcdPath string 80 | CRIPath string 81 | CNIPath string 82 | VeleroPath string 83 | HostPath string 84 | KubeConfig string 85 | ContainerdSock string 86 | }{ 87 | CurrentPath: os.Getenv("PATH"), 88 | K8STEWPath: _config.GetFullLocalAssetDirectory(utils.DirectoryBinaries), 89 | K8SPath: _config.GetFullLocalAssetDirectory(utils.DirectoryK8sBinaries), 90 | EtcdPath: _config.GetFullLocalAssetDirectory(utils.DirectoryEtcdBinaries), 91 | CRIPath: _config.GetFullLocalAssetDirectory(utils.DirectoryCriBinaries), 92 | CNIPath: _config.GetFullLocalAssetDirectory(utils.DirectoryCniBinaries), 93 | VeleroPath: _config.GetFullLocalAssetDirectory(utils.DirectoryVeleroBinaries), 94 | HostPath: _config.GetFullLocalAssetDirectory(utils.DirectoryHostBinaries), 95 | KubeConfig: kubeConfig, 96 | ContainerdSock: _config.GetFullTargetAssetFilename(utils.ContainerdSock), 97 | }, false) 98 | 99 | if error != nil { 100 | log.WithFields(log.Fields{"error": error}).Error("Failed generating environment") 101 | 102 | os.Exit(-1) 103 | } 104 | 105 | fmt.Println(content) 106 | }, 107 | } 108 | 109 | func init() { 110 | RootCmd.AddCommand(environmentCmd) 111 | } 112 | -------------------------------------------------------------------------------- /cmd/k8s-tew/generate.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/darxkies/k8s-tew/pkg/download" 5 | "github.com/darxkies/k8s-tew/pkg/generate" 6 | "github.com/darxkies/k8s-tew/pkg/utils" 7 | 8 | "os" 9 | 10 | log "github.com/sirupsen/logrus" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | var forceDownload bool 15 | var parallel bool 16 | var pullImages bool 17 | 18 | var generateCmd = &cobra.Command{ 19 | Use: "generate", 20 | Short: "Generate assets", 21 | Long: "Generate assets", 22 | Run: func(cmd *cobra.Command, args []string) { 23 | // Load config and check the rights 24 | if error := bootstrap(false); error != nil { 25 | log.WithFields(log.Fields{"error": error}).Error("Generate failed") 26 | 27 | os.Exit(-1) 28 | } 29 | 30 | if !_config.Config.Nodes.HasControllerNode() { 31 | log.WithFields(log.Fields{"error": "No controller node found"}).Error("Generate failed") 32 | 33 | os.Exit(-2) 34 | } 35 | 36 | if !_config.Config.Nodes.HasWorkerNode() { 37 | log.WithFields(log.Fields{"error": "No worker node found"}).Error("Generate failed") 38 | 39 | os.Exit(-3) 40 | } 41 | 42 | if !_config.Config.Nodes.HasStorageNode() { 43 | log.WithFields(log.Fields{"error": "No storage node found"}).Error("Generate failed") 44 | 45 | os.Exit(-4) 46 | } 47 | 48 | downloader := download.NewDownloader(_config, forceDownload, parallel, pullImages) 49 | generator := generate.NewGenerator(_config) 50 | 51 | utils.SetProgressSteps(2 + downloader.Steps() + generator.Steps() + 1) 52 | 53 | utils.ShowProgress() 54 | 55 | _config.Generate() 56 | 57 | log.Info("Generated config entries") 58 | 59 | utils.IncreaseProgressStep() 60 | 61 | if error := _config.Save(); error != nil { 62 | log.WithFields(log.Fields{"error": error}).Error("Generate failed") 63 | 64 | os.Exit(-1) 65 | } 66 | 67 | utils.IncreaseProgressStep() 68 | 69 | // Download binaries 70 | if error := downloader.DownloadBinaries(); error != nil { 71 | log.WithFields(log.Fields{"error": error}).Error("Generate failed") 72 | 73 | os.Exit(-1) 74 | } 75 | 76 | // Download binaries 77 | if error := generator.GenerateFiles(); error != nil { 78 | log.WithFields(log.Fields{"error": error}).Error("Generate failed") 79 | 80 | os.Exit(-1) 81 | } 82 | 83 | utils.HideProgress() 84 | 85 | log.Info("Done") 86 | }, 87 | } 88 | 89 | func init() { 90 | generateCmd.Flags().UintVarP(&commandRetries, "command-retries", "r", 300, "The count of command retries during the setup") 91 | generateCmd.Flags().BoolVar(&forceDownload, "force-download", false, "Force downloading all binary dependencies from the internet") 92 | generateCmd.Flags().BoolVar(¶llel, "parallel", false, "Download binary dependencies in parallel") 93 | generateCmd.Flags().BoolVar(&pullImages, "pull-images", false, "Pull and convert images to OCI to be deployed later on") 94 | RootCmd.AddCommand(generateCmd) 95 | } 96 | -------------------------------------------------------------------------------- /cmd/k8s-tew/initialize.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/darxkies/k8s-tew/pkg/config" 7 | "github.com/darxkies/k8s-tew/pkg/utils" 8 | 9 | log "github.com/sirupsen/logrus" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | var force bool 14 | 15 | var initializeCmd = &cobra.Command{ 16 | Use: "initialize", 17 | Short: "Initialize the configuration", 18 | Long: "Initialize the configuration", 19 | Run: func(cmd *cobra.Command, args []string) { 20 | _config = config.NewInternalConfig(getBaseDirectory()) 21 | 22 | if force { 23 | utils.SetProgressSteps(3) 24 | } else { 25 | utils.SetProgressSteps(2) 26 | } 27 | 28 | utils.ShowProgress() 29 | 30 | if force { 31 | log.Info("Forcing initialization") 32 | 33 | oldConfig := config.NewInternalConfig(getBaseDirectory()) 34 | 35 | // Extract Cluster ID from old configuration and pass it to the new configuration 36 | if error := oldConfig.Load(); error == nil { 37 | _config.Config.ClusterID = oldConfig.Config.ClusterID 38 | } 39 | 40 | utils.IncreaseProgressStep() 41 | 42 | } else { 43 | if error := _config.Load(); error == nil { 44 | log.WithFields(log.Fields{"error": "already initialized"}).Error("Initialize failed") 45 | 46 | os.Exit(-1) 47 | } 48 | } 49 | 50 | if error := _config.Save(); error != nil { 51 | log.WithFields(log.Fields{"error": error}).Error("Initialize failed") 52 | 53 | os.Exit(-1) 54 | } 55 | 56 | utils.IncreaseProgressStep() 57 | 58 | log.Info("Done") 59 | }, 60 | } 61 | 62 | func init() { 63 | initializeCmd.Flags().BoolVarP(&force, "force", "f", false, "Force initialization if already initialized. This will basically remove the previous configuration.") 64 | RootCmd.AddCommand(initializeCmd) 65 | } 66 | -------------------------------------------------------------------------------- /cmd/k8s-tew/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "path" 8 | 9 | "github.com/darxkies/k8s-tew/pkg/config" 10 | "github.com/darxkies/k8s-tew/pkg/utils" 11 | "github.com/darxkies/k8s-tew/pkg/version" 12 | 13 | log "github.com/sirupsen/logrus" 14 | 15 | "github.com/spf13/cobra" 16 | _ "github.com/spf13/viper" 17 | ) 18 | 19 | var debug *bool 20 | var hideProgress *bool 21 | var baseDirectory string 22 | var _config *config.InternalConfig 23 | 24 | func init() { 25 | utils.SetupLogger() 26 | } 27 | 28 | // RootCmd represents the base command when called without any subcommands 29 | var RootCmd = &cobra.Command{ 30 | Use: "k8s-tew", 31 | Short: utils.ProjectTitle, 32 | Long: utils.ProjectTitle, 33 | Run: func(cmd *cobra.Command, args []string) { 34 | fmt.Printf("Version: %s\n", version.Version) 35 | fmt.Printf("OS: %s\n", utils.GetOSNameAndRelease()) 36 | fmt.Println() 37 | 38 | _ = cmd.Help() 39 | }, 40 | } 41 | 42 | // getDefaultBaseDirectory returns the base directory based on the current working directory 43 | func getDefaultBaseDirectory() string { 44 | directory, error := os.Getwd() 45 | if error != nil { 46 | log.WithFields(log.Fields{"error": error}).Fatal("Failed to retrieve cwd") 47 | } 48 | 49 | return path.Join(directory, utils.BaseDirectory) 50 | } 51 | 52 | // getBaseDirectory returns the base directory pointing to the assets 53 | func getBaseDirectory() string { 54 | result := baseDirectory 55 | 56 | environmentBaseDirectory := os.Getenv(utils.K8sTewBaseDirectory) 57 | 58 | if len(environmentBaseDirectory) > 0 { 59 | result = environmentBaseDirectory 60 | } 61 | 62 | return result 63 | } 64 | 65 | // bootstrap loads the configuration and performs other checks such as the need for root rights 66 | func bootstrap(needsRoot bool) error { 67 | utils.SetDebug(*debug) 68 | utils.SupressProgress(*hideProgress) 69 | 70 | if needsRoot && !utils.IsRoot() { 71 | return errors.New("this program needs root rights") 72 | } 73 | 74 | _config = config.NewInternalConfig(getBaseDirectory()) 75 | 76 | return _config.Load() 77 | } 78 | 79 | func main() { 80 | debug = RootCmd.PersistentFlags().BoolP("debug", "d", false, "Show debug messages") 81 | hideProgress = RootCmd.PersistentFlags().Bool("hide-progress", false, "Hide progress") 82 | RootCmd.PersistentFlags().StringVar(&baseDirectory, "base-directory", getDefaultBaseDirectory(), "Base directory") 83 | 84 | if _error := RootCmd.Execute(); _error != nil { 85 | fmt.Println(_error) 86 | 87 | os.Exit(-1) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /cmd/k8s-tew/node-add.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "strings" 8 | 9 | "github.com/darxkies/k8s-tew/pkg/utils" 10 | 11 | log "github.com/sirupsen/logrus" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | var nodeName string 16 | var nodeIP string 17 | var nodeIndex uint 18 | var nodeStorageIndex uint 19 | var nodeLabels string 20 | var nodeSelf bool 21 | 22 | func addNode() error { 23 | // Load config and check the rights 24 | if error := bootstrap(false); error != nil { 25 | return error 26 | } 27 | 28 | utils.SetProgressSteps(1) 29 | 30 | var error error 31 | 32 | labels := []string{} 33 | 34 | for _, label := range strings.Split(nodeLabels, ",") { 35 | labels = append(labels, strings.Trim(label, "\n ")) 36 | } 37 | 38 | if nodeSelf { 39 | log.Debug("Adding self as node") 40 | 41 | // Get ip of the node 42 | nodeIP, error = utils.RunCommandWithOutput("ip route get 8.8.8.8 | cut -d ' ' -f 7") 43 | if error != nil { 44 | return error 45 | } 46 | 47 | // Parse the ip 48 | nodeIP = strings.Trim(nodeIP, "\n") 49 | 50 | // Throw error if the IP could not be retrieved 51 | if len(nodeIP) == 0 { 52 | return errors.New("Could not find own IP") 53 | } 54 | 55 | // Set name of the node 56 | nodeName, error = os.Hostname() 57 | if error != nil { 58 | return error 59 | } 60 | 61 | // Set labels 62 | labels = []string{utils.NodeBootstrapper, utils.NodeController, utils.NodeWorker, utils.NodeStorage} 63 | 64 | // Get public network settings 65 | network, error := utils.RunCommandWithOutput(fmt.Sprintf("ip address | grep %s | cut -d ' ' -f 6", nodeIP)) 66 | if error != nil { 67 | return error 68 | } 69 | 70 | // Set public network settings 71 | _config.Config.PublicNetwork = network 72 | 73 | // Set deployment directory by assigning the base directory 74 | _config.Config.DeploymentDirectory = _config.BaseDirectory 75 | } 76 | 77 | node, nodeName, error := _config.AddNode(nodeName, nodeIP, nodeIndex, nodeStorageIndex, labels) 78 | if error != nil { 79 | return error 80 | } 81 | 82 | log.WithFields(log.Fields{"name": nodeName, "ip": node.IP, "index": node.Index, "storage-index": node.StorageIndex, "labels": node.Labels}).Info("Node added") 83 | 84 | if error := _config.Save(); error != nil { 85 | return error 86 | } 87 | 88 | return nil 89 | } 90 | 91 | var nodeAddCmd = &cobra.Command{ 92 | Use: "node-add", 93 | Short: "Add or update a node", 94 | Long: "Add a node. This can be also called when updating a node, only the name has to be unique.", 95 | Run: func(cmd *cobra.Command, args []string) { 96 | if error := addNode(); error != nil { 97 | log.WithFields(log.Fields{"error": error}).Error("Failed to add node") 98 | 99 | os.Exit(-1) 100 | } 101 | }, 102 | } 103 | 104 | func init() { 105 | nodeAddCmd.Flags().StringVarP(&nodeName, "name", "n", "single-node", "The hostname of the node") 106 | nodeAddCmd.Flags().StringVarP(&nodeIP, "ip", "i", "192.168.100.50", "IP of the node") 107 | nodeAddCmd.Flags().UintVarP(&nodeIndex, "index", "x", 0, "The unique index of the node which should never be reused; if it is already in use a new one is assigned") 108 | nodeAddCmd.Flags().UintVarP(&nodeStorageIndex, "storage-index", "r", 0, "The unique index of the storage node which should never be reused; if it is already in use a new one is assigned") 109 | nodeAddCmd.Flags().StringVarP(&nodeLabels, "labels", "l", fmt.Sprintf("%s,%s", utils.NodeController, utils.NodeWorker), "The labels of the node which define the attributes of the node") 110 | nodeAddCmd.Flags().BoolVarP(&nodeSelf, "self", "s", false, "Add this machine by inferring the host's name & IP and by setting the labels controller,worker,bootstrapper - The public-network and the deployment-directory are also updated") 111 | RootCmd.AddCommand(nodeAddCmd) 112 | } 113 | -------------------------------------------------------------------------------- /cmd/k8s-tew/node-list.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/darxkies/k8s-tew/pkg/utils" 7 | log "github.com/sirupsen/logrus" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | func listNodes() error { 12 | // Load config and check the rights 13 | if error := bootstrap(false); error != nil { 14 | return error 15 | } 16 | 17 | utils.SetProgressSteps(1) 18 | 19 | for name, node := range _config.Config.Nodes { 20 | log.WithFields(log.Fields{"index": node.Index, "name": name, "ip": node.IP, "labels": node.Labels}).Info("Node") 21 | } 22 | 23 | return nil 24 | } 25 | 26 | var nodeListCmd = &cobra.Command{ 27 | Use: "node-list", 28 | Short: "List nodes", 29 | Long: "List nodes", 30 | Run: func(cmd *cobra.Command, args []string) { 31 | if error := listNodes(); error != nil { 32 | log.WithFields(log.Fields{"error": error}).Error("Failed to list nodes") 33 | 34 | os.Exit(-1) 35 | } 36 | }, 37 | } 38 | 39 | func init() { 40 | RootCmd.AddCommand(nodeListCmd) 41 | } 42 | -------------------------------------------------------------------------------- /cmd/k8s-tew/node-remove.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/darxkies/k8s-tew/pkg/utils" 7 | log "github.com/sirupsen/logrus" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var removeNodeName string 12 | 13 | func removeNode() error { 14 | // Load config and check the rights 15 | if error := bootstrap(false); error != nil { 16 | return error 17 | } 18 | 19 | utils.SetProgressSteps(1) 20 | 21 | if error := _config.RemoveNode(removeNodeName); error != nil { 22 | return error 23 | } 24 | 25 | log.WithFields(log.Fields{"name": removeNodeName}).Info("Node removed") 26 | 27 | if error := _config.Save(); error != nil { 28 | return error 29 | } 30 | 31 | return nil 32 | } 33 | 34 | var nodeRemoveCmd = &cobra.Command{ 35 | Use: "node-remove", 36 | Short: "Remove a node", 37 | Long: "Remove a node", 38 | Run: func(cmd *cobra.Command, args []string) { 39 | if error := removeNode(); error != nil { 40 | log.WithFields(log.Fields{"error": error}).Error("Failed to remove node") 41 | 42 | os.Exit(-1) 43 | } 44 | }, 45 | } 46 | 47 | func init() { 48 | nodeRemoveCmd.Flags().StringVarP(&removeNodeName, "name", "n", "", "Unique name of the node") 49 | RootCmd.AddCommand(nodeRemoveCmd) 50 | } 51 | -------------------------------------------------------------------------------- /cmd/k8s-tew/run.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/darxkies/k8s-tew/pkg/container" 7 | "github.com/darxkies/k8s-tew/pkg/servers" 8 | "github.com/darxkies/k8s-tew/pkg/utils" 9 | "github.com/spf13/cobra" 10 | 11 | log "github.com/sirupsen/logrus" 12 | ) 13 | 14 | var killContainers bool 15 | 16 | var runCmd = &cobra.Command{ 17 | Use: "run", 18 | Short: "Run", 19 | Long: "Run servers", 20 | Run: func(cmd *cobra.Command, args []string) { 21 | if error := bootstrap(true); error != nil { 22 | log.WithFields(log.Fields{"error": error}).Error("Failed initialization") 23 | 24 | os.Exit(-1) 25 | } 26 | 27 | if len(_config.Config.Nodes) == 0 { 28 | log.WithFields(log.Fields{"error": "no nodes defined"}).Error("Failed to run") 29 | 30 | os.Exit(-1) 31 | } 32 | 33 | if _config.Node == nil { 34 | log.WithFields(log.Fields{"error": "current host not found in the list of nodes"}).Error("Failed to run") 35 | 36 | os.Exit(-1) 37 | } 38 | 39 | serversContainer := servers.NewServers(_config) 40 | 41 | utils.SetProgressSteps(serversContainer.Steps()) 42 | 43 | utils.ShowProgress() 44 | 45 | if error := serversContainer.Run(commandRetries, func() { 46 | if killContainers { 47 | pods := container.NewPods(_config) 48 | 49 | pods.Kill() 50 | } 51 | 52 | }); error != nil { 53 | log.WithFields(log.Fields{"error": error}).Error("Failed to run") 54 | 55 | os.Exit(-1) 56 | } 57 | 58 | }, 59 | } 60 | 61 | func init() { 62 | runCmd.Flags().UintVarP(&commandRetries, "command-retries", "r", 300, "The count of command retries") 63 | runCmd.Flags().BoolVarP(&killContainers, "kill-containers", "k", true, "Kill containers when shutting down") 64 | RootCmd.AddCommand(runCmd) 65 | } 66 | -------------------------------------------------------------------------------- /data/data.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import "embed" 4 | 5 | //go:embed templates 6 | var Templates embed.FS 7 | -------------------------------------------------------------------------------- /data/templates/ceph/ceph.conf: -------------------------------------------------------------------------------- 1 | [global] 2 | fsid = {{.ClusterID}} 3 | 4 | auth cluster required = cephx 5 | auth service required = cephx 6 | auth client required = cephx 7 | 8 | public network = {{.PublicNetwork}} 9 | cluster network = {{.ClusterNetwork}} 10 | 11 | osd objectstore = bluestore 12 | osd journal size = 100 13 | osd max object name len = 256 14 | osd max object namespace len = 64 15 | osd pg bits = 11 16 | osd pgp bits = 11 17 | osd pool default size = {{len .StorageNodes}} 18 | osd pool default min size = 1 19 | 20 | rbd_default_features = 3 21 | 22 | fatal signal handlers = false 23 | 24 | mon_allow_pool_delete = true 25 | mon_clock_drift_allowed = 1 26 | 27 | log file = /dev/null 28 | 29 | [mon] 30 | keyring = {{$.MonKeyringTemplate}} 31 | mon data = {{$.MonDataTemplate}} 32 | 33 | [osd] 34 | keyring = {{$.OsdKeyringTemplate}} 35 | osd data = {{$.OsdDataTemplate}} 36 | osd journal = {{$.OsdJournalTemplate}} 37 | 38 | {{- range $index, $node := .StorageControllers}} 39 | 40 | [mon.{{$node.Name}}] 41 | host = {{$node.Name}} 42 | mon addr = {{$node.IP}}:6789 43 | {{- end}} 44 | 45 | {{- range $index, $node := .StorageNodes}} 46 | 47 | [mds.{{$node.Name}}] 48 | host = {{$node.Name}} 49 | {{- end}} 50 | 51 | {{- range $index, $node := .StorageNodes}} 52 | 53 | [ods.{{$node.StorageIndex}}] 54 | host = {{$node.Name}} 55 | {{- end}} 56 | -------------------------------------------------------------------------------- /data/templates/ceph/client-admin.keyring: -------------------------------------------------------------------------------- 1 | [client.admin] 2 | key = {{.Key | unescape}} 3 | auid = 0 4 | caps mds = "allow" 5 | caps mgr = "allow *" 6 | caps mon = "allow *" 7 | caps osd = "allow *" 8 | -------------------------------------------------------------------------------- /data/templates/ceph/client.keyring: -------------------------------------------------------------------------------- 1 | [client.{{.Name}}] 2 | key = {{.Key | unescape}} 3 | caps mon = "allow profile {{.Name}}" 4 | -------------------------------------------------------------------------------- /data/templates/ceph/monitor.keyring: -------------------------------------------------------------------------------- 1 | [mon.] 2 | key = {{.MonitorKey | unescape}} 3 | caps mon = "allow *" 4 | [client.admin] 5 | key = {{.ClientAdminKey | unescape}} 6 | auid = 0 7 | caps mds = "allow" 8 | caps mgr = "allow *" 9 | caps mon = "allow *" 10 | caps osd = "allow *" 11 | [client.bootstrap-mds] 12 | key = {{.ClientBootstrapMetadataServerKey | unescape}} 13 | caps mon = "allow profile bootstrap-mds" 14 | [client.bootstrap-osd] 15 | key = {{.ClientBootstrapObjectStorageKey | unescape}} 16 | caps mon = "allow profile bootstrap-osd" 17 | [client.bootstrap-rbd] 18 | key = {{.ClientBootstrapRadosBlockDeviceKey | unescape}} 19 | caps mon = "allow profile bootstrap-rbd" 20 | [client.bootstrap-rgw] 21 | key = {{.ClientBootstrapRadosGatewayKey | unescape}} 22 | caps mon = "allow profile bootstrap-rgw" 23 | [client.k8s-tew] 24 | key = {{.ClientK8STEWKey | unescape}} 25 | caps mon = "allow r" 26 | caps mds = "allow rw" 27 | caps osd = "allow rwx pool={{.CephRbdPoolName}}, allow class-read object_prefix rbd_children, allow rwx pool={{.CephFsPoolName}}, allow rwx pool={{.CephFsMetadataPoolName}}" 28 | caps mgr = "allow rw" 29 | -------------------------------------------------------------------------------- /data/templates/k8s/config-map.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: {{.Namespace}} 6 | 7 | --- 8 | apiVersion: v1 9 | kind: ConfigMap 10 | metadata: 11 | namespace: {{.Namespace}} 12 | name: {{.Name}} 13 | data: 14 | {{range $key, $value := .Data -}} 15 | {{$key}}: | 16 | {{ $value | file}} 17 | {{end}} 18 | -------------------------------------------------------------------------------- /data/templates/k8s/credentials.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: {{.Namespace}} 6 | 7 | --- 8 | apiVersion: v1 9 | kind: Secret 10 | metadata: 11 | namespace: {{.Namespace}} 12 | name: {{.SecretName}} 13 | type: Opaque 14 | data: 15 | {{range $key, $value := .Data -}} 16 | {{$key}}: "{{ $value | base64 }}" 17 | {{end}} 18 | -------------------------------------------------------------------------------- /data/templates/k8s/cri/containerd.toml: -------------------------------------------------------------------------------- 1 | version = 2 2 | root = "{{.ContainerdRootDirectory}}" 3 | state = "{{.ContainerdStateDirectory}}" 4 | plugin_dir = "" 5 | disabled_plugins = [] 6 | required_plugins = [] 7 | oom_score = 0 8 | 9 | [grpc] 10 | address = "{{.ContainerdSock}}" 11 | tcp_address = "" 12 | tcp_tls_cert = "" 13 | tcp_tls_key = "" 14 | uid = 0 15 | gid = 0 16 | max_recv_message_size = 16777216 17 | max_send_message_size = 16777216 18 | 19 | [ttrpc] 20 | address = "" 21 | uid = 0 22 | gid = 0 23 | 24 | [debug] 25 | address = "" 26 | uid = 0 27 | gid = 0 28 | level = "" 29 | 30 | [metrics] 31 | address = "" 32 | grpc_histogram = false 33 | 34 | [cgroup] 35 | path = "" 36 | 37 | [timeouts] 38 | "io.containerd.timeout.shim.cleanup" = "5s" 39 | "io.containerd.timeout.shim.load" = "5s" 40 | "io.containerd.timeout.shim.shutdown" = "3s" 41 | "io.containerd.timeout.task.state" = "2s" 42 | 43 | [plugins] 44 | [plugins."io.containerd.gc.v1.scheduler"] 45 | pause_threshold = 0.02 46 | deletion_threshold = 0 47 | mutation_threshold = 100 48 | schedule_delay = "0s" 49 | startup_delay = "100ms" 50 | [plugins."io.containerd.grpc.v1.cri"] 51 | disable_tcp_service = true 52 | stream_server_address = "{{.IP}}" 53 | stream_server_port = "0" 54 | stream_idle_timeout = "4h0m0s" 55 | enable_selinux = false 56 | sandbox_image = "{{.PauseImage}}" 57 | stats_collect_period = 10 58 | systemd_cgroup = false 59 | enable_tls_streaming = false 60 | max_container_log_line_size = 16384 61 | disable_cgroup = false 62 | disable_apparmor = false 63 | restrict_oom_score_adj = false 64 | max_concurrent_downloads = 3 65 | disable_proc_mount = false 66 | [plugins."io.containerd.grpc.v1.cri".containerd] 67 | snapshotter = "overlayfs" 68 | default_runtime_name = "runc" 69 | no_pivot = false 70 | [plugins."io.containerd.grpc.v1.cri".containerd.runtimes] 71 | [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc] 72 | runtime_type = "io.containerd.runc.v2" 73 | runtime_engine = "" 74 | runtime_root = "" 75 | privileged_without_host_devices = false 76 | [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] 77 | BinaryName = "{{.CRIBinariesDirectory}}/runc" 78 | [plugins."io.containerd.grpc.v1.cri".cni] 79 | bin_dir = "{{.CNIBinariesDirectory}}" 80 | conf_dir = "{{.CNIConfigDirectory}}" 81 | max_conf_num = 1 82 | conf_template = "" 83 | [plugins."io.containerd.grpc.v1.cri".registry] 84 | [plugins."io.containerd.grpc.v1.cri".registry.mirrors] 85 | [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"] 86 | endpoint = ["https://registry-1.docker.io"] 87 | [plugins."io.containerd.grpc.v1.cri".x509_key_pair_streaming] 88 | tls_cert_file = "" 89 | tls_key_file = "" 90 | [plugins."io.containerd.internal.v1.opt"] 91 | path = "/opt/containerd" 92 | [plugins."io.containerd.internal.v1.restart"] 93 | interval = "10s" 94 | [plugins."io.containerd.metadata.v1.bolt"] 95 | content_sharing_policy = "shared" 96 | [plugins."io.containerd.monitor.v1.cgroups"] 97 | no_prometheus = false 98 | [plugins."io.containerd.runtime.v1.linux"] 99 | shim = "{{.CRIBinariesDirectory}}/containerd-shim" 100 | runtime = "{{.CRIBinariesDirectory}}/runc" 101 | runtime_root = "" 102 | no_shim = false 103 | shim_debug = false 104 | [plugins."io.containerd.runtime.v2.task"] 105 | platforms = ["linux/amd64"] 106 | [plugins."io.containerd.service.v1.diff-service"] 107 | default = ["walking"] 108 | [plugins."io.containerd.snapshotter.v1.devmapper"] 109 | root_path = "" 110 | pool_name = "" 111 | base_image_size = "" 112 | -------------------------------------------------------------------------------- /data/templates/k8s/encryption-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: EncryptionConfig 3 | resources: 4 | - resources: 5 | - secrets 6 | providers: 7 | - aescbc: 8 | keys: 9 | - name: key1 10 | secret: {{.EncryptionKey | unescape}} 11 | - identity: {} 12 | -------------------------------------------------------------------------------- /data/templates/k8s/kube-proxy-configuration.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kubeproxy.config.k8s.io/v1alpha1 2 | kind: KubeProxyConfiguration 3 | clientConnection: 4 | kubeconfig: "{{.KubeConfig}}" 5 | clusterCIDR: "{{.ClusterCIDR}}" 6 | mode: "iptables" 7 | metricsBindAddress: "0.0.0.0:10249" 8 | -------------------------------------------------------------------------------- /data/templates/k8s/kube-scheduler-configuration.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kubescheduler.config.k8s.io/v1 2 | kind: KubeSchedulerConfiguration 3 | clientConnection: 4 | kubeconfig: "{{.KubeConfig}}" 5 | leaderElection: 6 | leaderElect: true 7 | -------------------------------------------------------------------------------- /data/templates/k8s/kubeconfig.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Config 3 | clusters: 4 | - cluster: 5 | certificate-authority-data: {{.CAData}} 6 | server: https://{{.APIServer}} 7 | name: kubernetes-the-easier-way 8 | users: 9 | - name: {{.Name}} 10 | user: 11 | client-certificate-data: {{.CertificateData}} 12 | client-key-data: {{.KeyData}} 13 | contexts: 14 | - context: 15 | cluster: kubernetes-the-easier-way 16 | user: {{.User}} 17 | name: default 18 | current-context: default 19 | -------------------------------------------------------------------------------- /data/templates/k8s/kubelet-configuration.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kubelet.config.k8s.io/v1beta1 2 | kind: KubeletConfiguration 3 | authentication: 4 | anonymous: 5 | enabled: false 6 | webhook: 7 | enabled: true 8 | x509: 9 | clientCAFile: "{{.CA}}" 10 | authorization: 11 | mode: Webhook 12 | clusterDomain: "cluster.local" 13 | clusterDNS: 14 | - "{{.ClusterDNSIP}}" 15 | podCIDR: "{{.PODCIDR}}" 16 | runtimeRequestTimeout: "15m" 17 | tlsCertFile: "{{.CertificateFilename}}" 18 | tlsPrivateKeyFile: "{{.KeyFilename}}" 19 | staticPodPath: "{{.StaticPodPath}}" 20 | failSwapOn: false 21 | resolvConf: "{{.ResolvConf}}" 22 | readOnlyPort: 10255 23 | maxPods: {{.MaxPods}} 24 | containerRuntimeEndpoint: {{.ContainerRuntimeEndpoint}} 25 | -------------------------------------------------------------------------------- /data/templates/k8s/lb/gobetween.toml: -------------------------------------------------------------------------------- 1 | [servers.kube-apiserver] 2 | bind = "0.0.0.0:{{ .LoadBalancerPort }}" 3 | protocol = "tcp" 4 | balance = "roundrobin" 5 | 6 | max_connections = 10000 7 | client_idle_timeout = "10m" 8 | backend_idle_timeout = "10m" 9 | backend_connection_timeout = "2s" 10 | 11 | [servers.kube-apiserver.discovery] 12 | kind = "static" 13 | static_list = [ {{ .KubeAPIServers | quoted_string_list }} ] 14 | -------------------------------------------------------------------------------- /data/templates/k8s/manifests/etcd.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | namespace: kube-system 5 | name: etcd 6 | labels: 7 | app.kubernetes.io/name: etcd 8 | app.kubernetes.io/instance: {{.Name}} 9 | cluster-relevant: "true" 10 | cluster-weight: "100" 11 | spec: 12 | hostNetwork: true 13 | containers: 14 | - name: etcd 15 | image: {{.EtcdImage}} 16 | command: 17 | - etcd 18 | - --advertise-client-urls=https://{{.NodeIP}}:2379 19 | - --cert-file={{.PemKubernetes}} 20 | - --client-cert-auth 21 | - --data-dir={{.EtcdDataDirectory}} 22 | - --initial-advertise-peer-urls=https://{{.NodeIP}}:2380 23 | - --initial-cluster={{.EtcdCluster}} 24 | - --initial-cluster-state=new 25 | - --initial-cluster-token=etcd-cluster 26 | - --key-file={{.PemKubernetesKey}} 27 | - --listen-client-urls=https://{{.NodeIP}}:2379 28 | - --listen-peer-urls=https://{{.NodeIP}}:2380 29 | - --name={{.Name}} 30 | - --peer-cert-file={{.PemKubernetes}} 31 | - --peer-client-cert-auth 32 | - --peer-key-file={{.PemKubernetesKey}} 33 | - --peer-trusted-ca-file={{.PemCA}} 34 | - --trusted-ca-file={{.PemCA}} 35 | - --listen-metrics-urls=http://{{.NodeIP}}:2381 36 | readinessProbe: 37 | httpGet: 38 | path: /health 39 | port: 2381 40 | scheme: HTTP 41 | initialDelaySeconds: 1 42 | timeoutSeconds: 5 43 | failureThreshold: 5 44 | livenessProbe: 45 | httpGet: 46 | path: /health 47 | port: 2381 48 | scheme: HTTP 49 | initialDelaySeconds: 10 50 | timeoutSeconds: 10 51 | failureThreshold: 5 52 | volumeMounts: 53 | - name: etcd-data-directory 54 | mountPath: {{.EtcdDataDirectory}} 55 | - name: pem-ca 56 | mountPath: {{.PemCA}} 57 | readOnly: true 58 | - name: pem-kubernetes 59 | mountPath: {{.PemKubernetes}} 60 | readOnly: true 61 | - name: pem-kubernetes-key 62 | mountPath: {{.PemKubernetesKey}} 63 | readOnly: true 64 | volumes: 65 | - name: etcd-data-directory 66 | hostPath: 67 | type: DirectoryOrCreate 68 | path: {{.EtcdDataDirectory}} 69 | - name: pem-ca 70 | hostPath: 71 | type: File 72 | path: {{.PemCA}} 73 | - name: pem-kubernetes 74 | hostPath: 75 | type: File 76 | path: {{.PemKubernetes}} 77 | - name: pem-kubernetes-key 78 | hostPath: 79 | type: File 80 | path: {{.PemKubernetesKey}} 81 | -------------------------------------------------------------------------------- /data/templates/k8s/manifests/gobetween.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | namespace: kube-system 5 | name: gobetween 6 | labels: 7 | cluster-relevant: "true" 8 | cluster-weight: "98" 9 | spec: 10 | hostNetwork: true 11 | containers: 12 | - name: gobetween 13 | image: {{.GobetweenImage}} 14 | command: 15 | - /gobetween 16 | - -c 17 | - {{.Config}} 18 | resources: 19 | requests: 20 | cpu: 100m 21 | volumeMounts: 22 | - name: config 23 | mountPath: {{.Config}} 24 | readOnly: true 25 | volumes: 26 | - name: config 27 | hostPath: 28 | type: File 29 | path: {{.Config}} 30 | -------------------------------------------------------------------------------- /data/templates/k8s/manifests/kube-apiserver.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | namespace: kube-system 5 | name: kube-apiserver 6 | labels: 7 | cluster-relevant: "true" 8 | cluster-weight: "95" 9 | spec: 10 | hostNetwork: true 11 | containers: 12 | - name: kube-apiserver 13 | image: {{.KubernetesImage}} 14 | command: 15 | - kube-apiserver 16 | - --advertise-address={{.NodeIP}} 17 | - --allow-privileged=true 18 | - --apiserver-count={{.ControllersCount}} 19 | - --audit-log-maxage=30 20 | - --audit-log-maxbackup=3 21 | - --audit-log-maxsize=100 22 | - --audit-log-path={{.AuditLog}} 23 | - --authorization-mode=Node,RBAC 24 | - --bind-address=0.0.0.0 25 | - --client-ca-file={{.PemCA}} 26 | - --enable-admission-plugins=NamespaceLifecycle,NodeRestriction,LimitRanger,ServiceAccount,DefaultStorageClass,ResourceQuota 27 | - --enable-aggregator-routing=true 28 | - --etcd-cafile={{.PemCA}} 29 | - --etcd-certfile={{.PemKubernetes}} 30 | - --etcd-keyfile={{.PemKubernetesKey}} 31 | - --etcd-servers={{.EtcdServers}} 32 | - --event-ttl=1h 33 | - --encryption-provider-config={{.EncryptionConfig}} 34 | - --kubelet-certificate-authority={{.PemCA}} 35 | - --kubelet-client-certificate={{.PemKubernetes}} 36 | - --kubelet-client-key={{.PemKubernetesKey}} 37 | - --proxy-client-cert-file={{.PemAggregator}} 38 | - --proxy-client-key-file={{.PemAggregatorKey}} 39 | - --requestheader-allowed-names=aggregator,admin,system:kube-controller-manager,system:kube-controller-manager,system:kube-scheduler,system:node:single-node 40 | - --requestheader-client-ca-file={{.PemCA}} 41 | - --requestheader-extra-headers-prefix=X-Remote-Extra- 42 | - --requestheader-group-headers=X-Remote-Group 43 | - --requestheader-username-headers=X-Remote-User 44 | - --runtime-config=api/all=true 45 | - --secure-port={{.APIServerPort}} 46 | - --service-account-signing-key-file={{.PemServiceAccountKey}} 47 | - --service-account-key-file={{.PemServiceAccount}} 48 | - --service-account-issuer=https://kubernetes.default.svc.{{.ClusterDomain}} 49 | - --service-cluster-ip-range={{.ClusterIPRange}} 50 | - --service-node-port-range=30000-32767 51 | - --tls-cert-file={{.PemKubernetes}} 52 | - --tls-private-key-file={{.PemKubernetesKey}} 53 | livenessProbe: 54 | failureThreshold: 8 55 | httpGet: 56 | scheme: HTTPS 57 | host: {{.NodeIP}} 58 | port: 6443 59 | path: /healthz 60 | initialDelaySeconds: 15 61 | timeoutSeconds: 15 62 | resources: 63 | requests: 64 | cpu: 250m 65 | volumeMounts: 66 | - name: pem-ca 67 | mountPath: {{.PemCA}} 68 | readOnly: true 69 | - name: pem-kubernetes 70 | mountPath: {{.PemKubernetes}} 71 | readOnly: true 72 | - name: pem-kubernetes-key 73 | mountPath: {{.PemKubernetesKey}} 74 | readOnly: true 75 | - name: pem-aggregator 76 | mountPath: {{.PemAggregator}} 77 | readOnly: true 78 | - name: pem-aggregator-key 79 | mountPath: {{.PemAggregatorKey}} 80 | readOnly: true 81 | - name: pem-service-account 82 | mountPath: {{.PemServiceAccount}} 83 | readOnly: true 84 | - name: pem-service-account-key 85 | mountPath: {{.PemServiceAccountKey}} 86 | readOnly: true 87 | - name: encryption-config 88 | mountPath: {{.EncryptionConfig}} 89 | readOnly: true 90 | - name: audit-log 91 | mountPath: {{.AuditLog}} 92 | volumes: 93 | - name: pem-ca 94 | hostPath: 95 | type: File 96 | path: {{.PemCA}} 97 | - name: pem-kubernetes 98 | hostPath: 99 | type: File 100 | path: {{.PemKubernetes}} 101 | - name: pem-kubernetes-key 102 | hostPath: 103 | type: File 104 | path: {{.PemKubernetesKey}} 105 | - name: pem-aggregator 106 | hostPath: 107 | type: File 108 | path: {{.PemAggregator}} 109 | - name: pem-aggregator-key 110 | hostPath: 111 | type: File 112 | path: {{.PemAggregatorKey}} 113 | - name: pem-service-account 114 | hostPath: 115 | type: File 116 | path: {{.PemServiceAccount}} 117 | - name: pem-service-account-key 118 | hostPath: 119 | type: File 120 | path: {{.PemServiceAccountKey}} 121 | - name: encryption-config 122 | hostPath: 123 | type: File 124 | path: {{.EncryptionConfig}} 125 | - name: audit-log 126 | hostPath: 127 | type: FileOrCreate 128 | path: {{.AuditLog}} 129 | -------------------------------------------------------------------------------- /data/templates/k8s/manifests/kube-controller-manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | namespace: kube-system 5 | name: kube-controller-manager 6 | labels: 7 | cluster-relevant: "true" 8 | cluster-weight: "90" 9 | spec: 10 | hostNetwork: true 11 | containers: 12 | - name: kube-controller-manager 13 | image: {{.KubernetesImage}} 14 | command: 15 | - kube-controller-manager 16 | - --bind-address=0.0.0.0 17 | - --allocate-node-cidrs=true 18 | - --cluster-cidr={{.ClusterCIDR}} 19 | - --cluster-name=kubernetes 20 | - --cluster-signing-cert-file={{.PemCA}} 21 | - --cluster-signing-key-file={{.PemCAKey}} 22 | - --kubeconfig={{.Kubeconfig}} 23 | - --leader-elect=true 24 | - --root-ca-file={{.PemCA}} 25 | - --service-account-private-key-file={{.PemServiceAccountKey}} 26 | - --service-cluster-ip-range={{.ClusterIPRange}} 27 | - --use-service-account-credentials=true 28 | - --authorization-always-allow-paths=/healthz,/readyz,/livez,/metrics 29 | livenessProbe: 30 | failureThreshold: 8 31 | httpGet: 32 | scheme: HTTPS 33 | host: 127.0.0.1 34 | port: 10257 35 | path: /healthz 36 | initialDelaySeconds: 15 37 | timeoutSeconds: 15 38 | resources: 39 | requests: 40 | cpu: 200m 41 | volumeMounts: 42 | - name: pem-ca 43 | mountPath: {{.PemCA}} 44 | readOnly: true 45 | - name: pem-ca-key 46 | mountPath: {{.PemCAKey}} 47 | readOnly: true 48 | - name: pem-service-account-key 49 | mountPath: {{.PemServiceAccountKey}} 50 | readOnly: true 51 | - name: kubeconfig 52 | mountPath: {{.Kubeconfig}} 53 | readOnly: true 54 | volumes: 55 | - name: pem-ca 56 | hostPath: 57 | type: File 58 | path: {{.PemCA}} 59 | - name: pem-ca-key 60 | hostPath: 61 | type: File 62 | path: {{.PemCAKey}} 63 | - name: pem-service-account-key 64 | hostPath: 65 | type: File 66 | path: {{.PemServiceAccountKey}} 67 | - name: kubeconfig 68 | hostPath: 69 | type: File 70 | path: {{.Kubeconfig}} 71 | -------------------------------------------------------------------------------- /data/templates/k8s/manifests/kube-proxy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | namespace: kube-system 5 | name: kube-proxy 6 | labels: 7 | cluster-relevant: "true" 8 | cluster-weight: "80" 9 | spec: 10 | hostNetwork: true 11 | containers: 12 | - name: kube-proxy 13 | image: {{.KubernetesImage}} 14 | command: 15 | - kube-proxy 16 | - --config={{.KubeProxyConfig}} 17 | securityContext: 18 | privileged: true 19 | livenessProbe: 20 | failureThreshold: 8 21 | httpGet: 22 | scheme: HTTP 23 | host: 127.0.0.1 24 | port: 10249 25 | path: /healthz 26 | initialDelaySeconds: 15 27 | timeoutSeconds: 15 28 | resources: 29 | requests: 30 | cpu: 200m 31 | volumeMounts: 32 | - name: kube-proxy-config 33 | mountPath: {{.KubeProxyConfig}} 34 | readOnly: true 35 | - name: kube-proxy-kubeconfig 36 | mountPath: {{.KubeProxyKubeconfig}} 37 | readOnly: true 38 | - name: lib-modules 39 | mountPath: /lib/modules 40 | readOnly: true 41 | volumes: 42 | - name: kube-proxy-config 43 | hostPath: 44 | type: File 45 | path: {{.KubeProxyConfig}} 46 | - name: kube-proxy-kubeconfig 47 | hostPath: 48 | type: File 49 | path: {{.KubeProxyKubeconfig}} 50 | - name: lib-modules 51 | hostPath: 52 | type: Directory 53 | path: /lib/modules 54 | -------------------------------------------------------------------------------- /data/templates/k8s/manifests/kube-scheduler.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | namespace: kube-system 5 | name: kube-scheduler 6 | labels: 7 | cluster-relevant: "true" 8 | cluster-weight: "85" 9 | spec: 10 | hostNetwork: true 11 | containers: 12 | - name: kube-scheduler 13 | image: {{.KubernetesImage}} 14 | command: 15 | - kube-scheduler 16 | - --config={{.KubeSchedulerConfig}} 17 | - --authorization-always-allow-paths=/healthz,/readyz,/livez,/metrics 18 | livenessProbe: 19 | failureThreshold: 8 20 | httpGet: 21 | scheme: HTTPS 22 | host: 127.0.0.1 23 | port: 10259 24 | path: /healthz 25 | initialDelaySeconds: 15 26 | timeoutSeconds: 15 27 | resources: 28 | requests: 29 | cpu: 100m 30 | volumeMounts: 31 | - name: kube-scheduler-config 32 | mountPath: {{.KubeSchedulerConfig}} 33 | readOnly: true 34 | - name: kube-scheduler-kubeconfig 35 | mountPath: {{.KubeSchedulerKubeconfig}} 36 | readOnly: true 37 | volumes: 38 | - name: kube-scheduler-config 39 | hostPath: 40 | type: File 41 | path: {{.KubeSchedulerConfig}} 42 | - name: kube-scheduler-kubeconfig 43 | hostPath: 44 | type: File 45 | path: {{.KubeSchedulerKubeconfig}} 46 | -------------------------------------------------------------------------------- /data/templates/k8s/manifests/virtual-ip.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | namespace: kube-system 5 | name: {{.Type}}-virtual-ip 6 | labels: 7 | cluster-relevant: "true" 8 | cluster-weight: "99" 9 | spec: 10 | hostNetwork: true 11 | containers: 12 | - name: virtual-ip 13 | image: {{.VirtualIPImage}} 14 | command: 15 | - /virtual-ip 16 | - -id 17 | - {{.ID}} 18 | - -bind 19 | - {{.Bind}} 20 | - -peers 21 | - {{.Peers}} 22 | - -interface 23 | - {{.Interface}} 24 | - -virtual-ip 25 | - {{.VirtualIP}} 26 | securityContext: 27 | privileged: true 28 | -------------------------------------------------------------------------------- /data/templates/k8s/service-account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: {{.Name}} 5 | namespace: {{.Namespace}} 6 | --- 7 | apiVersion: rbac.authorization.k8s.io/v1 8 | kind: ClusterRoleBinding 9 | metadata: 10 | name: {{.Name}} 11 | roleRef: 12 | apiGroup: rbac.authorization.k8s.io 13 | kind: ClusterRole 14 | name: cluster-admin 15 | subjects: 16 | - kind: ServiceAccount 17 | name: {{.Name}} 18 | namespace: {{.Namespace}} 19 | -------------------------------------------------------------------------------- /data/templates/k8s/setup/dns/coredns.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: coredns 6 | namespace: {{.Namespace}} 7 | labels: 8 | app.kubernetes.io/instance: "k8s-tew" 9 | k8s-app: coredns 10 | kubernetes.io/cluster-service: "true" 11 | kubernetes.io/name: "CoreDNS" 12 | app.kubernetes.io/name: coredns 13 | data: 14 | Corefile: |- 15 | .:53 { 16 | errors 17 | health { 18 | lameduck 10s 19 | } 20 | ready 21 | kubernetes {{.ClusterDomain}} in-addr.arpa ip6.arpa { 22 | pods verified 23 | fallthrough in-addr.arpa ip6.arpa 24 | ttl 30 25 | } 26 | prometheus 0.0.0.0:9153 27 | forward . /etc/resolv.conf 28 | cache 30 29 | loop 30 | reload 31 | loadbalance 32 | } 33 | 34 | --- 35 | apiVersion: v1 36 | kind: ServiceAccount 37 | metadata: 38 | name: coredns 39 | namespace: {{.Namespace}} 40 | 41 | --- 42 | apiVersion: rbac.authorization.k8s.io/v1 43 | kind: ClusterRole 44 | metadata: 45 | name: coredns 46 | labels: 47 | app.kubernetes.io/instance: "k8s-tew" 48 | k8s-app: coredns 49 | kubernetes.io/cluster-service: "true" 50 | kubernetes.io/name: "CoreDNS" 51 | app.kubernetes.io/name: coredns 52 | rules: 53 | - apiGroups: 54 | - "" 55 | resources: 56 | - endpoints 57 | - services 58 | - pods 59 | - namespaces 60 | verbs: 61 | - list 62 | - watch 63 | - apiGroups: 64 | - discovery.k8s.io 65 | resources: 66 | - endpointslices 67 | verbs: 68 | - list 69 | - watch 70 | 71 | --- 72 | apiVersion: rbac.authorization.k8s.io/v1 73 | kind: ClusterRoleBinding 74 | metadata: 75 | name: coredns 76 | labels: 77 | app.kubernetes.io/instance: "k8s-tew" 78 | k8s-app: coredns 79 | kubernetes.io/cluster-service: "true" 80 | kubernetes.io/name: "CoreDNS" 81 | app.kubernetes.io/name: coredns 82 | roleRef: 83 | apiGroup: rbac.authorization.k8s.io 84 | kind: ClusterRole 85 | name: coredns 86 | subjects: 87 | - kind: ServiceAccount 88 | name: coredns 89 | namespace: {{.Namespace}} 90 | 91 | --- 92 | apiVersion: v1 93 | kind: Service 94 | metadata: 95 | name: coredns 96 | namespace: {{.Namespace}} 97 | annotations: 98 | prometheus.io/port: "9153" 99 | prometheus.io/scrape: "true" 100 | labels: 101 | app.kubernetes.io/instance: "k8s-tew" 102 | k8s-app: coredns 103 | kubernetes.io/cluster-service: "true" 104 | kubernetes.io/name: "CoreDNS" 105 | app.kubernetes.io/name: coredns 106 | spec: 107 | selector: 108 | app.kubernetes.io/instance: "k8s-tew" 109 | k8s-app: coredns 110 | app.kubernetes.io/name: coredns 111 | clusterIP: {{.ClusterDNSIP}} 112 | ports: 113 | - {"name":"udp-53","port":53,"protocol":"UDP","targetPort":53} 114 | - {"name":"tcp-53","port":53,"protocol":"TCP","targetPort":53} 115 | - {"name":"metrics","port": 9153,"protocol": "TCP"} 116 | type: ClusterIP 117 | 118 | --- 119 | apiVersion: apps/v1 120 | kind: Deployment 121 | metadata: 122 | name: coredns 123 | namespace: {{.Namespace}} 124 | labels: 125 | app.kubernetes.io/instance: "k8s-tew" 126 | k8s-app: coredns 127 | kubernetes.io/cluster-service: "true" 128 | kubernetes.io/name: "CoreDNS" 129 | app.kubernetes.io/name: coredns 130 | spec: 131 | replicas: 2 132 | strategy: 133 | type: RollingUpdate 134 | rollingUpdate: 135 | maxUnavailable: 1 136 | maxSurge: 25% 137 | selector: 138 | matchLabels: 139 | app.kubernetes.io/instance: "k8s-tew" 140 | k8s-app: coredns 141 | app.kubernetes.io/name: coredns 142 | template: 143 | metadata: 144 | labels: 145 | k8s-app: coredns 146 | app.kubernetes.io/name: coredns 147 | app.kubernetes.io/instance: "k8s-tew" 148 | spec: 149 | terminationGracePeriodSeconds: 30 150 | serviceAccountName: coredns 151 | dnsPolicy: Default 152 | containers: 153 | - name: "coredns" 154 | image: {{.CoreDNSImage}} 155 | imagePullPolicy: IfNotPresent 156 | args: [ "-conf", "/etc/coredns/Corefile" ] 157 | volumeMounts: 158 | - name: config-volume 159 | mountPath: /etc/coredns 160 | resources: 161 | limits: 162 | cpu: 100m 163 | memory: 128Mi 164 | requests: 165 | cpu: 100m 166 | memory: 128Mi 167 | ports: 168 | - {"containerPort":53,"name":"udp-53","protocol":"UDP"} 169 | - {"containerPort":53,"name":"tcp-53","protocol":"TCP"} 170 | - {"containerPort":9153,"name":"metrics","protocol":"TCP"} 171 | livenessProbe: 172 | httpGet: 173 | path: /health 174 | port: 8080 175 | scheme: HTTP 176 | initialDelaySeconds: 60 177 | periodSeconds: 10 178 | timeoutSeconds: 5 179 | successThreshold: 1 180 | failureThreshold: 5 181 | readinessProbe: 182 | httpGet: 183 | path: /ready 184 | port: 8181 185 | scheme: HTTP 186 | initialDelaySeconds: 30 187 | periodSeconds: 5 188 | timeoutSeconds: 5 189 | successThreshold: 1 190 | failureThreshold: 1 191 | securityContext: 192 | allowPrivilegeEscalation: false 193 | capabilities: 194 | add: 195 | - NET_BIND_SERVICE 196 | drop: 197 | - ALL 198 | readOnlyRootFilesystem: true 199 | volumes: 200 | - name: config-volume 201 | configMap: 202 | name: coredns 203 | items: 204 | - key: Corefile 205 | path: Corefile 206 | -------------------------------------------------------------------------------- /data/templates/k8s/setup/ingress/letsencrypt-cluster-issuer.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: ClusterIssuer 3 | metadata: 4 | name: letsencrypt-production 5 | spec: 6 | acme: 7 | server: https://acme-v02.api.letsencrypt.org/directory 8 | email: "{{.Email}}" 9 | solvers: 10 | - selector: {} 11 | http01: 12 | ingress: 13 | class: nginx 14 | privateKeySecretRef: 15 | name: letsencrypt-production 16 | -------------------------------------------------------------------------------- /data/templates/k8s/setup/kubelet-setup.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | annotations: 5 | rbac.authorization.kubernetes.io/autoupdate: "true" 6 | labels: 7 | kubernetes.io/bootstrapping: rbac-defaults 8 | name: system:kube-apiserver-to-kubelet 9 | rules: 10 | - apiGroups: 11 | - "" 12 | resources: 13 | - nodes/proxy 14 | - nodes/stats 15 | - nodes/log 16 | - nodes/spec 17 | - nodes/metrics 18 | verbs: 19 | - "*" 20 | --- 21 | apiVersion: rbac.authorization.k8s.io/v1 22 | kind: ClusterRoleBinding 23 | metadata: 24 | name: system:kube-apiserver 25 | namespace: "" 26 | roleRef: 27 | apiGroup: rbac.authorization.k8s.io 28 | kind: ClusterRole 29 | name: system:kube-apiserver-to-kubelet 30 | subjects: 31 | - apiGroup: rbac.authorization.k8s.io 32 | kind: User 33 | name: kubernetes 34 | -------------------------------------------------------------------------------- /data/templates/k8s/setup/miscellaneous/wordpress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: {{.Namespace}} 5 | 6 | --- 7 | apiVersion: v1 8 | kind: PersistentVolumeClaim 9 | metadata: 10 | name: mysql-pv-claim 11 | namespace: {{.Namespace}} 12 | labels: 13 | app: wordpress 14 | spec: 15 | accessModes: 16 | - ReadWriteOnce 17 | resources: 18 | requests: 19 | storage: 2Gi 20 | 21 | --- 22 | apiVersion: v1 23 | kind: PersistentVolumeClaim 24 | metadata: 25 | name: wordpress-pv-claim 26 | namespace: {{.Namespace}} 27 | labels: 28 | app: wordpress 29 | spec: 30 | accessModes: 31 | - ReadWriteOnce 32 | resources: 33 | requests: 34 | storage: 2Gi 35 | 36 | --- 37 | apiVersion: apps/v1 38 | kind: Deployment 39 | metadata: 40 | name: mysql 41 | namespace: {{.Namespace}} 42 | labels: 43 | app: wordpress 44 | spec: 45 | selector: 46 | matchLabels: 47 | app: wordpress 48 | tier: mysql 49 | strategy: 50 | type: Recreate 51 | template: 52 | metadata: 53 | labels: 54 | app: wordpress 55 | tier: mysql 56 | spec: 57 | containers: 58 | - image: {{.MySQLImage}} 59 | name: mysql 60 | args: 61 | - --default-authentication-plugin=mysql_native_password 62 | env: 63 | - name: MYSQL_ROOT_PASSWORD 64 | value: changeme 65 | - name: MYSQL_USER 66 | value: wordpress 67 | - name: MYSQL_PASSWORD 68 | value: changeme 69 | - name: MYSQL_DATABASE 70 | value: wordpress 71 | ports: 72 | - containerPort: 3306 73 | name: mysql 74 | protocol: TCP 75 | volumeMounts: 76 | - name: mysql-persistent-storage 77 | mountPath: /var/lib/mysql 78 | volumes: 79 | - name: mysql-persistent-storage 80 | persistentVolumeClaim: 81 | claimName: mysql-pv-claim 82 | 83 | --- 84 | apiVersion: apps/v1 85 | kind: Deployment 86 | metadata: 87 | name: wordpress 88 | namespace: {{.Namespace}} 89 | labels: 90 | app: wordpress 91 | spec: 92 | selector: 93 | matchLabels: 94 | app: wordpress 95 | strategy: 96 | type: Recreate 97 | template: 98 | metadata: 99 | labels: 100 | app: wordpress 101 | tier: frontend 102 | spec: 103 | containers: 104 | - image: {{.WordPressImage}} 105 | name: wordpress 106 | env: 107 | - name: WORDPRESS_DB_HOST 108 | value: mysql 109 | - name: WORDPRESS_DB_USER 110 | value: wordpress 111 | - name: WORDPRESS_DB_PASSWORD 112 | value: changeme 113 | - name: WORDPRESS_DB_NAME 114 | value: wordpress 115 | livenessProbe: 116 | httpGet: 117 | path: /wp-admin/install.php 118 | port: wordpress 119 | periodSeconds: 5 120 | timeoutSeconds: 5 121 | ports: 122 | - containerPort: 80 123 | name: wordpress 124 | protocol: TCP 125 | volumeMounts: 126 | - name: wordpress-persistent-storage 127 | mountPath: /var/www/html 128 | volumes: 129 | - name: wordpress-persistent-storage 130 | persistentVolumeClaim: 131 | claimName: wordpress-pv-claim 132 | 133 | --- 134 | apiVersion: v1 135 | kind: Service 136 | metadata: 137 | name: mysql 138 | namespace: {{.Namespace}} 139 | labels: 140 | app: wordpress 141 | spec: 142 | ports: 143 | - port: 3306 144 | protocol: TCP 145 | selector: 146 | app: wordpress 147 | tier: mysql 148 | clusterIP: None 149 | 150 | --- 151 | apiVersion: v1 152 | kind: Service 153 | metadata: 154 | name: wordpress 155 | namespace: {{.Namespace}} 156 | labels: 157 | app: wordpress 158 | spec: 159 | ports: 160 | - port: 80 161 | nodePort: {{.WordPressPort}} 162 | protocol: TCP 163 | selector: 164 | app: wordpress 165 | tier: frontend 166 | type: NodePort 167 | 168 | --- 169 | apiVersion: networking.k8s.io/v1 170 | kind: Ingress 171 | metadata: 172 | name: wordpress 173 | namespace: {{.Namespace}} 174 | annotations: 175 | ingress.kubernetes.io/ssl-redirect: "true" 176 | kubernetes.io/tls-acme: "true" 177 | cert-manager.io/cluster-issuer: letsencrypt-production 178 | spec: 179 | tls: 180 | - hosts: 181 | - {{.WordPressIngressDomain}} 182 | secretName: wordpress-letsencrypt 183 | rules: 184 | - host: {{.WordPressIngressDomain}} 185 | http: 186 | paths: 187 | - path: / 188 | pathType: Prefix 189 | backend: 190 | service: 191 | name: wordpress 192 | port: 193 | number: 80 194 | -------------------------------------------------------------------------------- /data/templates/k8s/setup/monitoring/alert-manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | namespace: {{.Namespace}} 5 | name: alert-manager 6 | --- 7 | apiVersion: v1 8 | kind: ConfigMap 9 | metadata: 10 | namespace: {{.Namespace}} 11 | name: alert-manager-config 12 | labels: 13 | k8s-app: alert-manager 14 | data: 15 | alert-manager.yml: | 16 | global: null 17 | receivers: 18 | - name: default-receiver 19 | route: 20 | group_interval: 5m 21 | group_wait: 10s 22 | receiver: default-receiver 23 | repeat_interval: 3h 24 | --- 25 | apiVersion: v1 26 | kind: Service 27 | metadata: 28 | namespace: {{.Namespace}} 29 | name: alert-manager-operated 30 | labels: 31 | k8s-app: alert-manager 32 | spec: 33 | type: "ClusterIP" 34 | clusterIP: None 35 | selector: 36 | k8s-app: alert-manager 37 | ports: 38 | - name: mesh 39 | port: 6783 40 | protocol: TCP 41 | targetPort: 6783 42 | - name: http 43 | port: 9093 44 | protocol: TCP 45 | targetPort: 9093 46 | --- 47 | apiVersion: v1 48 | kind: Service 49 | metadata: 50 | namespace: {{.Namespace}} 51 | name: alert-manager 52 | labels: 53 | k8s-app: alert-manager 54 | spec: 55 | ports: 56 | - name: http 57 | port: 9093 58 | protocol: TCP 59 | targetPort: 9093 60 | selector: 61 | k8s-app: alert-manager 62 | type: "ClusterIP" 63 | --- 64 | apiVersion: apps/v1 65 | kind: StatefulSet 66 | metadata: 67 | namespace: {{.Namespace}} 68 | name: alert-manager 69 | labels: 70 | k8s-app: alert-manager 71 | spec: 72 | serviceName: "alert-manager-operated" 73 | replicas: {{ .AlertManagerCount }} 74 | podManagementPolicy: OrderedReady 75 | updateStrategy: 76 | type: RollingUpdate 77 | revisionHistoryLimit: 10 78 | selector: 79 | matchLabels: 80 | k8s-app: alert-manager 81 | template: 82 | metadata: 83 | labels: 84 | k8s-app: alert-manager 85 | spec: 86 | serviceAccountName: alert-manager 87 | initContainers: 88 | - name: "init-chmod-data" 89 | image: {{ .BusyboxImage }} 90 | imagePullPolicy: "IfNotPresent" 91 | command: ["chmod", "777", "/data"] 92 | volumeMounts: 93 | - name: alert-manager-data 94 | mountPath: "/data" 95 | containers: 96 | - name: alert-manager 97 | image: {{ .AlertManagerImage }} 98 | imagePullPolicy: IfNotPresent 99 | args: 100 | - --config.file=/etc/config/alert-manager.yml 101 | - --storage.path=/data 102 | - --web.listen-address=:9093 103 | - --web.route-prefix=/ 104 | - --cluster.listen-address=$(POD_IP):6783 105 | {{ range $index, $element := .AlertManagerCounts -}} 106 | - --cluster.peer=alert-manager-{{ $element }}.alert-manager-operated.{{$.Namespace}}.svc:6783 107 | {{ end -}} 108 | - --log.level=debug 109 | env: 110 | - name: POD_IP 111 | valueFrom: 112 | fieldRef: 113 | apiVersion: v1 114 | fieldPath: status.podIP 115 | ports: 116 | - containerPort: 9093 117 | name: http 118 | protocol: TCP 119 | - containerPort: 6783 120 | name: mesh 121 | protocol: TCP 122 | readinessProbe: 123 | httpGet: 124 | path: /#/status 125 | port: 9093 126 | initialDelaySeconds: 30 127 | timeoutSeconds: 30 128 | volumeMounts: 129 | - name: config-volume 130 | mountPath: /etc/config 131 | - name: alert-manager-data 132 | mountPath: "/data" 133 | resources: 134 | limits: 135 | cpu: 10m 136 | memory: 50Mi 137 | requests: 138 | cpu: 10m 139 | memory: 50Mi 140 | volumes: 141 | - name: config-volume 142 | configMap: 143 | name: alert-manager-config 144 | affinity: 145 | podAntiAffinity: 146 | requiredDuringSchedulingIgnoredDuringExecution: 147 | - labelSelector: 148 | matchExpressions: 149 | - key: k8s-app 150 | operator: In 151 | values: 152 | - alert-manager 153 | topologyKey: "kubernetes.io/hostname" 154 | volumeClaimTemplates: 155 | - metadata: 156 | name: alert-manager-data 157 | spec: 158 | accessModes: 159 | - ReadWriteOnce 160 | resources: 161 | requests: 162 | storage: "{{ .AlertManagerSize }}Gi" 163 | -------------------------------------------------------------------------------- /data/templates/k8s/setup/monitoring/node-exporter.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: prometheus-node-exporter 6 | namespace: {{.Namespace}} 7 | labels: 8 | app.kubernetes.io/component: metrics 9 | app.kubernetes.io/part-of: prometheus-node-exporter 10 | app.kubernetes.io/name: prometheus-node-exporter 11 | imagePullSecrets: 12 | [] 13 | 14 | --- 15 | kind: ClusterRole 16 | apiVersion: rbac.authorization.k8s.io/v1 17 | metadata: 18 | name: node-exporter 19 | labels: 20 | app: node-exporter 21 | rules: 22 | - apiGroups: ['extensions'] 23 | resources: ['podsecuritypolicies'] 24 | verbs: ['use'] 25 | resourceNames: 26 | - prometheus-node-exporter 27 | 28 | --- 29 | apiVersion: rbac.authorization.k8s.io/v1 30 | kind: ClusterRoleBinding 31 | metadata: 32 | name: node-exporter 33 | labels: 34 | app: node-exporter 35 | roleRef: 36 | apiGroup: rbac.authorization.k8s.io 37 | kind: ClusterRole 38 | name: node-exporter 39 | subjects: 40 | - kind: ServiceAccount 41 | name: node-exporter 42 | namespace: {{.Namespace}} 43 | 44 | --- 45 | apiVersion: v1 46 | kind: Service 47 | metadata: 48 | name: prometheus-node-exporter 49 | namespace: {{.Namespace}} 50 | labels: 51 | app.kubernetes.io/component: metrics 52 | app.kubernetes.io/part-of: prometheus-node-exporter 53 | app.kubernetes.io/name: prometheus-node-exporter 54 | app.kubernetes.io/instance: k8s-tew 55 | annotations: 56 | prometheus.io/scrape: "true" 57 | spec: 58 | type: ClusterIP 59 | ports: 60 | - port: 9100 61 | targetPort: 9100 62 | protocol: TCP 63 | name: metrics 64 | selector: 65 | app.kubernetes.io/name: prometheus-node-exporter 66 | app.kubernetes.io/instance: k8s-tew 67 | --- 68 | apiVersion: apps/v1 69 | kind: DaemonSet 70 | metadata: 71 | name: prometheus-node-exporter 72 | namespace: {{.Namespace}} 73 | labels: 74 | app.kubernetes.io/component: metrics 75 | app.kubernetes.io/part-of: prometheus-node-exporter 76 | app.kubernetes.io/name: prometheus-node-exporter 77 | app.kubernetes.io/instance: k8s-tew 78 | spec: 79 | selector: 80 | matchLabels: 81 | app.kubernetes.io/name: prometheus-node-exporter 82 | app.kubernetes.io/instance: k8s-tew 83 | revisionHistoryLimit: 10 84 | updateStrategy: 85 | rollingUpdate: 86 | maxUnavailable: 1 87 | type: RollingUpdate 88 | template: 89 | metadata: 90 | annotations: 91 | cluster-autoscaler.kubernetes.io/safe-to-evict: "true" 92 | labels: 93 | app.kubernetes.io/component: metrics 94 | app.kubernetes.io/part-of: prometheus-node-exporter 95 | app.kubernetes.io/name: prometheus-node-exporter 96 | app.kubernetes.io/instance: k8s-tew 97 | spec: 98 | automountServiceAccountToken: false 99 | securityContext: 100 | fsGroup: 65534 101 | runAsGroup: 65534 102 | runAsNonRoot: true 103 | runAsUser: 65534 104 | serviceAccountName: prometheus-node-exporter 105 | containers: 106 | - name: node-exporter 107 | image: {{ .NodeExporterImage }} 108 | imagePullPolicy: IfNotPresent 109 | args: 110 | - --path.procfs=/host/proc 111 | - --path.sysfs=/host/sys 112 | - --path.rootfs=/host/root 113 | - --path.udev.data=/host/root/run/udev/data 114 | - --web.listen-address=[$(HOST_IP)]:9100 115 | securityContext: 116 | readOnlyRootFilesystem: true 117 | env: 118 | - name: HOST_IP 119 | value: 0.0.0.0 120 | ports: 121 | - name: metrics 122 | containerPort: 9100 123 | protocol: TCP 124 | livenessProbe: 125 | failureThreshold: 3 126 | httpGet: 127 | httpHeaders: 128 | path: / 129 | port: 9100 130 | scheme: HTTP 131 | initialDelaySeconds: 0 132 | periodSeconds: 10 133 | successThreshold: 1 134 | timeoutSeconds: 1 135 | readinessProbe: 136 | failureThreshold: 3 137 | httpGet: 138 | httpHeaders: 139 | path: / 140 | port: 9100 141 | scheme: HTTP 142 | initialDelaySeconds: 0 143 | periodSeconds: 10 144 | successThreshold: 1 145 | timeoutSeconds: 1 146 | volumeMounts: 147 | - name: proc 148 | mountPath: /host/proc 149 | readOnly: true 150 | - name: sys 151 | mountPath: /host/sys 152 | readOnly: true 153 | - name: root 154 | mountPath: /host/root 155 | mountPropagation: HostToContainer 156 | readOnly: true 157 | hostNetwork: true 158 | hostPID: true 159 | nodeSelector: 160 | kubernetes.io/os: linux 161 | tolerations: 162 | - effect: NoSchedule 163 | operator: Exists 164 | volumes: 165 | - name: proc 166 | hostPath: 167 | path: /proc 168 | - name: sys 169 | hostPath: 170 | path: /sys 171 | - name: root 172 | hostPath: 173 | path: / 174 | -------------------------------------------------------------------------------- /data/templates/k8s/setup/storage/ceph-secrets.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: {{.Namespace}} 5 | 6 | --- 7 | apiVersion: v1 8 | kind: Secret 9 | metadata: 10 | name: ceph-admin 11 | namespace: {{.Namespace}} 12 | type: "kubernetes.io/rbd" 13 | data: 14 | key: {{.ClientAdminKey | base64}} 15 | 16 | --- 17 | apiVersion: v1 18 | kind: Secret 19 | metadata: 20 | name: ceph-k8s-tew 21 | namespace: {{.Namespace}} 22 | type: "kubernetes.io/rbd" 23 | data: 24 | key: {{.ClientK8STEWKey | base64}} 25 | 26 | --- 27 | apiVersion: v1 28 | kind: Secret 29 | metadata: 30 | name: csi-rbd-secret 31 | namespace: {{.Namespace}} 32 | data: 33 | adminID: {{"admin" | base64}} 34 | adminKey: {{.ClientAdminKey | base64}} 35 | userID: {{"k8s-tew" | base64}} 36 | userKey: {{.ClientK8STEWKey | base64}} 37 | 38 | --- 39 | apiVersion: v1 40 | kind: Secret 41 | metadata: 42 | name: csi-cephfs-secret 43 | namespace: {{.Namespace}} 44 | data: 45 | adminID: {{"admin" | base64}} 46 | adminKey: {{.ClientAdminKey | base64}} 47 | userID: {{"k8s-tew" | base64}} 48 | userKey: {{.ClientK8STEWKey | base64}} 49 | -------------------------------------------------------------------------------- /data/templates/system/environment.sh: -------------------------------------------------------------------------------- 1 | export PATH="{{.K8STEWPath}}":"{{.K8SPath}}":"{{.EtcdPath}}":"{{.CRIPath}}":"{{.CNIPath}}":"{{.VeleroPath}}":"{{.HostPath}}":{{.CurrentPath}} 2 | {{- if .KubeConfig }} 3 | export KUBECONFIG="{{.KubeConfig}}" 4 | {{- end }} 5 | export VELERO_NAMESPACE=backup 6 | export CONTAINER_RUNTIME_ENDPOINT=unix://{{.ContainerdSock}} 7 | export CONTAINERD_NAMESPACE=k8s.io 8 | export ETCDCTL_API=3 9 | -------------------------------------------------------------------------------- /data/templates/system/k8s-tew.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description={{.ProjectTitle}} 3 | 4 | [Service] 5 | ExecStart={{.Command}} run --base-directory={{.BaseDirectory}} --hide-progress --kill-containers=true 6 | Restart=on-failure 7 | KillSignal=SIGINT 8 | KillMode=process 9 | RestartSec=5 10 | LimitNOFILE=1000000 11 | Delegate=yes 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /data/templates/system/k8s-tew.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | export K8S_TEW_BASE_DIRECTORY={{.BaseDirectory}} 4 | 5 | source <({{.Binary}} environment) 6 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/.nojekyll -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = . 8 | BUILDDIR = _build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/_build/doctrees/about.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/doctrees/about.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/environment.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/doctrees/environment.pickle -------------------------------------------------------------------------------- /docs/_build/doctrees/features.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/doctrees/features.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/index.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/doctrees/index.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/installation.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/doctrees/installation.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/quickstart.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/doctrees/quickstart.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/setups.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/doctrees/setups.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/troubleshooting.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/doctrees/troubleshooting.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/usage.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/doctrees/usage.doctree -------------------------------------------------------------------------------- /docs/_build/html/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: ee51ab9b04222949e74f650086225f02 4 | tags: 645f666f9bcd5a90fca523b33c5a78b7 5 | -------------------------------------------------------------------------------- /docs/_build/html/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/html/.nojekyll -------------------------------------------------------------------------------- /docs/_build/html/_sources/about.rst.txt: -------------------------------------------------------------------------------- 1 | About 2 | ===== 3 | 4 | k8s-tew_ is a CLI tool to install a Kubernetes Cluster (local, single-node, multi-node or HA-cluster) on Bare Metal. It installs the most essential components needed by a cluster such as networking, storage, monitoring, logging, backuping/restoring and so on. Once the nodes are configured, k8s-tew is started on each node to supervise the cluster components. k8s-tew is also used internally to generate the configuration files for a Ceph cluster and also to start the necessary Ceph deamons with the right parameters. The Ceph functionality can be used with or without Kubernetes. 5 | 6 | .. _k8s-tew: https://github.com/darxkies/k8s-tew 7 | 8 | Why 9 | --- 10 | 11 | Kubernetes_ is a fairly complex project. For a newbie it is hard to understand and also to use. While `Kelsey Hightower's Kubernetes The Hard Way `_, on which this project is based, helps a lot to understand Kubernetes, it is optimized for the use with Google Cloud Platform. 12 | 13 | Thus, this project's aim is to give newbies an easy to use tool that allows them to tinker with Kubernetes and later on to install HA production grade clusters. 14 | 15 | .. _Kubernetes: https://kubernetes.io/ 16 | 17 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/features.rst.txt: -------------------------------------------------------------------------------- 1 | Features 2 | ======== 3 | 4 | * HA cluster setup passes all CNCF conformance tests (Kubernetes `1.10 `_, `1.11 `_, `1.12 `_, `1.13 `_, `1.14 `_, `1.15 `_, `1.16 `_, `1.17 `_, `1.18 `_, `1.19 `_, `1.20 `_, `1.21 `_, `1.22 `_ , `1.23 `_ , `1.24 `_ , `1.25 `_ , `1.26 `_ & `1.27 `_) 5 | * Container Management: `Containerd `_ 6 | * Networking: `Calico `_ 7 | * Ingress: `NGINX Ingress `_ and `cert-manager `_ for `Let's Encrypt `_ 8 | * Storage: `Ceph/RBD `_ 9 | * Metrics: `metering-metrics `_ and `Heapster `_ 10 | * Monitoring: `Prometheus `_ and `Grafana `_ 11 | * Logging: `Fluent-Bit `_, `Elasticsearch `_, `Kibana `_ and `Cerebro `_ 12 | * Backups: `Velero `_, `Restic `_ and `Minio `_ 13 | * Cluster Load Balancing: `MetalLB `_ 14 | * Controller Load Balancing: `gobetween `_ 15 | * Package Manager: `Helm `_ 16 | * Dashboard: `Kubernetes Dashboard `_ 17 | * The communication between the components is encrypted 18 | * RBAC is enabled 19 | * The controllers and the workers have Floating/Virtual IPs 20 | * Integrated Load Balancer for the API Servers 21 | * Support for deployment to a HA cluster using ssh 22 | * Only the changed files are deployed 23 | * No `Docker `_ installation required 24 | * No cloud provider required 25 | * Single binary without any dependencies 26 | * Runs locally 27 | * Nodes management from the command line 28 | * Downloads all the used binaries (kubernetes, calico, ceph...) from the Internet 29 | * Pull Images, Convert them to OCI and import them on the cluster for offline installations 30 | * Uses systemd to install itself as a service on the remote machine 31 | * Installs `WordPress `_ and `MySQL `_ to test drive the installation 32 | 33 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/index.rst.txt: -------------------------------------------------------------------------------- 1 | .. spelling:: 2 | 3 | prem 4 | 5 | .. k8s-tew documentation master file, created by 6 | sphinx-quickstart on Sat Oct 6 19:46:44 2018. 7 | You can adapt this file completely to your liking, but it should at least 8 | contain the root `toctree` directive. 9 | 10 | Welcome to k8s-tew's documentation! 11 | =================================== 12 | 13 | .. toctree:: 14 | :maxdepth: 2 15 | :caption: Contents: 16 | 17 | about 18 | features 19 | quickstart 20 | installation 21 | usage 22 | setups 23 | troubleshooting 24 | 25 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/installation.rst.txt: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | The commands in the upcoming sections will assume that k8s-tew is going to be installed in the directory :file:`/usr/local/bin`. That means that the aforementioned directory exists and it is included in the PATH. If that is not the case use the following commands: 5 | 6 | .. code:: shell 7 | 8 | sudo mkdir -p /usr/local/bin 9 | export PATH=/usr/local/bin:$PATH 10 | 11 | Binary 12 | ------ 13 | 14 | The x86 64-bit binary can be downloaded from the following address: https://github.com/darxkies/k8s-tew/releases 15 | 16 | Additionally the these commands can be used to download it and install it in :file:`/usr/local/bin` 17 | 18 | .. code:: shell 19 | 20 | curl -s https://api.github.com/repos/darxkies/k8s-tew/releases/latest | grep "browser_download_url" | cut -d : -f 2,3 | tr -d \" | sudo wget -O /usr/local/bin/k8s-tew -qi - 21 | sudo chmod a+x /usr/local/bin/k8s-tew 22 | 23 | Source 24 | ------ 25 | 26 | To compile it from source you will need a Go (version 1.15+) environment, Git, Make and Docker installed. Once everything is installed, enter the following commands: 27 | 28 | .. code:: shell 29 | 30 | export GOPATH=~/go 31 | export PATH=$GOPATH/bin:$PATH 32 | mkdir -p $GOPATH/src/github.com/darxkies 33 | cd $GOPATH/src/github.com/darxkies 34 | git clone https://github.com/darxkies/k8s-tew.git 35 | cd k8s-tew 36 | make 37 | sudo mv k8s-tew /usr/local/bin 38 | 39 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/quickstart.rst.txt: -------------------------------------------------------------------------------- 1 | Quick Start 2 | =========== 3 | 4 | The following snippet will create a cluster on the host computer or in a virtual machine: 5 | 6 | .. code:: shell 7 | 8 | # Switch to user root 9 | sudo su - 10 | 11 | # Download Binary 12 | wget https://github.com/darxkies/k8s-tew/releases/download/2.4.1/k8s-tew 13 | chmod a+x k8s-tew 14 | 15 | # Everything is installed relative to the root directory 16 | export K8S_TEW_BASE_DIRECTORY=/ 17 | 18 | # Initialize cluster configuration 19 | ./k8s-tew initialize 20 | 21 | # Node the current machine to the cluster (the settings such as IP and hostname are inferred) 22 | ./k8s-tew node-add -s 23 | 24 | # Only on Ubuntu 20.04 to solve any DNS related issues 25 | ./k8s-tew configure --resolv-conf=/run/systemd/resolve/resolv.conf 26 | 27 | # Generate artefacts (e.g. certificates, configurations and so on) 28 | ./k8s-tew generate 29 | 30 | # Activate and start service 31 | systemctl daemon-reload 32 | systemctl enable k8s-tew 33 | systemctl start k8s-tew 34 | 35 | # Activate environment variables and switch back to root 36 | exit 37 | sudo su - 38 | 39 | # Watch the pods being installed 40 | watch -n 1 kubectl get pods --all-namespaces 41 | 42 | .. note:: You will need at least 20GB HDD, 8GB RAM and 4 CPU Cores. 43 | .. note:: To use k8s-tew with Vagrant take a look at `https://github.com/darxkies/k8s-tew/tree/2.4.1/setup `_. 44 | 45 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/setups.rst.txt: -------------------------------------------------------------------------------- 1 | Setups 2 | ====== 3 | 4 | Vagrant 5 | ------- 6 | 7 | Vagrant/VirtualBox can be used to test drive k8s-tew. The host is used to bootstrap the cluster which runs in VirtualBox. The Vagrantfile included in the repository can be used for single-node/multi-node & Ubuntu 20.04/CentOS 8.2 setups. 8 | 9 | The Vagrantfile can be configured using the environment variables: 10 | 11 | - OS - define the operating system. It accepts ubuntu, the default value, and centos. 12 | - MULTI_NODE - if set then a HA cluster is generated. Otherwise a single-node setup is used. 13 | - CONTROLLERS - defines the number of controller nodes. The default number is 3. 14 | - WORKERS - specifies the number of worker nodes. The default number is 2. 15 | - SSH_PUBLIC_KEY - if this environment variable is not set, then :file:`$HOME/.ssh/id_rsa` is used by default. 16 | - IP_PREFIX - this value is used to generate the IP addresses of the nodes. If not set 192.168.100 will be used. The single node has the IP address 192.168.100.50. The controllers start with the IP address 192.168.100.200 and the workers with 192.168.100.100. 17 | - CONTROLLERS_RAM - amount of RAM for one controller 18 | - WORKERS_RAM - amount of RAM for one worker 19 | - STORAGE_RAM - amount of RAM for one storage 20 | - CONTROLLERS_CPUS - number of CPUs per controller 21 | - WORKERS_CPUS - number of CPUs per worker 22 | - STORAGE_CPUS - number of CPUs per storage 23 | 24 | .. note:: The multi-node setup with the default settings needs about 20GB RAM for itself. 25 | 26 | 27 | Usage 28 | ^^^^^ 29 | 30 | The directory called :file:`setup` (`https://github.com/darxkies/k8s-tew/tree/2.4.1/setup `_) contains sub-directories for various cluster setup configurations: 31 | 32 | - local - it starts a single-node cluster locally without using any kind of virtualization. This kind of setup needs root rights. It is meant for local development where it might be important to fire the cluster up and shut it down fast. If you want it to start automatically, take a look at the quickstart section. 33 | - ubuntu-single-node - Ubuntu 20.04 single-node cluster. It needs about 8GB Ram. 34 | - ubuntu-multi-node - Ubuntu 20.04 HA cluster. It needs around 20GB Ram. 35 | - centos-single-node - CentOS 8.2 single-node cluster. It needs about 8GB Ram. 36 | - centos-multi-node - CentOS 8.2 HA cluster. It needs around 20GB Ram. 37 | 38 | .. note:: Regardless of the setup, once the deployment is done it will take a while to download all required containers from the internet. So better use kubectl to check the status of the pods. 39 | 40 | .. note:: For the local setup, to access the Kubernetes Dashboard use the internal IP address (e.g. 192.168.x.y or 10.x.y.z) and not 127.0.0.1/localhost. Depending on the hardware used, it might take a while until it starts and setups everything. 41 | 42 | Create 43 | ^^^^^^ 44 | 45 | Change to one of the sub-directories and enter the following command to start the cluster: 46 | 47 | .. code:: shell 48 | 49 | make 50 | 51 | .. note:: This will destroy any existing VMs, creates new VMs and performs all the steps (forced initialization, configuration, generation and deployment) to create the cluster. 52 | 53 | Stop 54 | ^^^^^^ 55 | 56 | For the local setup, just press CTRL+C. 57 | 58 | For the other setups enter: 59 | 60 | .. code:: shell 61 | 62 | make halt 63 | 64 | Start 65 | ^^^^^ 66 | 67 | To start an existing setup/VMs enter: 68 | 69 | .. code:: shell 70 | 71 | make up 72 | 73 | .. note:: This and the following commands work only for Vagrant based setups. 74 | 75 | SSH 76 | ^^^ 77 | 78 | For single-node setups enter: 79 | 80 | .. code:: shell 81 | 82 | make ssh 83 | 84 | And for multi-node setups: 85 | 86 | .. code:: shell 87 | 88 | make ssh-controller00 89 | make ssh-controller01 90 | make ssh-controller02 91 | make ssh-worker00 92 | make ssh-worker01 93 | 94 | Kubernetes Dashboard 95 | ^^^^^^^^^^^^^^^^^^^^ 96 | 97 | This will display the token, and then it will open the web browser pointing to the address of Kubernetes Dashboard: 98 | 99 | .. code:: shell 100 | 101 | make dashboard 102 | 103 | Ingress Port Forwarding 104 | ^^^^^^^^^^^^^^^^^^^^^^^ 105 | 106 | In order to start port forwarding from your host's ports 80 and 443 to Vagrant's VMs for Ingress enter: 107 | 108 | .. code:: shell 109 | 110 | make forward-80 111 | make forward-443 112 | 113 | .. note:: Both commands are blocking. So you need two different terminal sessions. 114 | 115 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/troubleshooting.rst.txt: -------------------------------------------------------------------------------- 1 | Troubleshooting 2 | =============== 3 | 4 | k8s-tew enables logging for all components by default. The log files are stored in :file:`{base-directory}/var/log/k8s-tew/`. 5 | 6 | -------------------------------------------------------------------------------- /docs/_build/html/_static/_sphinx_javascript_frameworks_compat.js: -------------------------------------------------------------------------------- 1 | /* Compatability shim for jQuery and underscores.js. 2 | * 3 | * Copyright Sphinx contributors 4 | * Released under the two clause BSD licence 5 | */ 6 | 7 | /** 8 | * small helper function to urldecode strings 9 | * 10 | * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL 11 | */ 12 | jQuery.urldecode = function(x) { 13 | if (!x) { 14 | return x 15 | } 16 | return decodeURIComponent(x.replace(/\+/g, ' ')); 17 | }; 18 | 19 | /** 20 | * small helper function to urlencode strings 21 | */ 22 | jQuery.urlencode = encodeURIComponent; 23 | 24 | /** 25 | * This function returns the parsed url parameters of the 26 | * current request. Multiple values per key are supported, 27 | * it will always return arrays of strings for the value parts. 28 | */ 29 | jQuery.getQueryParameters = function(s) { 30 | if (typeof s === 'undefined') 31 | s = document.location.search; 32 | var parts = s.substr(s.indexOf('?') + 1).split('&'); 33 | var result = {}; 34 | for (var i = 0; i < parts.length; i++) { 35 | var tmp = parts[i].split('=', 2); 36 | var key = jQuery.urldecode(tmp[0]); 37 | var value = jQuery.urldecode(tmp[1]); 38 | if (key in result) 39 | result[key].push(value); 40 | else 41 | result[key] = [value]; 42 | } 43 | return result; 44 | }; 45 | 46 | /** 47 | * highlight a given string on a jquery object by wrapping it in 48 | * span elements with the given class name. 49 | */ 50 | jQuery.fn.highlightText = function(text, className) { 51 | function highlight(node, addItems) { 52 | if (node.nodeType === 3) { 53 | var val = node.nodeValue; 54 | var pos = val.toLowerCase().indexOf(text); 55 | if (pos >= 0 && 56 | !jQuery(node.parentNode).hasClass(className) && 57 | !jQuery(node.parentNode).hasClass("nohighlight")) { 58 | var span; 59 | var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); 60 | if (isInSVG) { 61 | span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); 62 | } else { 63 | span = document.createElement("span"); 64 | span.className = className; 65 | } 66 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 67 | node.parentNode.insertBefore(span, node.parentNode.insertBefore( 68 | document.createTextNode(val.substr(pos + text.length)), 69 | node.nextSibling)); 70 | node.nodeValue = val.substr(0, pos); 71 | if (isInSVG) { 72 | var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); 73 | var bbox = node.parentElement.getBBox(); 74 | rect.x.baseVal.value = bbox.x; 75 | rect.y.baseVal.value = bbox.y; 76 | rect.width.baseVal.value = bbox.width; 77 | rect.height.baseVal.value = bbox.height; 78 | rect.setAttribute('class', className); 79 | addItems.push({ 80 | "parent": node.parentNode, 81 | "target": rect}); 82 | } 83 | } 84 | } 85 | else if (!jQuery(node).is("button, select, textarea")) { 86 | jQuery.each(node.childNodes, function() { 87 | highlight(this, addItems); 88 | }); 89 | } 90 | } 91 | var addItems = []; 92 | var result = this.each(function() { 93 | highlight(this, addItems); 94 | }); 95 | for (var i = 0; i < addItems.length; ++i) { 96 | jQuery(addItems[i].parent).before(addItems[i].target); 97 | } 98 | return result; 99 | }; 100 | 101 | /* 102 | * backward compatibility for jQuery.browser 103 | * This will be supported until firefox bug is fixed. 104 | */ 105 | if (!jQuery.browser) { 106 | jQuery.uaMatch = function(ua) { 107 | ua = ua.toLowerCase(); 108 | 109 | var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || 110 | /(webkit)[ \/]([\w.]+)/.exec(ua) || 111 | /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || 112 | /(msie) ([\w.]+)/.exec(ua) || 113 | ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || 114 | []; 115 | 116 | return { 117 | browser: match[ 1 ] || "", 118 | version: match[ 2 ] || "0" 119 | }; 120 | }; 121 | jQuery.browser = {}; 122 | jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; 123 | } 124 | -------------------------------------------------------------------------------- /docs/_build/html/_static/css/badge_only.css: -------------------------------------------------------------------------------- 1 | .clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/html/_static/css/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/html/_static/css/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/lato-bold-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/html/_static/css/fonts/lato-bold-italic.woff -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/lato-bold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/html/_static/css/fonts/lato-bold-italic.woff2 -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/html/_static/css/fonts/lato-bold.woff -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/html/_static/css/fonts/lato-bold.woff2 -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/lato-normal-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/html/_static/css/fonts/lato-normal-italic.woff -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/lato-normal-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/html/_static/css/fonts/lato-normal-italic.woff2 -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/lato-normal.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/html/_static/css/fonts/lato-normal.woff -------------------------------------------------------------------------------- /docs/_build/html/_static/css/fonts/lato-normal.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/html/_static/css/fonts/lato-normal.woff2 -------------------------------------------------------------------------------- /docs/_build/html/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * doctools.js 3 | * ~~~~~~~~~~~ 4 | * 5 | * Base JavaScript utilities for all Sphinx HTML documentation. 6 | * 7 | * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | "use strict"; 12 | 13 | const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ 14 | "TEXTAREA", 15 | "INPUT", 16 | "SELECT", 17 | "BUTTON", 18 | ]); 19 | 20 | const _ready = (callback) => { 21 | if (document.readyState !== "loading") { 22 | callback(); 23 | } else { 24 | document.addEventListener("DOMContentLoaded", callback); 25 | } 26 | }; 27 | 28 | /** 29 | * Small JavaScript module for the documentation. 30 | */ 31 | const Documentation = { 32 | init: () => { 33 | Documentation.initDomainIndexTable(); 34 | Documentation.initOnKeyListeners(); 35 | }, 36 | 37 | /** 38 | * i18n support 39 | */ 40 | TRANSLATIONS: {}, 41 | PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), 42 | LOCALE: "unknown", 43 | 44 | // gettext and ngettext don't access this so that the functions 45 | // can safely bound to a different name (_ = Documentation.gettext) 46 | gettext: (string) => { 47 | const translated = Documentation.TRANSLATIONS[string]; 48 | switch (typeof translated) { 49 | case "undefined": 50 | return string; // no translation 51 | case "string": 52 | return translated; // translation exists 53 | default: 54 | return translated[0]; // (singular, plural) translation tuple exists 55 | } 56 | }, 57 | 58 | ngettext: (singular, plural, n) => { 59 | const translated = Documentation.TRANSLATIONS[singular]; 60 | if (typeof translated !== "undefined") 61 | return translated[Documentation.PLURAL_EXPR(n)]; 62 | return n === 1 ? singular : plural; 63 | }, 64 | 65 | addTranslations: (catalog) => { 66 | Object.assign(Documentation.TRANSLATIONS, catalog.messages); 67 | Documentation.PLURAL_EXPR = new Function( 68 | "n", 69 | `return (${catalog.plural_expr})` 70 | ); 71 | Documentation.LOCALE = catalog.locale; 72 | }, 73 | 74 | /** 75 | * helper function to focus on search bar 76 | */ 77 | focusSearchBar: () => { 78 | document.querySelectorAll("input[name=q]")[0]?.focus(); 79 | }, 80 | 81 | /** 82 | * Initialise the domain index toggle buttons 83 | */ 84 | initDomainIndexTable: () => { 85 | const toggler = (el) => { 86 | const idNumber = el.id.substr(7); 87 | const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); 88 | if (el.src.substr(-9) === "minus.png") { 89 | el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; 90 | toggledRows.forEach((el) => (el.style.display = "none")); 91 | } else { 92 | el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; 93 | toggledRows.forEach((el) => (el.style.display = "")); 94 | } 95 | }; 96 | 97 | const togglerElements = document.querySelectorAll("img.toggler"); 98 | togglerElements.forEach((el) => 99 | el.addEventListener("click", (event) => toggler(event.currentTarget)) 100 | ); 101 | togglerElements.forEach((el) => (el.style.display = "")); 102 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); 103 | }, 104 | 105 | initOnKeyListeners: () => { 106 | // only install a listener if it is really needed 107 | if ( 108 | !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && 109 | !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS 110 | ) 111 | return; 112 | 113 | document.addEventListener("keydown", (event) => { 114 | // bail for input elements 115 | if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; 116 | // bail with special keys 117 | if (event.altKey || event.ctrlKey || event.metaKey) return; 118 | 119 | if (!event.shiftKey) { 120 | switch (event.key) { 121 | case "ArrowLeft": 122 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; 123 | 124 | const prevLink = document.querySelector('link[rel="prev"]'); 125 | if (prevLink && prevLink.href) { 126 | window.location.href = prevLink.href; 127 | event.preventDefault(); 128 | } 129 | break; 130 | case "ArrowRight": 131 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; 132 | 133 | const nextLink = document.querySelector('link[rel="next"]'); 134 | if (nextLink && nextLink.href) { 135 | window.location.href = nextLink.href; 136 | event.preventDefault(); 137 | } 138 | break; 139 | } 140 | } 141 | 142 | // some keyboard layouts may need Shift to get / 143 | switch (event.key) { 144 | case "/": 145 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; 146 | Documentation.focusSearchBar(); 147 | event.preventDefault(); 148 | } 149 | }); 150 | }, 151 | }; 152 | 153 | // quick alias for translations 154 | const _ = Documentation.gettext; 155 | 156 | _ready(Documentation.init); 157 | -------------------------------------------------------------------------------- /docs/_build/html/_static/documentation_options.js: -------------------------------------------------------------------------------- 1 | const DOCUMENTATION_OPTIONS = { 2 | VERSION: '', 3 | LANGUAGE: 'en', 4 | COLLAPSE_INDEX: false, 5 | BUILDER: 'html', 6 | FILE_SUFFIX: '.html', 7 | LINK_SUFFIX: '.html', 8 | HAS_SOURCE: true, 9 | SOURCELINK_SUFFIX: '.txt', 10 | NAVIGATION_WITH_KEYS: false, 11 | SHOW_SEARCH_SUMMARY: true, 12 | ENABLE_SEARCH_SHORTCUTS: true, 13 | }; -------------------------------------------------------------------------------- /docs/_build/html/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/html/_static/file.png -------------------------------------------------------------------------------- /docs/_build/html/_static/js/badge_only.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=4)}({4:function(e,t,r){}}); -------------------------------------------------------------------------------- /docs/_build/html/_static/js/theme.js: -------------------------------------------------------------------------------- 1 | !function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t0 63 | var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 64 | var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 65 | var s_v = "^(" + C + ")?" + v; // vowel in stem 66 | 67 | this.stemWord = function (w) { 68 | var stem; 69 | var suffix; 70 | var firstch; 71 | var origword = w; 72 | 73 | if (w.length < 3) 74 | return w; 75 | 76 | var re; 77 | var re2; 78 | var re3; 79 | var re4; 80 | 81 | firstch = w.substr(0,1); 82 | if (firstch == "y") 83 | w = firstch.toUpperCase() + w.substr(1); 84 | 85 | // Step 1a 86 | re = /^(.+?)(ss|i)es$/; 87 | re2 = /^(.+?)([^s])s$/; 88 | 89 | if (re.test(w)) 90 | w = w.replace(re,"$1$2"); 91 | else if (re2.test(w)) 92 | w = w.replace(re2,"$1$2"); 93 | 94 | // Step 1b 95 | re = /^(.+?)eed$/; 96 | re2 = /^(.+?)(ed|ing)$/; 97 | if (re.test(w)) { 98 | var fp = re.exec(w); 99 | re = new RegExp(mgr0); 100 | if (re.test(fp[1])) { 101 | re = /.$/; 102 | w = w.replace(re,""); 103 | } 104 | } 105 | else if (re2.test(w)) { 106 | var fp = re2.exec(w); 107 | stem = fp[1]; 108 | re2 = new RegExp(s_v); 109 | if (re2.test(stem)) { 110 | w = stem; 111 | re2 = /(at|bl|iz)$/; 112 | re3 = new RegExp("([^aeiouylsz])\\1$"); 113 | re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 114 | if (re2.test(w)) 115 | w = w + "e"; 116 | else if (re3.test(w)) { 117 | re = /.$/; 118 | w = w.replace(re,""); 119 | } 120 | else if (re4.test(w)) 121 | w = w + "e"; 122 | } 123 | } 124 | 125 | // Step 1c 126 | re = /^(.+?)y$/; 127 | if (re.test(w)) { 128 | var fp = re.exec(w); 129 | stem = fp[1]; 130 | re = new RegExp(s_v); 131 | if (re.test(stem)) 132 | w = stem + "i"; 133 | } 134 | 135 | // Step 2 136 | re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; 137 | if (re.test(w)) { 138 | var fp = re.exec(w); 139 | stem = fp[1]; 140 | suffix = fp[2]; 141 | re = new RegExp(mgr0); 142 | if (re.test(stem)) 143 | w = stem + step2list[suffix]; 144 | } 145 | 146 | // Step 3 147 | re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; 148 | if (re.test(w)) { 149 | var fp = re.exec(w); 150 | stem = fp[1]; 151 | suffix = fp[2]; 152 | re = new RegExp(mgr0); 153 | if (re.test(stem)) 154 | w = stem + step3list[suffix]; 155 | } 156 | 157 | // Step 4 158 | re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; 159 | re2 = /^(.+?)(s|t)(ion)$/; 160 | if (re.test(w)) { 161 | var fp = re.exec(w); 162 | stem = fp[1]; 163 | re = new RegExp(mgr1); 164 | if (re.test(stem)) 165 | w = stem; 166 | } 167 | else if (re2.test(w)) { 168 | var fp = re2.exec(w); 169 | stem = fp[1] + fp[2]; 170 | re2 = new RegExp(mgr1); 171 | if (re2.test(stem)) 172 | w = stem; 173 | } 174 | 175 | // Step 5 176 | re = /^(.+?)e$/; 177 | if (re.test(w)) { 178 | var fp = re.exec(w); 179 | stem = fp[1]; 180 | re = new RegExp(mgr1); 181 | re2 = new RegExp(meq1); 182 | re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 183 | if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) 184 | w = stem; 185 | } 186 | re = /ll$/; 187 | re2 = new RegExp(mgr1); 188 | if (re.test(w) && re2.test(w)) { 189 | re = /.$/; 190 | w = w.replace(re,""); 191 | } 192 | 193 | // and turn initial Y back to y 194 | if (firstch == "y") 195 | w = firstch.toLowerCase() + w.substr(1); 196 | return w; 197 | } 198 | } 199 | 200 | -------------------------------------------------------------------------------- /docs/_build/html/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/html/_static/minus.png -------------------------------------------------------------------------------- /docs/_build/html/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/html/_static/plus.png -------------------------------------------------------------------------------- /docs/_build/html/_static/pygments.css: -------------------------------------------------------------------------------- 1 | pre { line-height: 125%; } 2 | td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } 3 | span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } 4 | td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } 5 | span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } 6 | .highlight .hll { background-color: #ffffcc } 7 | .highlight { background: #f8f8f8; } 8 | .highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ 9 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 10 | .highlight .k { color: #008000; font-weight: bold } /* Keyword */ 11 | .highlight .o { color: #666666 } /* Operator */ 12 | .highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ 13 | .highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ 14 | .highlight .cp { color: #9C6500 } /* Comment.Preproc */ 15 | .highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ 16 | .highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ 17 | .highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ 18 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 19 | .highlight .ge { font-style: italic } /* Generic.Emph */ 20 | .highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ 21 | .highlight .gr { color: #E40000 } /* Generic.Error */ 22 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 23 | .highlight .gi { color: #008400 } /* Generic.Inserted */ 24 | .highlight .go { color: #717171 } /* Generic.Output */ 25 | .highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 26 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 27 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 28 | .highlight .gt { color: #0044DD } /* Generic.Traceback */ 29 | .highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ 30 | .highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ 31 | .highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ 32 | .highlight .kp { color: #008000 } /* Keyword.Pseudo */ 33 | .highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ 34 | .highlight .kt { color: #B00040 } /* Keyword.Type */ 35 | .highlight .m { color: #666666 } /* Literal.Number */ 36 | .highlight .s { color: #BA2121 } /* Literal.String */ 37 | .highlight .na { color: #687822 } /* Name.Attribute */ 38 | .highlight .nb { color: #008000 } /* Name.Builtin */ 39 | .highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ 40 | .highlight .no { color: #880000 } /* Name.Constant */ 41 | .highlight .nd { color: #AA22FF } /* Name.Decorator */ 42 | .highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ 43 | .highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ 44 | .highlight .nf { color: #0000FF } /* Name.Function */ 45 | .highlight .nl { color: #767600 } /* Name.Label */ 46 | .highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ 47 | .highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ 48 | .highlight .nv { color: #19177C } /* Name.Variable */ 49 | .highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ 50 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 51 | .highlight .mb { color: #666666 } /* Literal.Number.Bin */ 52 | .highlight .mf { color: #666666 } /* Literal.Number.Float */ 53 | .highlight .mh { color: #666666 } /* Literal.Number.Hex */ 54 | .highlight .mi { color: #666666 } /* Literal.Number.Integer */ 55 | .highlight .mo { color: #666666 } /* Literal.Number.Oct */ 56 | .highlight .sa { color: #BA2121 } /* Literal.String.Affix */ 57 | .highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ 58 | .highlight .sc { color: #BA2121 } /* Literal.String.Char */ 59 | .highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ 60 | .highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ 61 | .highlight .s2 { color: #BA2121 } /* Literal.String.Double */ 62 | .highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ 63 | .highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ 64 | .highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ 65 | .highlight .sx { color: #008000 } /* Literal.String.Other */ 66 | .highlight .sr { color: #A45A77 } /* Literal.String.Regex */ 67 | .highlight .s1 { color: #BA2121 } /* Literal.String.Single */ 68 | .highlight .ss { color: #19177C } /* Literal.String.Symbol */ 69 | .highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ 70 | .highlight .fm { color: #0000FF } /* Name.Function.Magic */ 71 | .highlight .vc { color: #19177C } /* Name.Variable.Class */ 72 | .highlight .vg { color: #19177C } /* Name.Variable.Global */ 73 | .highlight .vi { color: #19177C } /* Name.Variable.Instance */ 74 | .highlight .vm { color: #19177C } /* Name.Variable.Magic */ 75 | .highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /docs/_build/html/genindex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Index — k8s-tew documentation 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 61 | 62 |
66 | 67 |
68 |
69 |
70 |
    71 |
  • 72 | 73 |
  • 74 |
  • 75 |
76 |
77 |
78 |
79 |
80 | 81 | 82 |

Index

83 | 84 |
85 | 86 |
87 | 88 | 89 |
90 |
91 |
92 | 93 |
94 | 95 |
96 |

© Copyright 2020, Mircea-Cristian Racasan.

97 |
98 | 99 | Built with Sphinx using a 100 | theme 101 | provided by Read the Docs. 102 | 103 | 104 |
105 |
106 |
107 |
108 |
109 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /docs/_build/html/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_build/html/objects.inv -------------------------------------------------------------------------------- /docs/_build/html/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Search — k8s-tew documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 64 | 65 |
69 | 70 |
71 |
72 |
73 |
    74 |
  • 75 | 76 |
  • 77 |
  • 78 |
79 |
80 |
81 |
82 |
83 | 84 | 91 | 92 | 93 |
94 | 95 |
96 | 97 |
98 |
99 |
100 | 101 |
102 | 103 |
104 |

© Copyright 2020, Mircea-Cristian Racasan.

105 |
106 | 107 | Built with Sphinx using a 108 | theme 109 | provided by Read the Docs. 110 | 111 | 112 |
113 |
114 |
115 |
116 |
117 | 122 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /docs/_build/html/troubleshooting.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Troubleshooting — k8s-tew documentation 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 63 | 64 |
68 | 69 |
70 |
71 |
72 | 79 |
80 |
81 |
82 |
83 | 84 |
85 |

Troubleshooting

86 |

k8s-tew enables logging for all components by default. The log files are stored in base-directory/var/log/k8s-tew/.

87 |
88 | 89 | 90 |
91 |
92 |
95 | 96 |
97 | 98 |
99 |

© Copyright 2020, Mircea-Cristian Racasan.

100 |
101 | 102 | Built with Sphinx using a 103 | theme 104 | provided by Read the Docs. 105 | 106 | 107 |
108 |
109 |
110 |
111 |
112 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /docs/_static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_static/.gitkeep -------------------------------------------------------------------------------- /docs/_templates/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/_templates/.gitkeep -------------------------------------------------------------------------------- /docs/about.rst: -------------------------------------------------------------------------------- 1 | About 2 | ===== 3 | 4 | k8s-tew_ is a CLI tool to install a Kubernetes Cluster (local, single-node, multi-node or HA-cluster) on Bare Metal. It installs the most essential components needed by a cluster such as networking, storage, monitoring, logging, backuping/restoring and so on. Once the nodes are configured, k8s-tew is started on each node to supervise the cluster components. k8s-tew is also used internally to generate the configuration files for a Ceph cluster and also to start the necessary Ceph deamons with the right parameters. The Ceph functionality can be used with or without Kubernetes. 5 | 6 | .. _k8s-tew: https://github.com/darxkies/k8s-tew 7 | 8 | Why 9 | --- 10 | 11 | Kubernetes_ is a fairly complex project. For a newbie it is hard to understand and also to use. While `Kelsey Hightower's Kubernetes The Hard Way `_, on which this project is based, helps a lot to understand Kubernetes, it is optimized for the use with Google Cloud Platform. 12 | 13 | Thus, this project's aim is to give newbies an easy to use tool that allows them to tinker with Kubernetes and later on to install HA production grade clusters. 14 | 15 | .. _Kubernetes: https://kubernetes.io/ 16 | 17 | -------------------------------------------------------------------------------- /docs/eval: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darxkies/k8s-tew/25cfcb404439ce9c4b4e8f5ec1602f7b28f3d5a2/docs/eval -------------------------------------------------------------------------------- /docs/features.rst: -------------------------------------------------------------------------------- 1 | Features 2 | ======== 3 | 4 | * HA cluster setup passes all CNCF conformance tests (Kubernetes `1.10 `_, `1.11 `_, `1.12 `_, `1.13 `_, `1.14 `_, `1.15 `_, `1.16 `_, `1.17 `_, `1.18 `_, `1.19 `_, `1.20 `_, `1.21 `_, `1.22 `_ , `1.23 `_ , `1.24 `_ , `1.25 `_ , `1.26 `_ & `1.27 `_) 5 | * Container Management: `Containerd `_ 6 | * Networking: `Calico `_ 7 | * Ingress: `NGINX Ingress `_ and `cert-manager `_ for `Let's Encrypt `_ 8 | * Storage: `Ceph/RBD `_ 9 | * Metrics: `metering-metrics `_ and `Heapster `_ 10 | * Monitoring: `Prometheus `_ and `Grafana `_ 11 | * Logging: `Fluent-Bit `_, `Elasticsearch `_, `Kibana `_ and `Cerebro `_ 12 | * Backups: `Velero `_, `Restic `_ and `Minio `_ 13 | * Cluster Load Balancing: `MetalLB `_ 14 | * Controller Load Balancing: `gobetween `_ 15 | * Package Manager: `Helm `_ 16 | * Dashboard: `Kubernetes Dashboard `_ 17 | * The communication between the components is encrypted 18 | * RBAC is enabled 19 | * The controllers and the workers have Floating/Virtual IPs 20 | * Integrated Load Balancer for the API Servers 21 | * Support for deployment to a HA cluster using ssh 22 | * Only the changed files are deployed 23 | * No `Docker `_ installation required 24 | * No cloud provider required 25 | * Single binary without any dependencies 26 | * Runs locally 27 | * Nodes management from the command line 28 | * Downloads all the used binaries (kubernetes, calico, ceph...) from the Internet 29 | * Pull Images, Convert them to OCI and import them on the cluster for offline installations 30 | * Uses systemd to install itself as a service on the remote machine 31 | * Installs `WordPress `_ and `MySQL `_ to test drive the installation 32 | 33 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. spelling:: 2 | 3 | prem 4 | 5 | .. k8s-tew documentation master file, created by 6 | sphinx-quickstart on Sat Oct 6 19:46:44 2018. 7 | You can adapt this file completely to your liking, but it should at least 8 | contain the root `toctree` directive. 9 | 10 | Welcome to k8s-tew's documentation! 11 | =================================== 12 | 13 | .. toctree:: 14 | :maxdepth: 2 15 | :caption: Contents: 16 | 17 | about 18 | features 19 | quickstart 20 | installation 21 | usage 22 | setups 23 | troubleshooting 24 | 25 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | The commands in the upcoming sections will assume that k8s-tew is going to be installed in the directory :file:`/usr/local/bin`. That means that the aforementioned directory exists and it is included in the PATH. If that is not the case use the following commands: 5 | 6 | .. code:: shell 7 | 8 | sudo mkdir -p /usr/local/bin 9 | export PATH=/usr/local/bin:$PATH 10 | 11 | Binary 12 | ------ 13 | 14 | The x86 64-bit binary can be downloaded from the following address: https://github.com/darxkies/k8s-tew/releases 15 | 16 | Additionally the these commands can be used to download it and install it in :file:`/usr/local/bin` 17 | 18 | .. code:: shell 19 | 20 | curl -s https://api.github.com/repos/darxkies/k8s-tew/releases/latest | grep "browser_download_url" | cut -d : -f 2,3 | tr -d \" | sudo wget -O /usr/local/bin/k8s-tew -qi - 21 | sudo chmod a+x /usr/local/bin/k8s-tew 22 | 23 | Source 24 | ------ 25 | 26 | To compile it from source you will need a Go (version 1.15+) environment, Git, Make and Docker installed. Once everything is installed, enter the following commands: 27 | 28 | .. code:: shell 29 | 30 | export GOPATH=~/go 31 | export PATH=$GOPATH/bin:$PATH 32 | mkdir -p $GOPATH/src/github.com/darxkies 33 | cd $GOPATH/src/github.com/darxkies 34 | git clone https://github.com/darxkies/k8s-tew.git 35 | cd k8s-tew 36 | make 37 | sudo mv k8s-tew /usr/local/bin 38 | 39 | -------------------------------------------------------------------------------- /docs/quickstart.rst: -------------------------------------------------------------------------------- 1 | Quick Start 2 | =========== 3 | 4 | The following snippet will create a cluster on the host computer or in a virtual machine: 5 | 6 | .. code:: shell 7 | 8 | # Switch to user root 9 | sudo su - 10 | 11 | # Download Binary 12 | wget https://github.com/darxkies/k8s-tew/releases/download/2.4.1/k8s-tew 13 | chmod a+x k8s-tew 14 | 15 | # Everything is installed relative to the root directory 16 | export K8S_TEW_BASE_DIRECTORY=/ 17 | 18 | # Initialize cluster configuration 19 | ./k8s-tew initialize 20 | 21 | # Node the current machine to the cluster (the settings such as IP and hostname are inferred) 22 | ./k8s-tew node-add -s 23 | 24 | # Only on Ubuntu 20.04 to solve any DNS related issues 25 | ./k8s-tew configure --resolv-conf=/run/systemd/resolve/resolv.conf 26 | 27 | # Generate artefacts (e.g. certificates, configurations and so on) 28 | ./k8s-tew generate 29 | 30 | # Activate and start service 31 | systemctl daemon-reload 32 | systemctl enable k8s-tew 33 | systemctl start k8s-tew 34 | 35 | # Activate environment variables and switch back to root 36 | exit 37 | sudo su - 38 | 39 | # Watch the pods being installed 40 | watch -n 1 kubectl get pods --all-namespaces 41 | 42 | .. note:: You will need at least 20GB HDD, 8GB RAM and 4 CPU Cores. 43 | .. note:: To use k8s-tew with Vagrant take a look at `https://github.com/darxkies/k8s-tew/tree/2.4.1/setup `_. 44 | 45 | -------------------------------------------------------------------------------- /docs/setups.rst: -------------------------------------------------------------------------------- 1 | Setups 2 | ====== 3 | 4 | Vagrant 5 | ------- 6 | 7 | Vagrant/VirtualBox can be used to test drive k8s-tew. The host is used to bootstrap the cluster which runs in VirtualBox. The Vagrantfile included in the repository can be used for single-node/multi-node & Ubuntu 20.04/CentOS 8.2 setups. 8 | 9 | The Vagrantfile can be configured using the environment variables: 10 | 11 | - OS - define the operating system. It accepts ubuntu, the default value, and centos. 12 | - MULTI_NODE - if set then a HA cluster is generated. Otherwise a single-node setup is used. 13 | - CONTROLLERS - defines the number of controller nodes. The default number is 3. 14 | - WORKERS - specifies the number of worker nodes. The default number is 2. 15 | - SSH_PUBLIC_KEY - if this environment variable is not set, then :file:`$HOME/.ssh/id_rsa` is used by default. 16 | - IP_PREFIX - this value is used to generate the IP addresses of the nodes. If not set 192.168.100 will be used. The single node has the IP address 192.168.100.50. The controllers start with the IP address 192.168.100.200 and the workers with 192.168.100.100. 17 | - CONTROLLERS_RAM - amount of RAM for one controller 18 | - WORKERS_RAM - amount of RAM for one worker 19 | - STORAGE_RAM - amount of RAM for one storage 20 | - CONTROLLERS_CPUS - number of CPUs per controller 21 | - WORKERS_CPUS - number of CPUs per worker 22 | - STORAGE_CPUS - number of CPUs per storage 23 | 24 | .. note:: The multi-node setup with the default settings needs about 20GB RAM for itself. 25 | 26 | 27 | Usage 28 | ^^^^^ 29 | 30 | The directory called :file:`setup` (`https://github.com/darxkies/k8s-tew/tree/2.4.1/setup `_) contains sub-directories for various cluster setup configurations: 31 | 32 | - local - it starts a single-node cluster locally without using any kind of virtualization. This kind of setup needs root rights. It is meant for local development where it might be important to fire the cluster up and shut it down fast. If you want it to start automatically, take a look at the quickstart section. 33 | - ubuntu-single-node - Ubuntu 20.04 single-node cluster. It needs about 8GB Ram. 34 | - ubuntu-multi-node - Ubuntu 20.04 HA cluster. It needs around 20GB Ram. 35 | - centos-single-node - CentOS 8.2 single-node cluster. It needs about 8GB Ram. 36 | - centos-multi-node - CentOS 8.2 HA cluster. It needs around 20GB Ram. 37 | 38 | .. note:: Regardless of the setup, once the deployment is done it will take a while to download all required containers from the internet. So better use kubectl to check the status of the pods. 39 | 40 | .. note:: For the local setup, to access the Kubernetes Dashboard use the internal IP address (e.g. 192.168.x.y or 10.x.y.z) and not 127.0.0.1/localhost. Depending on the hardware used, it might take a while until it starts and setups everything. 41 | 42 | Create 43 | ^^^^^^ 44 | 45 | Change to one of the sub-directories and enter the following command to start the cluster: 46 | 47 | .. code:: shell 48 | 49 | make 50 | 51 | .. note:: This will destroy any existing VMs, creates new VMs and performs all the steps (forced initialization, configuration, generation and deployment) to create the cluster. 52 | 53 | Stop 54 | ^^^^^^ 55 | 56 | For the local setup, just press CTRL+C. 57 | 58 | For the other setups enter: 59 | 60 | .. code:: shell 61 | 62 | make halt 63 | 64 | Start 65 | ^^^^^ 66 | 67 | To start an existing setup/VMs enter: 68 | 69 | .. code:: shell 70 | 71 | make up 72 | 73 | .. note:: This and the following commands work only for Vagrant based setups. 74 | 75 | SSH 76 | ^^^ 77 | 78 | For single-node setups enter: 79 | 80 | .. code:: shell 81 | 82 | make ssh 83 | 84 | And for multi-node setups: 85 | 86 | .. code:: shell 87 | 88 | make ssh-controller00 89 | make ssh-controller01 90 | make ssh-controller02 91 | make ssh-worker00 92 | make ssh-worker01 93 | 94 | Kubernetes Dashboard 95 | ^^^^^^^^^^^^^^^^^^^^ 96 | 97 | This will display the token, and then it will open the web browser pointing to the address of Kubernetes Dashboard: 98 | 99 | .. code:: shell 100 | 101 | make dashboard 102 | 103 | Ingress Port Forwarding 104 | ^^^^^^^^^^^^^^^^^^^^^^^ 105 | 106 | In order to start port forwarding from your host's ports 80 and 443 to Vagrant's VMs for Ingress enter: 107 | 108 | .. code:: shell 109 | 110 | make forward-80 111 | make forward-443 112 | 113 | .. note:: Both commands are blocking. So you need two different terminal sessions. 114 | 115 | -------------------------------------------------------------------------------- /docs/troubleshooting.rst: -------------------------------------------------------------------------------- 1 | Troubleshooting 2 | =============== 3 | 4 | k8s-tew enables logging for all components by default. The log files are stored in :file:`{base-directory}/var/log/k8s-tew/`. 5 | 6 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/darxkies/k8s-tew 2 | 3 | go 1.18 4 | 5 | replace github.com/docker/distribution => github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible 6 | 7 | require ( 8 | github.com/briandowns/spinner v1.23.0 9 | github.com/cavaliercoder/grab v2.0.0+incompatible 10 | github.com/davecgh/go-spew v1.1.1 11 | github.com/evanphx/json-patch v5.6.0+incompatible 12 | github.com/opencontainers/go-digest v1.0.0 13 | github.com/opencontainers/image-spec v1.0.2 14 | github.com/pkg/errors v0.9.1 15 | github.com/satori/go.uuid v1.2.0 16 | github.com/sethvargo/go-password v0.2.0 17 | github.com/sirupsen/logrus v1.9.0 18 | github.com/smallnest/goreq v0.0.0-20180727030113-2e3372c80388 19 | github.com/spf13/cobra v1.7.0 20 | github.com/spf13/pflag v1.0.5 21 | github.com/spf13/viper v1.15.0 22 | github.com/tmc/scp v0.0.0-20170824174625-f7b48647feef 23 | github.com/wille/osutil v0.0.0-20230417145339-416c15a22a77 24 | golang.org/x/crypto v0.8.0 25 | golang.org/x/sys v0.7.0 26 | google.golang.org/grpc v1.54.0 27 | gopkg.in/ini.v1 v1.67.0 28 | gopkg.in/yaml.v2 v2.4.0 29 | k8s.io/api v0.27.1 30 | k8s.io/apimachinery v0.27.1 31 | k8s.io/cli-runtime v0.27.1 32 | k8s.io/client-go v0.27.1 33 | k8s.io/cri-api v0.22.1 34 | k8s.io/kubectl v0.27.1 35 | ) 36 | 37 | require ( 38 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect 39 | github.com/MakeNowJust/heredoc v1.0.0 // indirect 40 | github.com/PuerkitoBio/purell v1.2.0 // indirect 41 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 42 | github.com/cespare/reflex v0.3.1 // indirect 43 | github.com/chai2010/gettext-go v1.0.2 // indirect 44 | github.com/creack/pty v1.1.18 // indirect 45 | github.com/emicklei/go-restful/v3 v3.10.2 // indirect 46 | github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect 47 | github.com/fatih/color v1.15.0 // indirect 48 | github.com/fsnotify/fsnotify v1.6.0 // indirect 49 | github.com/go-errors/errors v1.4.2 // indirect 50 | github.com/go-logr/logr v1.2.4 // indirect 51 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 52 | github.com/go-openapi/jsonreference v0.20.2 // indirect 53 | github.com/go-openapi/swag v0.22.3 // indirect 54 | github.com/gogo/protobuf v1.3.2 // indirect 55 | github.com/golang/protobuf v1.5.3 // indirect 56 | github.com/google/btree v1.1.2 // indirect 57 | github.com/google/gnostic v0.6.9 // indirect 58 | github.com/google/go-cmp v0.5.9 // indirect 59 | github.com/google/gofuzz v1.2.0 // indirect 60 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 61 | github.com/google/uuid v1.3.0 // indirect 62 | github.com/googleapis/gnostic v0.5.5 // indirect 63 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect 64 | github.com/hashicorp/hcl v1.0.0 // indirect 65 | github.com/imdario/mergo v0.3.15 // indirect 66 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 67 | github.com/josharian/intern v1.0.0 // indirect 68 | github.com/json-iterator/go v1.1.12 // indirect 69 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect 70 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect 71 | github.com/magiconair/properties v1.8.7 // indirect 72 | github.com/mailru/easyjson v0.7.7 // indirect 73 | github.com/mattn/go-colorable v0.1.13 // indirect 74 | github.com/mattn/go-isatty v0.0.18 // indirect 75 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect 76 | github.com/mitchellh/mapstructure v1.5.0 // indirect 77 | github.com/moby/spdystream v0.2.0 // indirect 78 | github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect 79 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 80 | github.com/modern-go/reflect2 v1.0.2 // indirect 81 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect 82 | github.com/moul/http2curl v1.0.0 // indirect 83 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 84 | github.com/ogier/pflag v0.0.1 // indirect 85 | github.com/onsi/gomega v1.27.4 // indirect 86 | github.com/pelletier/go-toml v1.9.5 // indirect 87 | github.com/pelletier/go-toml/v2 v2.0.7 // indirect 88 | github.com/peterbourgon/diskv v2.0.1+incompatible // indirect 89 | github.com/pmezard/go-difflib v1.0.0 // indirect 90 | github.com/russross/blackfriday v1.6.0 // indirect 91 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 92 | github.com/spf13/afero v1.9.5 // indirect 93 | github.com/spf13/cast v1.5.0 // indirect 94 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 95 | github.com/stretchr/testify v1.8.2 // indirect 96 | github.com/subosito/gotenv v1.4.2 // indirect 97 | github.com/xlab/treeprint v1.2.0 // indirect 98 | go.starlark.net v0.0.0-20230302034142-4b1e35fe2254 // indirect 99 | golang.org/x/net v0.9.0 // indirect 100 | golang.org/x/oauth2 v0.7.0 // indirect 101 | golang.org/x/term v0.7.0 // indirect 102 | golang.org/x/text v0.9.0 // indirect 103 | golang.org/x/time v0.3.0 // indirect 104 | google.golang.org/appengine v1.6.7 // indirect 105 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect 106 | google.golang.org/protobuf v1.30.0 // indirect 107 | gopkg.in/inf.v0 v0.9.1 // indirect 108 | gopkg.in/yaml.v3 v3.0.1 // indirect 109 | k8s.io/component-base v0.27.1 // indirect 110 | k8s.io/klog/v2 v2.90.1 // indirect 111 | k8s.io/kube-openapi v0.0.0-20230327201221-f5883ff37f0c // indirect 112 | k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect 113 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 114 | sigs.k8s.io/kustomize/api v0.13.2 // indirect 115 | sigs.k8s.io/kustomize/kyaml v0.14.1 // indirect 116 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 117 | sigs.k8s.io/yaml v1.3.0 // indirect 118 | ) 119 | -------------------------------------------------------------------------------- /pkg/config/asset_config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type AssetConfig struct { 4 | Directories map[string]*AssetDirectory 5 | Files map[string]*AssetFile 6 | } 7 | -------------------------------------------------------------------------------- /pkg/config/asset_directory.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type AssetDirectory struct { 4 | Labels Labels `yaml:"labels,omitempty"` 5 | Directory string `yaml:"directory"` 6 | Absolute bool `yaml:"absolute,omitempty"` 7 | } 8 | 9 | func NewAssetDirectory(labels []string, directory string, absolute bool) *AssetDirectory { 10 | return &AssetDirectory{Labels: labels, Directory: directory, Absolute: absolute} 11 | } 12 | -------------------------------------------------------------------------------- /pkg/config/asset_file.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type AssetFile struct { 4 | Labels Labels `yaml:"labels,omitempty"` 5 | Filename string `yaml:"filename,omitempty"` 6 | Directory string `yaml:"directory"` 7 | } 8 | 9 | func NewAssetFile(labels []string, filename, directory string) *AssetFile { 10 | return &AssetFile{Labels: labels, Filename: filename, Directory: directory} 11 | } 12 | -------------------------------------------------------------------------------- /pkg/config/command.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Command struct { 4 | Name string `yaml:"name"` 5 | Command string `yaml:"command,omitempty"` 6 | Manifest string `yaml:"manifest,omitempty"` 7 | Labels Labels `yaml:"labels,omitempty"` 8 | Features Features `yaml:"features,omitempty"` 9 | OS OS `yaml:"os,omitempty"` 10 | } 11 | 12 | type Commands []*Command 13 | type OS []string 14 | 15 | func NewCommand(name string, labels Labels, features Features, os OS, command string) *Command { 16 | return &Command{Name: name, Labels: labels, Features: features, OS: os, Command: command} 17 | } 18 | 19 | func NewManifest(name string, labels Labels, features Features, os OS, manifest string) *Command { 20 | return &Command{Name: name, Labels: labels, Features: features, OS: os, Manifest: manifest} 21 | } 22 | -------------------------------------------------------------------------------- /pkg/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/darxkies/k8s-tew/pkg/utils" 5 | uuid "github.com/satori/go.uuid" 6 | ) 7 | 8 | type Config struct { 9 | Version string `yaml:"version"` 10 | ClusterID string `yaml:"cluster-id"` 11 | ClusterName string `yaml:"cluster-name"` 12 | Email string `yaml:"email"` 13 | IngressDomain string `yaml:"ingress-domain"` 14 | LoadBalancerPort uint16 `yaml:"load-balancer-port"` 15 | VIPRaftControllerPort uint16 `yaml:"vip-raft-controller-port"` 16 | VIPRaftWorkerPort uint16 `yaml:"vip-raft-worker-port"` 17 | KubernetesDashboardPort uint16 `yaml:"kubernetes-dashboard-port"` 18 | APIServerPort uint16 `yaml:"apiserver-port,omitempty"` 19 | PublicNetwork string `yaml:"public-network"` 20 | ControllerVirtualIP string `yaml:"controller-virtual-ip,omitempty"` 21 | ControllerVirtualIPInterface string `yaml:"controller-virtual-ip-interface,omitempty"` 22 | WorkerVirtualIP string `yaml:"worker-virtual-ip,omitempty"` 23 | WorkerVirtualIPInterface string `yaml:"worker-virtual-ip-interface,omitempty"` 24 | ClusterDomain string `yaml:"cluster-domain"` 25 | ClusterIPRange string `yaml:"cluster-ip-range"` 26 | ClusterDNSIP string `yaml:"cluster-dns-ip"` 27 | ClusterCIDR string `yaml:"cluster-cidr"` 28 | CalicoTyphaIP string `yaml:"calico-typha-ip"` 29 | CephClusterName string `yaml:"ceph-cluster-name"` 30 | CephPlacementGroups uint `yaml:"ceph-placement-groups"` 31 | CephExpectedNumberOfObjects uint `yaml:"ceph-expected-number-of-objects"` 32 | MetalLBAddresses string `yaml:"metallb-addresses"` 33 | ResolvConf string `yaml:"resolv-conf"` 34 | DeploymentDirectory string `yaml:"deployment-directory,omitempty"` 35 | MaxPods uint16 `yaml:"max-pods"` 36 | SANIPAddresses string `yaml:"san-ip-addresses,omitempty"` 37 | SANDNSNames string `yaml:"san-dns-names,omitempty"` 38 | RSASize uint16 `yaml:"rsa-size"` 39 | CAValidityPeriod uint `yaml:"ca-validity-period"` 40 | ClientValidityPeriod uint `yaml:"client-validity-period"` 41 | GrafanaSize uint16 `yaml:"grafana-size"` 42 | PrometheusSize uint16 `yaml:"prometheus-size"` 43 | MinioSize uint16 `yaml:"minio-size"` 44 | ElasticsearchCount uint16 `yaml:"elasticsearch-count"` 45 | ElasticsearchSize uint16 `yaml:"elasticsearch-size"` 46 | AlertManagerCount uint16 `yaml:"alert-manager-count"` 47 | AlertManagerSize uint16 `yaml:"alert-manager-size"` 48 | KubeStateMetricsCount uint16 `yaml:"kube-state-metrics-count"` 49 | DrainGracePeriodSeconds uint16 `yaml:"drain-grace-period-seconds"` 50 | Versions Versions `yaml:"versions"` 51 | Assets AssetConfig `yaml:"assets,omitempty"` 52 | Nodes Nodes `yaml:"nodes"` 53 | Commands Commands `yaml:"commands,omitempty"` 54 | Servers Servers `yaml:"servers,omitempty"` 55 | } 56 | 57 | func NewConfig() *Config { 58 | config := &Config{Version: utils.VersionConfig} 59 | 60 | config.VIPRaftControllerPort = utils.PortVipRaftController 61 | config.VIPRaftWorkerPort = utils.PortVipRaftWorker 62 | config.ClusterID = uuid.NewV4().String() 63 | config.ClusterName = utils.ClusterName 64 | config.Email = utils.Email 65 | config.IngressDomain = utils.IngressDomain 66 | config.LoadBalancerPort = utils.PortLoadBalancer 67 | config.KubernetesDashboardPort = utils.PortKubernetesDashboard 68 | config.APIServerPort = utils.PortApiServer 69 | config.PublicNetwork = utils.PublicNetwork 70 | config.ClusterDomain = utils.ClusterDomain 71 | config.ClusterIPRange = utils.ClusterIpRange 72 | config.ClusterDNSIP = utils.ClusterDnsIp 73 | config.ClusterCIDR = utils.ClusterCidr 74 | config.CalicoTyphaIP = utils.CalicoTyphaIp 75 | config.CephClusterName = utils.CephClusterName 76 | config.CephPlacementGroups = utils.CephPlacementGroups 77 | config.CephExpectedNumberOfObjects = utils.CephExpectedNumberOfObjects 78 | config.MetalLBAddresses = utils.MetalLBAddresses 79 | config.ResolvConf = utils.ResolvConf 80 | config.DeploymentDirectory = utils.DeploymentDirectory 81 | config.MaxPods = utils.MaxPods 82 | config.RSASize = utils.RsaSize 83 | config.CAValidityPeriod = utils.CaValidityPeriod 84 | config.ClientValidityPeriod = utils.ClientValidityPeriod 85 | config.GrafanaSize = utils.GrafanaSize 86 | config.PrometheusSize = utils.PrometheusSize 87 | config.MinioSize = utils.MinioSize 88 | config.ElasticsearchCount = utils.ElasticsearchCount 89 | config.ElasticsearchSize = utils.ElasticsearchSize 90 | config.AlertManagerCount = utils.AlertManagerCount 91 | config.AlertManagerSize = utils.AlertManagerSize 92 | config.KubeStateMetricsCount = utils.KubeStateMetricsCount 93 | config.DrainGracePeriodSeconds = utils.DrainGracePeriodSeconds 94 | config.Versions = NewVersions() 95 | config.Assets = AssetConfig{Directories: map[string]*AssetDirectory{}, Files: map[string]*AssetFile{}} 96 | config.Nodes = Nodes{} 97 | config.Commands = Commands{} 98 | config.Servers = Servers{} 99 | 100 | return config 101 | } 102 | -------------------------------------------------------------------------------- /pkg/config/features.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Features []string 4 | 5 | func (features Features) HasFeatures(otherFeatures Features) bool { 6 | for _, feature := range features { 7 | for _, otherFeature := range otherFeatures { 8 | if feature == otherFeature { 9 | return true 10 | } 11 | } 12 | } 13 | 14 | return false 15 | } 16 | 17 | func CompareFeatures(source, destination Features) bool { 18 | return source != nil && destination != nil && source.HasFeatures(destination) 19 | } 20 | -------------------------------------------------------------------------------- /pkg/config/images.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "crypto/sha256" 5 | "fmt" 6 | ) 7 | 8 | type Image struct { 9 | Name string 10 | Features Features 11 | } 12 | 13 | type Images []Image 14 | 15 | func (image Image) GetImageFilename() (result string) { 16 | 17 | for _, char := range image.Name { 18 | if (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9') { 19 | result += string(char) 20 | 21 | continue 22 | } 23 | 24 | result += "_" 25 | } 26 | 27 | result = fmt.Sprintf("%s.%X.tar", result, sha256.Sum256([]byte(image.Name))) 28 | 29 | return 30 | } 31 | -------------------------------------------------------------------------------- /pkg/config/labels.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Labels []string 4 | 5 | func (labels Labels) HasLabels(otherLabels Labels) bool { 6 | for _, label := range labels { 7 | for _, otherLabel := range otherLabels { 8 | if label == otherLabel { 9 | return true 10 | } 11 | } 12 | } 13 | 14 | return false 15 | } 16 | 17 | func CompareLabels(source, destination Labels) bool { 18 | return source != nil && destination != nil && source.HasLabels(destination) 19 | } 20 | -------------------------------------------------------------------------------- /pkg/config/logger_config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type LoggerConfig struct { 4 | Enabled bool `yaml:"enabled"` 5 | Filename string `yaml:"filename"` 6 | } 7 | -------------------------------------------------------------------------------- /pkg/config/node.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "github.com/darxkies/k8s-tew/pkg/utils" 4 | 5 | type Node struct { 6 | IP string `yaml:"ip"` 7 | Index uint `yaml:"index"` 8 | StorageIndex uint `yaml:"storage-index,omitempty"` 9 | Labels Labels `yaml:"labels"` 10 | } 11 | 12 | type Nodes map[string]*Node 13 | 14 | func (nodes Nodes) HasControllerNode() bool { 15 | for _, node := range nodes { 16 | if node.IsController() { 17 | return true 18 | } 19 | } 20 | 21 | return false 22 | } 23 | 24 | func (nodes Nodes) HasWorkerNode() bool { 25 | for _, node := range nodes { 26 | if node.IsWorker() { 27 | return true 28 | } 29 | } 30 | 31 | return false 32 | } 33 | 34 | func (nodes Nodes) HasStorageNode() bool { 35 | for _, node := range nodes { 36 | if node.IsStorage() { 37 | return true 38 | } 39 | } 40 | 41 | return false 42 | } 43 | 44 | func NewNode(ip string, index, storageIndex uint, labels []string) *Node { 45 | return &Node{IP: ip, Index: index, StorageIndex: storageIndex, Labels: labels} 46 | } 47 | 48 | func (node *Node) IsController() bool { 49 | for _, label := range node.Labels { 50 | if label == utils.NodeController { 51 | return true 52 | } 53 | } 54 | 55 | return false 56 | } 57 | 58 | func (node *Node) IsWorker() bool { 59 | for _, label := range node.Labels { 60 | if label == utils.NodeWorker { 61 | return true 62 | } 63 | } 64 | 65 | return false 66 | } 67 | 68 | func (node *Node) IsStorage() bool { 69 | for _, label := range node.Labels { 70 | if label == utils.NodeStorage { 71 | return true 72 | } 73 | } 74 | 75 | return false 76 | } 77 | 78 | func (node *Node) IsControllerOnly() bool { 79 | return node.IsController() && !node.IsWorker() && !node.IsStorage() 80 | } 81 | 82 | func (node *Node) IsStorageOnly() bool { 83 | return !node.IsController() && !node.IsWorker() && node.IsStorage() 84 | } 85 | 86 | func (node *Node) IsWorkerOnly() bool { 87 | return !node.IsController() && node.IsWorker() && !node.IsStorage() 88 | } 89 | -------------------------------------------------------------------------------- /pkg/config/server_config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | log "github.com/sirupsen/logrus" 5 | ) 6 | 7 | type ServerConfig struct { 8 | Name string `yaml:"name"` 9 | Enabled bool `yaml:"enabled"` 10 | Labels Labels `yaml:"labels"` 11 | Logger LoggerConfig `yaml:"logger"` 12 | Command string `yaml:"command"` 13 | Arguments map[string]string `yaml:"arguments"` 14 | Environment map[string]string `yaml:"environment"` 15 | } 16 | 17 | type Servers []ServerConfig 18 | 19 | func (config ServerConfig) Dump() { 20 | log.WithFields(log.Fields{"name": config.Name, "labels": config.Labels, "command": config.Command}).Info("Config server") 21 | 22 | for key, value := range config.Arguments { 23 | log.WithFields(log.Fields{"name": config.Name, "argument": key, "value": value}).Info("Config server argument") 24 | } 25 | 26 | for key, value := range config.Environment { 27 | log.WithFields(log.Fields{"name": config.Name, "environment": key, "value": value}).Info("Config server environment") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /pkg/container/image/converter/layers.go: -------------------------------------------------------------------------------- 1 | package converter 2 | 3 | type layer struct { 4 | MediaType string 5 | Size int64 6 | BlobDigest string 7 | TarDigest string 8 | Filename string 9 | } 10 | 11 | type layers []*layer 12 | -------------------------------------------------------------------------------- /pkg/container/image/manifest/config.go: -------------------------------------------------------------------------------- 1 | package manifest 2 | 3 | type Config struct { 4 | ArgsEscaped bool 5 | AttachStdin bool 6 | AttachStderr bool 7 | AttachStdout bool 8 | Tty bool 9 | OpenStdin bool 10 | StdinOnce bool 11 | Hostname string 12 | Domainname string 13 | Image string 14 | Cmd []string 15 | Env []string 16 | User string 17 | ExposedPorts map[string]struct{} 18 | Volumes map[string]struct{} 19 | WorkingDir string 20 | Entrypoint []string 21 | OnBuild []string 22 | Labels map[string]string 23 | } 24 | -------------------------------------------------------------------------------- /pkg/container/image/manifest/file_system_layers.go: -------------------------------------------------------------------------------- 1 | package manifest 2 | 3 | const EmptyLayer = "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4" 4 | 5 | type FileSystemLayer struct { 6 | BlobSum string `json:"blobSum"` 7 | } 8 | 9 | type FileSystemLayers []FileSystemLayer 10 | -------------------------------------------------------------------------------- /pkg/container/image/manifest/history.go: -------------------------------------------------------------------------------- 1 | package manifest 2 | 3 | type HistoryEntryData struct { 4 | ID string `json:"id"` 5 | Parent string `json:"parent"` 6 | DockerVersion string `json:"docker_version"` 7 | Architecture string `json:"architecture"` 8 | OS string `json:"os"` 9 | Container string `json:"container"` 10 | Throwaway bool `json:"throwaway"` 11 | Config *Config `json:"config"` 12 | ContainerConfig *Config `json:"container_config"` 13 | Created string `json:"created"` 14 | Author string `json:"author"` 15 | } 16 | 17 | type HistoryEntry struct { 18 | V1Compatibility string `json:"v1Compatibility"` 19 | } 20 | 21 | type History []*HistoryEntry 22 | -------------------------------------------------------------------------------- /pkg/container/image/manifest/manifest.go: -------------------------------------------------------------------------------- 1 | package manifest 2 | 3 | type Manifest struct { 4 | SchemaVersion int `json:"schemaVersion"` 5 | Name string `json:"name"` 6 | Tag string `json:"tag"` 7 | Architecture string `json:"architecture"` 8 | FileSystemLayers FileSystemLayers `json:"fsLayers"` 9 | History History `json:"history"` 10 | } 11 | -------------------------------------------------------------------------------- /pkg/container/image/storage/storage.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "archive/tar" 5 | "fmt" 6 | "os" 7 | "time" 8 | 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | type Storage interface { 13 | WriteFile(filename string, data []byte) error 14 | Close() error 15 | Remove() error 16 | } 17 | 18 | type TarStorage struct { 19 | filename string 20 | tarFile *os.File 21 | tarBall *tar.Writer 22 | timestamp time.Time 23 | } 24 | 25 | func NewTarStorage(filename string) (*TarStorage, error) { 26 | result := &TarStorage{filename: filename, timestamp: time.Now()} 27 | 28 | var error error 29 | 30 | result.tarFile, error = os.Create(filename) 31 | if error != nil { 32 | return nil, errors.Wrapf(error, "could not open %s", filename) 33 | } 34 | 35 | result.tarBall = tar.NewWriter(result.tarFile) 36 | 37 | return result, nil 38 | } 39 | 40 | func (storage *TarStorage) WriteFile(filename string, data []byte) error { 41 | tarHeader := new(tar.Header) 42 | tarHeader.Name = filename 43 | tarHeader.Size = int64(len(data)) 44 | tarHeader.Mode = 0660 45 | tarHeader.ModTime = storage.timestamp 46 | 47 | if error := storage.tarBall.WriteHeader(tarHeader); error != nil { 48 | return errors.Wrapf(error, "could not write header for %s to %s", filename, storage.filename) 49 | } 50 | 51 | written, error := storage.tarBall.Write(data) 52 | 53 | if written != len(data) { 54 | return fmt.Errorf("could not write all data for %s to %s", filename, storage.filename) 55 | } 56 | 57 | if error != nil { 58 | return errors.Wrapf(error, "could not write data for %s to %s", filename, storage.filename) 59 | } 60 | 61 | return nil 62 | } 63 | 64 | func (storage *TarStorage) Close() error { 65 | if error := storage.tarBall.Close(); error != nil { 66 | return errors.Wrapf(error, "could not close tarball for %s", storage.filename) 67 | } 68 | 69 | if error := storage.tarFile.Close(); error != nil { 70 | return errors.Wrapf(error, "could not close tarfile for %s", storage.filename) 71 | } 72 | 73 | return nil 74 | } 75 | 76 | func (storage *TarStorage) Remove() error { 77 | return os.Remove(storage.filename) 78 | } 79 | -------------------------------------------------------------------------------- /pkg/servers/server.go: -------------------------------------------------------------------------------- 1 | package servers 2 | 3 | type Server interface { 4 | Start() error 5 | Stop() 6 | Name() string 7 | } 8 | -------------------------------------------------------------------------------- /pkg/servers/server_wrapper.go: -------------------------------------------------------------------------------- 1 | package servers 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | "path/filepath" 9 | "strings" 10 | "syscall" 11 | "time" 12 | 13 | log "github.com/sirupsen/logrus" 14 | 15 | "github.com/darxkies/k8s-tew/pkg/config" 16 | "github.com/darxkies/k8s-tew/pkg/utils" 17 | ) 18 | 19 | type ServerWrapper struct { 20 | stop bool 21 | name string 22 | baseDirectory string 23 | command []string 24 | logger config.LoggerConfig 25 | environment map[string]string 26 | pathEnvironment string 27 | started bool 28 | context context.Context 29 | cancel context.CancelFunc 30 | done chan bool 31 | } 32 | 33 | func NewServerWrapper(_config config.InternalConfig, name string, serverConfig config.ServerConfig, pathEnvironment string) (Server, error) { 34 | var error error 35 | 36 | serverConfig.Command, error = _config.ApplyTemplate("command", serverConfig.Command) 37 | 38 | if error != nil { 39 | return nil, error 40 | } 41 | 42 | server := &ServerWrapper{name: name, baseDirectory: _config.BaseDirectory, command: []string{serverConfig.Command}, logger: serverConfig.Logger, pathEnvironment: pathEnvironment, environment: serverConfig.Environment} 43 | 44 | server.logger.Filename, error = _config.ApplyTemplate("LoggingDirectory", server.logger.Filename) 45 | if error != nil { 46 | return nil, error 47 | } 48 | 49 | for key, value := range serverConfig.Arguments { 50 | if len(value) == 0 { 51 | server.command = append(server.command, fmt.Sprintf("--%s", key)) 52 | 53 | } else { 54 | newValue, error := _config.ApplyTemplate(fmt.Sprintf("%s.%s", server.Name(), key), value) 55 | if error != nil { 56 | return nil, error 57 | } 58 | 59 | server.command = append(server.command, fmt.Sprintf("--%s=%s", key, newValue)) 60 | } 61 | } 62 | 63 | return server, nil 64 | } 65 | 66 | func (server *ServerWrapper) Start() error { 67 | if server.started { 68 | return nil 69 | } 70 | 71 | server.stop = false 72 | 73 | if server.logger.Enabled { 74 | logsDirectory := filepath.Dir(server.logger.Filename) 75 | 76 | if error := utils.CreateDirectoryIfMissing(logsDirectory); error != nil { 77 | return error 78 | } 79 | } 80 | 81 | log.WithFields(log.Fields{"name": server.Name(), "_command": strings.Join(server.command, " ")}).Info("Starting server") 82 | 83 | server.context, server.cancel = context.WithCancel(context.Background()) 84 | server.done = make(chan bool, 1) 85 | 86 | server.started = true 87 | 88 | go func() { 89 | for !server.stop { 90 | command := exec.CommandContext(server.context, server.command[0], server.command[1:]...) 91 | command.SysProcAttr = &syscall.SysProcAttr{ 92 | Setpgid: true, 93 | Pgid: 0, 94 | } 95 | 96 | command.Env = os.Environ() 97 | command.Env = append(command.Env, server.pathEnvironment) 98 | 99 | for key, value := range server.environment { 100 | command.Env = append(command.Env, fmt.Sprintf("%s=%s", key, value)) 101 | } 102 | 103 | log.WithFields(log.Fields{"name": server.Name(), "environment": strings.Join(command.Env, " ")}).Debug("Server environment") 104 | 105 | var logFile *os.File 106 | var error error 107 | 108 | if server.logger.Enabled { 109 | logFile, error = os.OpenFile(server.logger.Filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) 110 | 111 | if error != nil { 112 | log.WithFields(log.Fields{"filename": logFile, "error": error}).Error("Could not open file") 113 | 114 | continue 115 | } 116 | 117 | command.Stdout = logFile 118 | command.Stderr = logFile 119 | } 120 | 121 | defer func() { 122 | if logFile != nil { 123 | logFile.Close() 124 | } 125 | }() 126 | 127 | error = command.Run() 128 | 129 | if !server.stop { 130 | time.Sleep(time.Second) 131 | 132 | log.WithFields(log.Fields{"name": server.name, "error": error, "_command": strings.Join(server.command, " ")}).Error("Restarting server") 133 | } 134 | } 135 | 136 | close(server.done) 137 | }() 138 | 139 | return nil 140 | } 141 | 142 | func (server *ServerWrapper) Stop() { 143 | if !server.started { 144 | return 145 | } 146 | 147 | log.WithFields(log.Fields{"name": server.Name()}).Info("Stopping server") 148 | 149 | server.stop = true 150 | 151 | server.cancel() 152 | 153 | <-server.done 154 | 155 | log.WithFields(log.Fields{"name": server.name, "_command": strings.Join(server.command, " ")}).Info("Stopped server") 156 | } 157 | 158 | func (server *ServerWrapper) Name() string { 159 | return server.name 160 | } 161 | -------------------------------------------------------------------------------- /pkg/utils/checksums.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "fmt" 7 | "io" 8 | "os" 9 | "strings" 10 | 11 | "github.com/pkg/errors" 12 | log "github.com/sirupsen/logrus" 13 | ) 14 | 15 | type Checksum struct { 16 | value string 17 | updated bool 18 | } 19 | type Checksums struct { 20 | filename string 21 | baseDirectory string 22 | checksums map[string]Checksum 23 | loaded bool 24 | } 25 | 26 | func NewChecksums(filename, baseDirectory string) *Checksums { 27 | return &Checksums{filename: filename, baseDirectory: baseDirectory, checksums: map[string]Checksum{}} 28 | } 29 | 30 | func (checksums *Checksums) md5(targetFilename string) (result string, error error) { 31 | file, error := os.Open(targetFilename) 32 | if error != nil { 33 | return 34 | } 35 | 36 | defer file.Close() 37 | 38 | hash := md5.New() 39 | 40 | if _, error = io.Copy(hash, file); error != nil { 41 | return 42 | } 43 | 44 | result = hex.EncodeToString(hash.Sum(nil)[:16]) 45 | 46 | return 47 | } 48 | 49 | func (checksums *Checksums) GetChecksum(targetFilename string) (result string, error error) { 50 | relativeFilename := targetFilename[len(checksums.baseDirectory):] 51 | 52 | checksumsCache, _errorCache := os.Stat(checksums.filename) 53 | checksumsTarget, _errorTarget := os.Stat(targetFilename) 54 | 55 | if _errorCache == nil && _errorTarget == nil && checksumsTarget.ModTime().Before(checksumsCache.ModTime()) { 56 | if checksum, ok := checksums.checksums[relativeFilename]; ok { 57 | return checksum.value, nil 58 | } 59 | } 60 | 61 | if checksum, ok := checksums.checksums[relativeFilename]; ok && checksum.updated { 62 | return checksum.value, nil 63 | } 64 | 65 | result, _error := checksums.md5(targetFilename) 66 | if _error != nil { 67 | return "", _error 68 | } 69 | 70 | log.WithFields(log.Fields{"file": targetFilename, "checksum": result}).Debug("Updated checksum") 71 | 72 | checksums.checksums[relativeFilename] = Checksum{value: result, updated: true} 73 | 74 | return 75 | } 76 | 77 | func (checksums *Checksums) Save() error { 78 | buffer := "" 79 | 80 | for filename, value := range checksums.checksums { 81 | buffer += fmt.Sprintf("%s %s\n", value.value, filename) 82 | } 83 | 84 | if _error := os.WriteFile(checksums.filename, []byte(buffer), 0644); _error != nil { 85 | return errors.Wrapf(_error, "Could not write to %s", checksums.filename) 86 | } 87 | 88 | return nil 89 | } 90 | 91 | func (checksums *Checksums) Load() error { 92 | if checksums.loaded { 93 | return nil 94 | } 95 | 96 | checksums.loaded = true 97 | 98 | content, _error := os.ReadFile(checksums.filename) 99 | if _error != nil { 100 | return _error 101 | } 102 | 103 | lines := strings.Split(string(content), "\n") 104 | 105 | for _, line := range lines { 106 | if len(line) < 35 { 107 | continue 108 | } 109 | 110 | filename := line[33:] 111 | checksum := line[:32] 112 | 113 | checksums.checksums[filename] = Checksum{value: checksum, updated: false} 114 | } 115 | 116 | return nil 117 | } 118 | -------------------------------------------------------------------------------- /pkg/utils/limiter.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | type Limiter struct { 4 | channel chan bool 5 | } 6 | 7 | func NewLimiter(limit int) *Limiter { 8 | return &Limiter{channel: make(chan bool, limit)} 9 | } 10 | 11 | func (limiter Limiter) Lock() { 12 | limiter.channel <- true 13 | } 14 | 15 | func (limiter Limiter) Unlock() { 16 | <-limiter.channel 17 | } 18 | -------------------------------------------------------------------------------- /pkg/utils/logger.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "path" 5 | 6 | log "github.com/sirupsen/logrus" 7 | ) 8 | 9 | var debug bool 10 | 11 | func SetDebug(_debug bool) { 12 | debug = _debug 13 | 14 | // Turn logging debug info on/off 15 | if debug { 16 | log.SetLevel(log.DebugLevel) 17 | } else { 18 | log.SetLevel(log.InfoLevel) 19 | } 20 | } 21 | 22 | type logrusHook struct{} 23 | 24 | func (hook logrusHook) Fire(entry *log.Entry) error { 25 | show := _progressShow 26 | 27 | HideProgress() 28 | 29 | fields := log.Fields{} 30 | 31 | for key, value := range entry.Data { 32 | // Keys that start with _ denote debug information 33 | // and are hidden for info messages as long as debugging 34 | // is turned off. The underscore is removed. 35 | if len(key) > 0 && key[0] == '_' { 36 | if entry.Level != log.InfoLevel || debug { 37 | fields[key[1:]] = value 38 | } 39 | } else { 40 | fields[key] = value 41 | } 42 | } 43 | 44 | entry.Data = fields 45 | 46 | if show { 47 | ShowProgress() 48 | } 49 | 50 | return nil 51 | } 52 | 53 | func (hook logrusHook) Levels() []log.Level { 54 | return log.AllLevels 55 | } 56 | 57 | func SetupLogger() { 58 | log.AddHook(logrusHook{}) 59 | } 60 | 61 | func LogFilename(message, filename string) { 62 | name := path.Base(filename) 63 | 64 | log.WithFields(log.Fields{"name": name, "_filename": filename}).Info(message) 65 | } 66 | 67 | func LogDebugFilename(message, filename string) { 68 | name := path.Base(filename) 69 | 70 | log.WithFields(log.Fields{"name": name, "_filename": filename}).Debug(message) 71 | } 72 | 73 | func LogURL(message, url string) { 74 | name := path.Base(url) 75 | 76 | log.WithFields(log.Fields{"name": name, "_url": url}).Info(message) 77 | } 78 | -------------------------------------------------------------------------------- /pkg/utils/progress.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | 8 | "github.com/briandowns/spinner" 9 | ) 10 | 11 | var _spinner *spinner.Spinner 12 | var _progressSteps int 13 | var _progressStep int 14 | var _progressShow bool 15 | var _mutex sync.Mutex 16 | var _supressProgress bool 17 | 18 | func init() { 19 | _mutex = sync.Mutex{} 20 | 21 | _spinner = spinner.New(spinner.CharSets[9], 100*time.Millisecond) 22 | } 23 | 24 | func SupressProgress(hide bool) { 25 | _supressProgress = hide 26 | } 27 | 28 | func ShowProgress() { 29 | if _supressProgress { 30 | return 31 | } 32 | 33 | _progressPercentage := 0 34 | 35 | if _progressSteps > 0 { 36 | _progressPercentage = int(float32(_progressStep+1) * 100.0 / float32(_progressSteps)) 37 | 38 | if _progressPercentage > 100 { 39 | _progressPercentage = 100 40 | } 41 | } 42 | 43 | _spinner.Prefix = "[" 44 | _spinner.Suffix = fmt.Sprintf("] Progress: %d%%", _progressPercentage) 45 | 46 | _spinner.Start() 47 | 48 | _progressShow = true 49 | } 50 | 51 | func HideProgress() { 52 | _spinner.Stop() 53 | 54 | _progressShow = false 55 | } 56 | 57 | func IncreaseProgressStep() { 58 | _mutex.Lock() 59 | defer _mutex.Unlock() 60 | 61 | _progressStep++ 62 | } 63 | 64 | func SetProgressSteps(steps int) { 65 | _progressSteps = steps 66 | } 67 | -------------------------------------------------------------------------------- /pkg/utils/tasks.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "sync" 4 | 5 | type Task func() error 6 | type Tasks []Task 7 | type Errors []error 8 | 9 | func RunParallelTasks(tasks Tasks, parallel bool) (errors Errors) { 10 | if !parallel { 11 | for _, task := range tasks { 12 | if error := task(); error != nil { 13 | errors = append(errors, error) 14 | 15 | return 16 | } 17 | } 18 | 19 | return 20 | } 21 | 22 | waitGroup := sync.WaitGroup{} 23 | 24 | waitGroup.Add(len(tasks)) 25 | 26 | errorChannel := make(chan error, 1) 27 | finishedChannel := make(chan bool, 1) 28 | 29 | // Schedule tasks to be executed 30 | for _, task := range tasks { 31 | go func(_task Task) { 32 | if error := _task(); error != nil { 33 | errorChannel <- error 34 | } 35 | 36 | waitGroup.Done() 37 | }(task) 38 | } 39 | 40 | // Wait for all tasks to be done and send notification 41 | go func() { 42 | waitGroup.Wait() 43 | 44 | close(finishedChannel) 45 | }() 46 | 47 | done := false 48 | 49 | // Collect errors and wait for all tasks to be done 50 | for !done { 51 | select { 52 | case <-finishedChannel: 53 | done = true 54 | case error := <-errorChannel: 55 | errors = append(errors, error) 56 | } 57 | } 58 | 59 | return 60 | } 61 | -------------------------------------------------------------------------------- /pkg/utils/templates.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "path" 5 | 6 | "github.com/darxkies/k8s-tew/data" 7 | log "github.com/sirupsen/logrus" 8 | ) 9 | 10 | func GetTemplate(name string) string { 11 | content, error := data.Templates.ReadFile(path.Join("templates", name)) 12 | if error != nil { 13 | log.WithFields(log.Fields{"name": name, "error": error}).Panic("Template failure") 14 | } 15 | 16 | return string(content) 17 | } 18 | -------------------------------------------------------------------------------- /pkg/version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | var Version string 4 | -------------------------------------------------------------------------------- /setup/centos-multi-node/Makefile: -------------------------------------------------------------------------------- 1 | .EXPORT_ALL_VARIABLES: 2 | 3 | VAGRANT_VAGRANTFILE=../../Vagrantfile 4 | OS=centos 5 | IP_PREFIX=192.168.140 6 | MULTI_NODE=true 7 | 8 | run: destroy up deploy 9 | 10 | deploy: 11 | k8s-tew initialize -f 12 | k8s-tew configure --controller-virtual-ip=$(IP_PREFIX).20 13 | k8s-tew configure --controller-virtual-ip-interface=eth1 14 | k8s-tew configure --worker-virtual-ip=$(IP_PREFIX).10 15 | k8s-tew configure --worker-virtual-ip-interface=eth1 16 | k8s-tew configure --public-network=$(IP_PREFIX).0/24 17 | k8s-tew node-add -n controller00 -i $(IP_PREFIX).200 -l controller 18 | k8s-tew node-add -n controller01 -i $(IP_PREFIX).201 -l controller 19 | k8s-tew node-add -n controller02 -i $(IP_PREFIX).202 -l controller 20 | k8s-tew node-add -n worker00 -i $(IP_PREFIX).100 -l worker,storage 21 | k8s-tew node-add -n worker01 -i $(IP_PREFIX).101 -l worker,storage 22 | k8s-tew generate 23 | k8s-tew deploy 24 | 25 | destroy: 26 | vagrant destroy -f 27 | 28 | up: 29 | vagrant up 30 | 31 | halt: 32 | vagrant halt 33 | 34 | dashboard: 35 | k8s-tew dashboard kubernetes -o 36 | 37 | ssh-controller00: 38 | vagrant ssh controller00 39 | 40 | ssh-controller01: 41 | vagrant ssh controller01 42 | 43 | ssh-controller02: 44 | vagrant ssh controller02 45 | 46 | ssh-worker00: 47 | vagrant ssh worker00 48 | 49 | ssh-worker01: 50 | vagrant ssh worker01 51 | 52 | forward-80: 53 | sudo socat -d -v TCP-LISTEN:80,fork TCP:$(IP_PREFIX).20:80 54 | 55 | forward-443: 56 | sudo socat -d -v TCP-LISTEN:443,fork TCP:$(IP_PREFIX).20:443 57 | -------------------------------------------------------------------------------- /setup/centos-single-node/Makefile: -------------------------------------------------------------------------------- 1 | .EXPORT_ALL_VARIABLES: 2 | 3 | VAGRANT_VAGRANTFILE=../../Vagrantfile 4 | OS=centos 5 | IP_PREFIX=192.168.130 6 | 7 | run: destroy up deploy 8 | 9 | deploy: 10 | k8s-tew initialize -f 11 | k8s-tew configure --public-network=$(IP_PREFIX).0/24 12 | k8s-tew node-add -n single-node -i $(IP_PREFIX).50 -x 0 -l controller,worker,storage 13 | k8s-tew generate 14 | k8s-tew deploy 15 | 16 | destroy: 17 | vagrant destroy -f 18 | 19 | up: 20 | vagrant up 21 | 22 | halt: 23 | vagrant halt 24 | 25 | dashboard: 26 | k8s-tew dashboard kubernetes -o 27 | 28 | ssh: 29 | vagrant ssh 30 | 31 | forward-80: 32 | sudo socat -d -v TCP-LISTEN:80,fork TCP:$(IP_PREFIX).50:80 33 | 34 | forward-443: 35 | sudo socat -d -v TCP-LISTEN:443,fork TCP:$(IP_PREFIX).50:443 36 | -------------------------------------------------------------------------------- /setup/checks/backup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function wait_for_pod() 4 | { 5 | echo "waiting for pod $1" 6 | 7 | while [[ $(kubectl get pods -n showcase $1 -o 'jsonpath={..status.conditions[?(@.type=="Ready")].status}') != "True" ]]; do 8 | sleep 1 9 | done 10 | } 11 | 12 | function get_wordpress_pod_name() 13 | { 14 | kubectl get pods --selector tier=frontend -n showcase -o 'jsonpath={..metadata.name}' 15 | } 16 | 17 | function get_mysql_pod_name() 18 | { 19 | kubectl get pods --selector tier=mysql -n showcase -o 'jsonpath={..metadata.name}' 20 | } 21 | 22 | function write_to_pod() 23 | { 24 | kubectl exec -t -i -n showcase $1 -- sh -c "echo $2 > $3; sync" 25 | } 26 | 27 | function read_from_pod() 28 | { 29 | kubectl exec -t -i -n showcase $1 -- sh -c "cat $2" | tr -d '\r' | tr -d '\n' 30 | } 31 | 32 | function check_content() 33 | { 34 | local content=$(read_from_pod $1 $3) 35 | 36 | if [ "$content" == "$2" ] 37 | then 38 | echo "content of pod $1 is OK" 39 | else 40 | echo "***content of pod $1 is wrong: got '$content' but expected '$2'***" 41 | fi 42 | } 43 | 44 | function check_showcase() 45 | { 46 | local wordpress_content="wordpress-check" 47 | local wordpress_file="/var/www/html/check" 48 | local mysql_content="mysql-check" 49 | local mysql_file="/var/lib/mysql/check" 50 | local wordpress_pod_name=$(get_wordpress_pod_name) 51 | local mysql_pod_name=$(get_mysql_pod_name) 52 | 53 | write_to_pod $wordpress_pod_name $wordpress_content $wordpress_file 54 | write_to_pod $mysql_pod_name $mysql_content $mysql_file 55 | 56 | velero backup delete showcase --confirm 57 | 58 | velero backup create showcase --include-namespaces showcase --wait 59 | 60 | kubectl delete namespace showcase 61 | 62 | velero restore create --from-backup showcase --wait 63 | 64 | wait_for_pod $wordpress_pod_name 65 | wait_for_pod $mysql_pod_name 66 | 67 | check_content $wordpress_pod_name $wordpress_content $wordpress_file 68 | check_content $mysql_pod_name $mysql_content $mysql_file 69 | } 70 | 71 | check_showcase 72 | -------------------------------------------------------------------------------- /setup/checks/ceph.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | VERSION=devel 4 | 5 | function wait_for_pod() 6 | { 7 | echo "waiting for pod $1" 8 | 9 | while [[ $(kubectl get pods $1 -o 'jsonpath={..status.conditions[?(@.type=="Ready")].status}') != "True" ]]; do 10 | sleep 1 11 | done 12 | } 13 | 14 | function write_to_pod() 15 | { 16 | kubectl exec -t -i $1 -- sh -c "echo $2 > /var/lib/www/$3/index.html;sync" 17 | } 18 | 19 | function read_from_pod() 20 | { 21 | kubectl exec -t -i $1 -- sh -c "cat /var/lib/www/$2/index.html" | tr -d '\r' 22 | } 23 | 24 | function check_content() 25 | { 26 | local content=$(read_from_pod $1 $3) 27 | 28 | if [ "$content" == "$2" ] 29 | then 30 | echo "content of pod $1 is [OK]" 31 | else 32 | echo "***content of pod $1 is wrong: got '$content' but expected '$2'***" 33 | fi 34 | } 35 | 36 | function wait_for_snapshot() 37 | { 38 | echo "waiting for snapshot $1" 39 | 40 | while [[ $(kubectl get volumesnapshots $1 -o 'jsonpath={..status.readyToUse}') != "true" ]] 41 | do 42 | sleep 1 43 | done 44 | } 45 | 46 | function create_pod() 47 | { 48 | kubectl apply -f https://raw.githubusercontent.com/ceph/ceph-csi/$VERSION/examples/$1/$2.yaml 49 | } 50 | 51 | function create_pvc() 52 | { 53 | curl -sSL https://raw.githubusercontent.com/ceph/ceph-csi/$VERSION/examples/$1/$2.yaml | sed "s/csi-$1-sc/csi-$1/" | kubectl apply -f - 54 | } 55 | 56 | function create_snapshot() 57 | { 58 | kubectl apply -f https://raw.githubusercontent.com/ceph/ceph-csi/$VERSION/examples/$1/$2.yaml 59 | } 60 | 61 | function clean_up_rbd() 62 | { 63 | kubectl delete pod pod-with-raw-block-volume csi-rbd-demo-pod csi-rbd-clone-demo-app csi-rbd-restore-demo-pod 64 | kubectl delete pvc raw-block-pvc rbd-pvc rbd-pvc-clone rbd-pvc-restore 65 | kubectl delete volumesnapshot.snapshot.storage.k8s.io rbd-pvc-snapshot 66 | } 67 | 68 | function clean_up_cephfs() 69 | { 70 | kubectl delete pod csi-cephfs-demo-pod csi-cephfs-clone-demo-app csi-cephfs-restore-demo-pod 71 | kubectl delete pvc csi-cephfs-pvc cephfs-pvc-clone cephfs-pvc-restore 72 | kubectl delete volumesnapshot.snapshot.storage.k8s.io cephfs-pvc-snapshot 73 | } 74 | 75 | function check_rbd() 76 | { 77 | clean_up_rbd 78 | 79 | # Raw - /dev/xvda 80 | create_pvc rbd raw-block-pvc 81 | create_pod rbd raw-block-pod 82 | wait_for_pod pod-with-raw-block-volume 83 | 84 | # PVC 85 | create_pvc rbd pvc 86 | create_pod rbd pod 87 | wait_for_pod csi-rbd-demo-pod 88 | 89 | # Write 90 | write_to_pod csi-rbd-demo-pod "xxx" "html" 91 | 92 | # PVC clone 93 | create_pvc rbd pvc-clone 94 | create_pod rbd pod-clone 95 | wait_for_pod csi-rbd-clone-demo-app 96 | 97 | # Write 98 | write_to_pod csi-rbd-demo-pod "yyy" "html" 99 | 100 | # Snapshot 101 | create_snapshot rbd snapshot 102 | wait_for_snapshot rbd-pvc-snapshot 103 | 104 | # Write 105 | write_to_pod csi-rbd-demo-pod "zzz" "html" 106 | 107 | # PVC restore 108 | create_pvc rbd pvc-restore 109 | create_pod rbd pod-restore 110 | wait_for_pod csi-rbd-restore-demo-pod 111 | 112 | # Check content 113 | check_content csi-rbd-clone-demo-app "xxx" "html" 114 | check_content csi-rbd-restore-demo-pod "yyy" "html" 115 | check_content csi-rbd-demo-pod "zzz" "html" 116 | 117 | clean_up_rbd 118 | } 119 | 120 | function check_cephfs() 121 | { 122 | clean_up_cephfs 123 | 124 | # PVC 125 | create_pvc cephfs pvc 126 | create_pod cephfs pod 127 | wait_for_pod csi-cephfs-demo-pod 128 | 129 | # Write 130 | write_to_pod csi-cephfs-demo-pod "xxx" 131 | 132 | # PVC clone 133 | create_pvc cephfs pvc-clone 134 | create_pod cephfs pod-clone 135 | wait_for_pod csi-cephfs-clone-demo-app 136 | 137 | # Write 138 | write_to_pod csi-cephfs-demo-pod "yyy" 139 | 140 | # Snapshot 141 | create_snapshot cephfs snapshot 142 | wait_for_snapshot cephfs-pvc-snapshot 143 | 144 | # Write 145 | write_to_pod csi-cephfs-demo-pod "zzz" 146 | 147 | # PVC restore 148 | create_pvc cephfs pvc-restore 149 | create_pod cephfs pod-restore 150 | wait_for_pod csi-cephfs-restore-demo-pod 151 | 152 | # Check content 153 | check_content csi-cephfs-clone-demo-app "xxx" "html" 154 | check_content csi-cephfs-restore-demo-pod "yyy" "html" 155 | check_content csi-cephfs-demo-pod "zzz" 156 | 157 | clean_up_cephfs 158 | } 159 | 160 | function check_rados_gateway() 161 | { 162 | local access_key=$(k8s-tew dashboard ceph-rados-gateway -q | cut -d ' ' -f 1) 163 | local secret_key=$(k8s-tew dashboard ceph-rados-gateway -q | cut -d ' ' -f 2) 164 | local url=$(k8s-tew dashboard ceph-rados-gateway -q | cut -d ' ' -f 3) 165 | local config=/tmp/s3cfg 166 | local put_file=/tmp/s3put 167 | local get_file=/tmp/s3get 168 | local content="xxx" 169 | local cmd="s3cmd -c $config --no-check-certificate" 170 | 171 | cat < $config 172 | # Setup endpoint 173 | host_base = $url 174 | host_bucket = $url 175 | bucket_location = us-east-1 176 | use_https = True 177 | 178 | # Setup access keys 179 | access_key = $access_key 180 | secret_key = $secret_key 181 | 182 | EOF 183 | 184 | echo $content > $put_file 185 | 186 | [ -f $get_file ] && rm $get_file 187 | 188 | $cmd mb s3://checks 189 | $cmd ls s3:// 190 | $cmd put $put_file s3://checks/content 191 | $cmd get s3://checks/content $get_file 192 | $cmd del s3://checks/content 193 | $cmd rb s3://checks 194 | 195 | if [ "$(cat $put_file)" == "$(cat $get_file)" ] 196 | then 197 | echo "Rados Gateway check [OK]" 198 | else 199 | echo "***Rados Gateway content is not ok***" 200 | fi 201 | 202 | rm $config $put_file $get_file 203 | } 204 | 205 | check_rbd 206 | check_cephfs 207 | check_rados_gateway 208 | -------------------------------------------------------------------------------- /setup/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | 3 | ENV container docker 4 | ENV LC_ALL C 5 | ENV DEBIAN_FRONTEND noninteractive 6 | 7 | EXPOSE 22 8 | 9 | VOLUME ["/sys/fs/cgroup"] 10 | 11 | STOPSIGNAL SIGRTMIN+3 12 | 13 | RUN sed -i 's/# deb/deb/g' /etc/apt/sources.list 14 | 15 | RUN apt-get update \ 16 | && apt-get install -y systemd systemd-sysv openssh-server iproute2 iputils-ping vim less iptables kmod ca-certificates curl libseccomp2 conntrack ethtool socat util-linux mount ebtables udev \ 17 | && apt-get clean \ 18 | && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 19 | 20 | RUN cd /lib/systemd/system/sysinit.target.wants/ \ 21 | && ls | grep -v systemd-tmpfiles-setup | xargs rm -fv $1 \ 22 | && mv /etc/systemd/system/multi-user.target.wants/ssh.service /tmp/ \ 23 | && rm -fv /lib/systemd/system/multi-user.target.wants/* \ 24 | /etc/systemd/system/*.wants/* \ 25 | /lib/systemd/system/local-fs.target.wants/* \ 26 | /lib/systemd/system/sockets.target.wants/*udev* \ 27 | /lib/systemd/system/sockets.target.wants/*initctl* \ 28 | /lib/systemd/system/basic.target.wants/* \ 29 | /lib/systemd/system/anaconda.target.wants/* \ 30 | /lib/systemd/system/plymouth* \ 31 | /lib/systemd/system/systemd-update-utmp* \ 32 | && echo "ReadKMsg=no" >> /etc/systemd/journald.conf \ 33 | && mv /tmp/ssh.service /etc/systemd/system/multi-user.target.wants/ 34 | 35 | RUN mkdir /var/run/sshd 36 | 37 | RUN echo 'root:root' | chpasswd 38 | 39 | RUN sed -ri 's/^#?PermitRootLogin\s+.*/PermitRootLogin yes/' /etc/ssh/sshd_config 40 | RUN sed -ri 's/UsePAM yes/#UsePAM yes/g' /etc/ssh/sshd_config 41 | 42 | RUN mkdir /root/.ssh && \ 43 | touch /root/.ssh/authorized_keys && \ 44 | chmod 700 /root/.ssh && \ 45 | chmod 600 /root/.ssh/authorized_keys 46 | 47 | COPY files/ssh_public_key /root/.ssh/authorized_keys 48 | 49 | CMD ["/lib/systemd/systemd"] 50 | -------------------------------------------------------------------------------- /setup/docker/Makefile: -------------------------------------------------------------------------------- 1 | CONTAINER ?= k8s-tew-in-docker 2 | NAME ?= $(CONTAINER) 3 | SSH_PUBLIC_KEY ?= ~/.ssh/id_rsa.pub 4 | IP ?= $(shell docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $(NAME)) 5 | 6 | build: 7 | mkdir -p files 8 | cp $(SSH_PUBLIC_KEY) files/ssh_public_key 9 | docker build -t $(CONTAINER) . 10 | 11 | start: 12 | docker run \ 13 | -d \ 14 | --name $(NAME) \ 15 | --hostname $(NAME) \ 16 | -v /lib/modules:/lib/modules:ro \ 17 | -v /sys/fs/cgroup:/sys/fs/cgroup \ 18 | -v /var/lib/k8s-tew \ 19 | -v /var/log/k8s-tew \ 20 | --privileged \ 21 | --security-opt seccomp=unconfined \ 22 | --security-opt apparmor=unconfined \ 23 | --rm $(CONTAINER) 24 | 25 | 26 | attach: 27 | docker exec -ti $(NAME) bash 28 | 29 | stop: 30 | docker kill $(NAME) 31 | 32 | shell: 33 | ssh -o StrictHostKeyChecking=no root@$(IP) 34 | 35 | deploy: 36 | k8s-tew initialize -f 37 | k8s-tew configure --public-network=$(IP)/24 38 | k8s-tew configure --metallb-addresses=$(IP)/32 39 | k8s-tew node-add -n $(NAME) -i $(IP) -x 0 -l controller,worker,storage 40 | k8s-tew generate --pull-images --parallel 41 | k8s-tew deploy --import-images --parallel 42 | -------------------------------------------------------------------------------- /setup/local/Makefile: -------------------------------------------------------------------------------- 1 | 2 | run: 3 | k8s-tew initialize -f 4 | # Remove this line if you do not use Ubuntu 5 | k8s-tew configure --resolv-conf=/run/systemd/resolve/resolv.conf 6 | k8s-tew node-add -s 7 | k8s-tew generate 8 | sudo $$(which k8s-tew) run 9 | 10 | dashboard: 11 | k8s-tew dashboard -o 12 | -------------------------------------------------------------------------------- /setup/multipass/Makefile: -------------------------------------------------------------------------------- 1 | .EXPORT_ALL_VARIABLES: 2 | 3 | NAME=single-node 4 | IP=$(shell multipass info $(NAME) 2> /dev/null | grep IPv4 | sed -e "s/IPv4://" | xargs) 5 | SSH_KEY=$(shell cat ~/.ssh/id_rsa.pub) 6 | CLOUD_INIT="disable_root: 0\\nssh_authorized_keys:\\n - $(SSH_KEY)" 7 | 8 | run: destroy create deploy 9 | 10 | deploy: 11 | k8s-tew initialize -f 12 | k8s-tew configure --ingress-domain=darxkies.duckdns.org 13 | k8s-tew configure --email=darxkies@gmail.com 14 | k8s-tew configure --public-network=$(IP)/24 15 | k8s-tew configure --resolv-conf=/run/systemd/resolve/resolv.conf 16 | k8s-tew configure --metallb-addresses=$(IP)/32 17 | k8s-tew node-add -n single-node -i $(IP) -l controller,worker,storage 18 | k8s-tew generate 19 | k8s-tew deploy --parallel --wait=5 20 | 21 | refresh: 22 | k8s-tew generate --parallel 23 | k8s-tew deploy --parallel --wait 24 | 25 | destroy: 26 | -multipass delete $(NAME) -p 27 | 28 | create: 29 | echo $(CLOUD_INIT) | multipass launch -n $(NAME) -c 4 -m 8G -d 40G --cloud-init - 20.04 30 | 31 | start: 32 | multipass start $(NAME) 33 | 34 | stop: 35 | multipass stop $(NAME) 36 | 37 | info: 38 | multipass info $(NAME) 39 | 40 | reboot: halt up 41 | 42 | dashboard: 43 | k8s-tew dashboard -o 44 | 45 | open-web-browser-all: 46 | k8s-tew open-web-browser --all 47 | 48 | shell: 49 | multipass shell $(NAME) 50 | 51 | forward-80: 52 | sudo socat -d -v TCP-LISTEN:80,fork TCP:$(IP):80 53 | 54 | forward-443: 55 | sudo socat -d -v TCP-LISTEN:443,fork TCP:$(IP):443 56 | 57 | k9s: 58 | KUBECONFIG=assets/etc/k8s-tew/k8s/kubeconfig/admin.kubeconfig k9s 59 | -------------------------------------------------------------------------------- /setup/ubuntu-multi-node/Makefile: -------------------------------------------------------------------------------- 1 | .EXPORT_ALL_VARIABLES: 2 | 3 | VAGRANT_VAGRANTFILE=../../Vagrantfile 4 | OS=ubuntu 5 | IP_PREFIX=192.168.120 6 | MULTI_NODE=true 7 | 8 | run: destroy up deploy 9 | 10 | deploy: 11 | k8s-tew initialize -f 12 | k8s-tew configure --controller-virtual-ip=$(IP_PREFIX).20 13 | k8s-tew configure --controller-virtual-ip-interface=eth1 14 | k8s-tew configure --worker-virtual-ip=$(IP_PREFIX).10 15 | k8s-tew configure --worker-virtual-ip-interface=eth1 16 | k8s-tew configure --public-network=$(IP_PREFIX).0/24 17 | k8s-tew configure --resolv-conf=/run/systemd/resolve/resolv.conf 18 | k8s-tew configure --metallb-addresses=$(IP_PREFIX).200/32 19 | k8s-tew node-add -n controller00 -i $(IP_PREFIX).200 -l controller 20 | k8s-tew node-add -n controller01 -i $(IP_PREFIX).201 -l controller 21 | k8s-tew node-add -n controller02 -i $(IP_PREFIX).202 -l controller 22 | k8s-tew node-add -n worker00 -i $(IP_PREFIX).100 -l worker,storage 23 | k8s-tew node-add -n worker01 -i $(IP_PREFIX).101 -l worker,storage 24 | k8s-tew generate 25 | k8s-tew deploy 26 | 27 | destroy: 28 | vagrant destroy -f 29 | 30 | up: 31 | vagrant up 32 | 33 | recreate: destroy up save-clean 34 | 35 | save-clean: 36 | vagrant halt 37 | vagrant snapshot save controller00 clean 38 | vagrant snapshot save controller01 clean 39 | vagrant snapshot save controller02 clean 40 | vagrant snapshot save worker00 clean 41 | vagrant snapshot save worker01 clean 42 | vagrant up --parallel 43 | 44 | restore-clean: 45 | vagrant halt -f 46 | vagrant snapshot restore controller00 clean 47 | vagrant snapshot restore controller01 clean 48 | vagrant snapshot restore controller02 clean 49 | vagrant snapshot restore worker00 clean 50 | vagrant snapshot restore worker01 clean 51 | vagrant up --parallel 52 | 53 | halt: 54 | vagrant halt 55 | 56 | dashboard: 57 | k8s-tew dashboard kubernetes -o 58 | 59 | ssh-controller00: 60 | vagrant ssh controller00 61 | 62 | ssh-controller01: 63 | vagrant ssh controller01 64 | 65 | ssh-controller02: 66 | vagrant ssh controller02 67 | 68 | ssh-worker00: 69 | vagrant ssh worker00 70 | 71 | ssh-worker01: 72 | vagrant ssh worker01 73 | 74 | forward-80: 75 | sudo socat -d -v TCP-LISTEN:80,fork TCP:$(IP_PREFIX).20:80 76 | 77 | forward-443: 78 | sudo socat -d -v TCP-LISTEN:443,fork TCP:$(IP_PREFIX).20:443 79 | -------------------------------------------------------------------------------- /setup/ubuntu-single-node/Makefile: -------------------------------------------------------------------------------- 1 | .EXPORT_ALL_VARIABLES: 2 | 3 | VAGRANT_VAGRANTFILE=../../Vagrantfile 4 | OS=ubuntu 5 | IP_PREFIX=192.168.110 6 | 7 | run: destroy up deploy 8 | 9 | deploy: 10 | k8s-tew initialize -f 11 | k8s-tew configure --public-network=$(IP_PREFIX).0/24 12 | k8s-tew configure --resolv-conf=/run/systemd/resolve/resolv.conf 13 | k8s-tew configure --metallb-addresses=$(IP_PREFIX).50/32 14 | k8s-tew node-add -n single-node -i $(IP_PREFIX).50 -x 0 -l controller,worker,storage 15 | k8s-tew generate 16 | k8s-tew deploy 17 | 18 | destroy: 19 | vagrant destroy -f 20 | 21 | up: 22 | vagrant up 23 | 24 | halt: 25 | vagrant halt 26 | 27 | dashboard: 28 | k8s-tew dashboard kubernetes -o 29 | 30 | ssh: 31 | vagrant ssh 32 | 33 | forward-80: 34 | sudo socat -d -v TCP-LISTEN:80,fork TCP:$(IP_PREFIX).50:80 35 | 36 | forward-443: 37 | sudo socat -d -v TCP-LISTEN:443,fork TCP:$(IP_PREFIX).50:443 38 | 39 | forward-minio-30800: 40 | sudo socat -d -v TCP-LISTEN:30800,fork TCP:$(IP_PREFIX).50:30800 41 | 42 | forward-grafana-30900: 43 | sudo socat -d -v TCP-LISTEN:30900,fork TCP:$(IP_PREFIX).50:30900 44 | 45 | forward-dashboard-32443: 46 | sudo socat -d -v TCP-LISTEN:32443,fork TCP:$(IP_PREFIX).50:32443 47 | 48 | forward-ceph-manager-30700: 49 | sudo socat -d -v TCP-LISTEN:30700,fork TCP:$(IP_PREFIX).50:30700 50 | 51 | forward-kibana-30980: 52 | sudo socat -d -v TCP-LISTEN:30980,fork TCP:$(IP_PREFIX).50:30980 53 | 54 | forward-cerebro-30990: 55 | sudo socat -d -v TCP-LISTEN:30990,fork TCP:$(IP_PREFIX).50:30990 56 | --------------------------------------------------------------------------------