Initial commit.
This commit is contained in:
46
infrastructure_setup/README.md
Normal file
46
infrastructure_setup/README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Infrastructure setup scripts
|
||||
|
||||
Creates a fully functional personal cloud infrastructure on a bare metal Kubernetes (k3s) cluster that provides:
|
||||
|
||||
1. **External access** to services via configured domain names (using ${DOMAIN})
|
||||
2. **Internal-only access** to admin interfaces (via internal.${DOMAIN} subdomains)
|
||||
3. **Secure traffic routing** with automatic TLS
|
||||
4. **Reliable networking** with proper load balancing
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Internet → External DNS → MetalLB LoadBalancer → Traefik → Kubernetes Services
|
||||
↑
|
||||
Internal DNS
|
||||
↑
|
||||
Internal Network
|
||||
```
|
||||
|
||||
## Key Components
|
||||
|
||||
- **MetalLB** - Provides load balancing for bare metal clusters
|
||||
- **Traefik** - Handles ingress traffic, TLS termination, and routing
|
||||
- **cert-manager** - Manages TLS certificates
|
||||
- **CoreDNS** - Provides DNS resolution for services
|
||||
- **Kubernetes Dashboard** - Web UI for cluster management (accessible via https://dashboard.internal.${DOMAIN})
|
||||
|
||||
## Configuration Approach
|
||||
|
||||
All infrastructure components use a consistent configuration approach:
|
||||
|
||||
1. **Environment Variables** - All configuration settings are managed using environment variables loaded by running `source load-env.sh`
|
||||
2. **Template Files** - Configuration files use templates with `${VARIABLE}` syntax
|
||||
3. **Setup Scripts** - Each component has a dedicated script in `infrastructure_setup/` for installation and configuration
|
||||
|
||||
## Idempotent Design
|
||||
|
||||
All setup scripts are designed to be idempotent:
|
||||
|
||||
- Scripts can be run multiple times without causing harm
|
||||
- Each script checks for existing resources before creating new ones
|
||||
- Configuration updates are applied cleanly without duplication
|
||||
- Failed or interrupted setups can be safely retried
|
||||
- Changes to configuration will be properly applied on subsequent runs
|
||||
|
||||
This idempotent approach ensures consistent, reliable infrastructure setup and allows for incremental changes without requiring a complete teardown and rebuild.
|
@@ -0,0 +1,19 @@
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: wildcard-internal-sovereign-cloud
|
||||
namespace: internal
|
||||
spec:
|
||||
secretName: wildcard-internal-sovereign-cloud-tls
|
||||
dnsNames:
|
||||
- "*.internal.${DOMAIN}"
|
||||
- "internal.${DOMAIN}"
|
||||
issuerRef:
|
||||
name: letsencrypt-prod
|
||||
kind: ClusterIssuer
|
||||
duration: 2160h # 90 days
|
||||
renewBefore: 360h # 15 days
|
||||
privateKey:
|
||||
algorithm: RSA
|
||||
size: 2048
|
@@ -0,0 +1,26 @@
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: letsencrypt-prod
|
||||
spec:
|
||||
acme:
|
||||
email: ${EMAIL}
|
||||
privateKeySecretRef:
|
||||
name: letsencrypt-prod
|
||||
server: https://acme-v02.api.letsencrypt.org/directory
|
||||
solvers:
|
||||
# DNS-01 solver for wildcard certificates
|
||||
- dns01:
|
||||
cloudflare:
|
||||
email: ${EMAIL}
|
||||
apiTokenSecretRef:
|
||||
name: cloudflare-api-token
|
||||
key: api-token
|
||||
selector:
|
||||
dnsZones:
|
||||
- "${CLOUDFLARE_DOMAIN}" # This will cover all subdomains
|
||||
# Keep the HTTP-01 solver for non-wildcard certificates
|
||||
- http01:
|
||||
ingress:
|
||||
class: traefik
|
@@ -0,0 +1,26 @@
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: letsencrypt-staging
|
||||
spec:
|
||||
acme:
|
||||
email: ${EMAIL}
|
||||
privateKeySecretRef:
|
||||
name: letsencrypt-staging
|
||||
server: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
solvers:
|
||||
# DNS-01 solver for wildcard certificates
|
||||
- dns01:
|
||||
cloudflare:
|
||||
email: ${EMAIL}
|
||||
apiTokenSecretRef:
|
||||
name: cloudflare-api-token
|
||||
key: api-token
|
||||
selector:
|
||||
dnsZones:
|
||||
- "${DOMAIN}" # This will cover all subdomains
|
||||
# Keep the HTTP-01 solver for non-wildcard certificates
|
||||
- http01:
|
||||
ingress:
|
||||
class: traefik
|
19
infrastructure_setup/cert-manager/wildcard-certificate.yaml
Normal file
19
infrastructure_setup/cert-manager/wildcard-certificate.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: wildcard-sovereign-cloud
|
||||
namespace: default
|
||||
spec:
|
||||
secretName: wildcard-sovereign-cloud-tls
|
||||
dnsNames:
|
||||
- "*.${DOMAIN}"
|
||||
- "${DOMAIN}"
|
||||
issuerRef:
|
||||
name: letsencrypt-prod
|
||||
kind: ClusterIssuer
|
||||
duration: 2160h # 90 days
|
||||
renewBefore: 360h # 15 days
|
||||
privateKey:
|
||||
algorithm: RSA
|
||||
size: 2048
|
48
infrastructure_setup/coredns/coredns-config.yaml
Normal file
48
infrastructure_setup/coredns/coredns-config.yaml
Normal file
@@ -0,0 +1,48 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: coredns
|
||||
namespace: kube-system
|
||||
data:
|
||||
Corefile: |
|
||||
.:53 {
|
||||
errors
|
||||
health
|
||||
ready
|
||||
kubernetes cluster.local in-addr.arpa ip6.arpa {
|
||||
pods insecure
|
||||
fallthrough in-addr.arpa ip6.arpa
|
||||
}
|
||||
hosts {
|
||||
192.168.8.218 box-01
|
||||
192.168.8.222 civil
|
||||
192.168.8.240 traefik.${DOMAIN}
|
||||
192.168.8.241 dns.internal.${DOMAIN}
|
||||
|
||||
# Test records
|
||||
192.168.8.240 test.${DOMAIN}
|
||||
192.168.8.240 example-app.${DOMAIN}
|
||||
192.168.8.240 civilsociety.${DOMAIN}
|
||||
192.168.8.241 test.internal.${DOMAIN}
|
||||
192.168.8.240 example-admin.internal.${DOMAIN}
|
||||
192.168.8.240 dashboard.internal.${DOMAIN}
|
||||
192.168.8.240 kubernetes-dashboard.internal.${DOMAIN}
|
||||
|
||||
ttl 60
|
||||
reload 15s
|
||||
fallthrough
|
||||
}
|
||||
prometheus :9153
|
||||
forward . 8.8.8.8 8.8.4.4 {
|
||||
max_concurrent 1000
|
||||
}
|
||||
cache 30
|
||||
loop
|
||||
reload
|
||||
loadbalance
|
||||
import /etc/coredns/custom/*.override
|
||||
}
|
||||
import /etc/coredns/custom/*.server
|
||||
NodeHosts: |
|
||||
# This field needs to remain for compatibility, even if empty
|
||||
# Host entries are now in the Corefile hosts section
|
25
infrastructure_setup/coredns/coredns-service.yaml
Normal file
25
infrastructure_setup/coredns/coredns-service.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: coredns-lb
|
||||
namespace: kube-system
|
||||
annotations:
|
||||
metallb.universe.tf/loadBalancerIPs: "192.168.8.241"
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
ports:
|
||||
- name: dns
|
||||
port: 53
|
||||
protocol: UDP
|
||||
targetPort: 53
|
||||
- name: dns-tcp
|
||||
port: 53
|
||||
protocol: TCP
|
||||
targetPort: 53
|
||||
- name: metrics
|
||||
port: 9153
|
||||
protocol: TCP
|
||||
targetPort: 9153
|
||||
selector:
|
||||
k8s-app: kube-dns
|
41
infrastructure_setup/coredns/split-horizon.yaml
Normal file
41
infrastructure_setup/coredns/split-horizon.yaml
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
# Split-horizon DNS configuration for CoreDNS
|
||||
# This allows different DNS responses for internal vs external domains
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: coredns-custom
|
||||
namespace: kube-system
|
||||
data:
|
||||
internal-zones.server: |
|
||||
# Internal zone configuration for *.internal.${DOMAIN}
|
||||
internal.${DOMAIN} {
|
||||
errors
|
||||
log
|
||||
hosts {
|
||||
192.168.8.240 example-admin.internal.${DOMAIN}
|
||||
192.168.8.240 dashboard.internal.${DOMAIN}
|
||||
192.168.8.241 test.internal.${DOMAIN}
|
||||
fallthrough
|
||||
}
|
||||
cache 30
|
||||
# Use kubernetes service discovery for internal services
|
||||
kubernetes cluster.local {
|
||||
pods insecure
|
||||
fallthrough in-addr.arpa ip6.arpa
|
||||
}
|
||||
# Forward to Google DNS if not found locally
|
||||
forward . 8.8.8.8 8.8.4.4
|
||||
}
|
||||
|
||||
external-zones.server: |
|
||||
# External zone configuration for *.${DOMAIN}
|
||||
${DOMAIN} {
|
||||
errors
|
||||
log
|
||||
cache 30
|
||||
# For external services, forward to Cloudflare for correct public resolution
|
||||
forward . 1.1.1.1 8.8.8.8 {
|
||||
max_concurrent 1000
|
||||
}
|
||||
}
|
69
infrastructure_setup/externaldns/externaldns.yaml
Normal file
69
infrastructure_setup/externaldns/externaldns.yaml
Normal file
@@ -0,0 +1,69 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: external-dns
|
||||
namespace: externaldns
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: external-dns
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["services", "endpoints", "pods"]
|
||||
verbs: ["get", "watch", "list"]
|
||||
- apiGroups: ["extensions", "networking.k8s.io"]
|
||||
resources: ["ingresses"]
|
||||
verbs: ["get", "watch", "list"]
|
||||
- apiGroups: [""]
|
||||
resources: ["nodes"]
|
||||
verbs: ["list"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: external-dns-viewer
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: external-dns
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: external-dns
|
||||
namespace: externaldns
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: external-dns
|
||||
namespace: externaldns
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: external-dns
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: external-dns
|
||||
spec:
|
||||
serviceAccountName: external-dns
|
||||
containers:
|
||||
- name: external-dns
|
||||
image: registry.k8s.io/external-dns/external-dns:v0.13.4
|
||||
args:
|
||||
- --source=service
|
||||
- --source=ingress
|
||||
- --provider=cloudflare
|
||||
- --txt-owner-id=${CLUSTER_ID}
|
||||
- --log-level=debug
|
||||
- --publish-internal-services # Also publish internal services
|
||||
- --no-cloudflare-proxied
|
||||
env:
|
||||
- name: CF_API_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: cloudflare-api-token
|
||||
key: api-token
|
347
infrastructure_setup/get_helm.sh
Executable file
347
infrastructure_setup/get_helm.sh
Executable file
@@ -0,0 +1,347 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright The Helm Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# The install script is based off of the MIT-licensed script from glide,
|
||||
# the package manager for Go: https://github.com/Masterminds/glide.sh/blob/master/get
|
||||
|
||||
: ${BINARY_NAME:="helm"}
|
||||
: ${USE_SUDO:="true"}
|
||||
: ${DEBUG:="false"}
|
||||
: ${VERIFY_CHECKSUM:="true"}
|
||||
: ${VERIFY_SIGNATURES:="false"}
|
||||
: ${HELM_INSTALL_DIR:="/usr/local/bin"}
|
||||
: ${GPG_PUBRING:="pubring.kbx"}
|
||||
|
||||
HAS_CURL="$(type "curl" &> /dev/null && echo true || echo false)"
|
||||
HAS_WGET="$(type "wget" &> /dev/null && echo true || echo false)"
|
||||
HAS_OPENSSL="$(type "openssl" &> /dev/null && echo true || echo false)"
|
||||
HAS_GPG="$(type "gpg" &> /dev/null && echo true || echo false)"
|
||||
HAS_GIT="$(type "git" &> /dev/null && echo true || echo false)"
|
||||
HAS_TAR="$(type "tar" &> /dev/null && echo true || echo false)"
|
||||
|
||||
# initArch discovers the architecture for this system.
|
||||
initArch() {
|
||||
ARCH=$(uname -m)
|
||||
case $ARCH in
|
||||
armv5*) ARCH="armv5";;
|
||||
armv6*) ARCH="armv6";;
|
||||
armv7*) ARCH="arm";;
|
||||
aarch64) ARCH="arm64";;
|
||||
x86) ARCH="386";;
|
||||
x86_64) ARCH="amd64";;
|
||||
i686) ARCH="386";;
|
||||
i386) ARCH="386";;
|
||||
esac
|
||||
}
|
||||
|
||||
# initOS discovers the operating system for this system.
|
||||
initOS() {
|
||||
OS=$(echo `uname`|tr '[:upper:]' '[:lower:]')
|
||||
|
||||
case "$OS" in
|
||||
# Minimalist GNU for Windows
|
||||
mingw*|cygwin*) OS='windows';;
|
||||
esac
|
||||
}
|
||||
|
||||
# runs the given command as root (detects if we are root already)
|
||||
runAsRoot() {
|
||||
if [ $EUID -ne 0 -a "$USE_SUDO" = "true" ]; then
|
||||
sudo "${@}"
|
||||
else
|
||||
"${@}"
|
||||
fi
|
||||
}
|
||||
|
||||
# verifySupported checks that the os/arch combination is supported for
|
||||
# binary builds, as well whether or not necessary tools are present.
|
||||
verifySupported() {
|
||||
local supported="darwin-amd64\ndarwin-arm64\nlinux-386\nlinux-amd64\nlinux-arm\nlinux-arm64\nlinux-ppc64le\nlinux-s390x\nlinux-riscv64\nwindows-amd64\nwindows-arm64"
|
||||
if ! echo "${supported}" | grep -q "${OS}-${ARCH}"; then
|
||||
echo "No prebuilt binary for ${OS}-${ARCH}."
|
||||
echo "To build from source, go to https://github.com/helm/helm"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "${HAS_CURL}" != "true" ] && [ "${HAS_WGET}" != "true" ]; then
|
||||
echo "Either curl or wget is required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "${VERIFY_CHECKSUM}" == "true" ] && [ "${HAS_OPENSSL}" != "true" ]; then
|
||||
echo "In order to verify checksum, openssl must first be installed."
|
||||
echo "Please install openssl or set VERIFY_CHECKSUM=false in your environment."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "${VERIFY_SIGNATURES}" == "true" ]; then
|
||||
if [ "${HAS_GPG}" != "true" ]; then
|
||||
echo "In order to verify signatures, gpg must first be installed."
|
||||
echo "Please install gpg or set VERIFY_SIGNATURES=false in your environment."
|
||||
exit 1
|
||||
fi
|
||||
if [ "${OS}" != "linux" ]; then
|
||||
echo "Signature verification is currently only supported on Linux."
|
||||
echo "Please set VERIFY_SIGNATURES=false or verify the signatures manually."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${HAS_GIT}" != "true" ]; then
|
||||
echo "[WARNING] Could not find git. It is required for plugin installation."
|
||||
fi
|
||||
|
||||
if [ "${HAS_TAR}" != "true" ]; then
|
||||
echo "[ERROR] Could not find tar. It is required to extract the helm binary archive."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# checkDesiredVersion checks if the desired version is available.
|
||||
checkDesiredVersion() {
|
||||
if [ "x$DESIRED_VERSION" == "x" ]; then
|
||||
# Get tag from release URL
|
||||
local latest_release_url="https://get.helm.sh/helm-latest-version"
|
||||
local latest_release_response=""
|
||||
if [ "${HAS_CURL}" == "true" ]; then
|
||||
latest_release_response=$( curl -L --silent --show-error --fail "$latest_release_url" 2>&1 || true )
|
||||
elif [ "${HAS_WGET}" == "true" ]; then
|
||||
latest_release_response=$( wget "$latest_release_url" -q -O - 2>&1 || true )
|
||||
fi
|
||||
TAG=$( echo "$latest_release_response" | grep '^v[0-9]' )
|
||||
if [ "x$TAG" == "x" ]; then
|
||||
printf "Could not retrieve the latest release tag information from %s: %s\n" "${latest_release_url}" "${latest_release_response}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
TAG=$DESIRED_VERSION
|
||||
fi
|
||||
}
|
||||
|
||||
# checkHelmInstalledVersion checks which version of helm is installed and
|
||||
# if it needs to be changed.
|
||||
checkHelmInstalledVersion() {
|
||||
if [[ -f "${HELM_INSTALL_DIR}/${BINARY_NAME}" ]]; then
|
||||
local version=$("${HELM_INSTALL_DIR}/${BINARY_NAME}" version --template="{{ .Version }}")
|
||||
if [[ "$version" == "$TAG" ]]; then
|
||||
echo "Helm ${version} is already ${DESIRED_VERSION:-latest}"
|
||||
return 0
|
||||
else
|
||||
echo "Helm ${TAG} is available. Changing from version ${version}."
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# downloadFile downloads the latest binary package and also the checksum
|
||||
# for that binary.
|
||||
downloadFile() {
|
||||
HELM_DIST="helm-$TAG-$OS-$ARCH.tar.gz"
|
||||
DOWNLOAD_URL="https://get.helm.sh/$HELM_DIST"
|
||||
CHECKSUM_URL="$DOWNLOAD_URL.sha256"
|
||||
HELM_TMP_ROOT="$(mktemp -dt helm-installer-XXXXXX)"
|
||||
HELM_TMP_FILE="$HELM_TMP_ROOT/$HELM_DIST"
|
||||
HELM_SUM_FILE="$HELM_TMP_ROOT/$HELM_DIST.sha256"
|
||||
echo "Downloading $DOWNLOAD_URL"
|
||||
if [ "${HAS_CURL}" == "true" ]; then
|
||||
curl -SsL "$CHECKSUM_URL" -o "$HELM_SUM_FILE"
|
||||
curl -SsL "$DOWNLOAD_URL" -o "$HELM_TMP_FILE"
|
||||
elif [ "${HAS_WGET}" == "true" ]; then
|
||||
wget -q -O "$HELM_SUM_FILE" "$CHECKSUM_URL"
|
||||
wget -q -O "$HELM_TMP_FILE" "$DOWNLOAD_URL"
|
||||
fi
|
||||
}
|
||||
|
||||
# verifyFile verifies the SHA256 checksum of the binary package
|
||||
# and the GPG signatures for both the package and checksum file
|
||||
# (depending on settings in environment).
|
||||
verifyFile() {
|
||||
if [ "${VERIFY_CHECKSUM}" == "true" ]; then
|
||||
verifyChecksum
|
||||
fi
|
||||
if [ "${VERIFY_SIGNATURES}" == "true" ]; then
|
||||
verifySignatures
|
||||
fi
|
||||
}
|
||||
|
||||
# installFile installs the Helm binary.
|
||||
installFile() {
|
||||
HELM_TMP="$HELM_TMP_ROOT/$BINARY_NAME"
|
||||
mkdir -p "$HELM_TMP"
|
||||
tar xf "$HELM_TMP_FILE" -C "$HELM_TMP"
|
||||
HELM_TMP_BIN="$HELM_TMP/$OS-$ARCH/helm"
|
||||
echo "Preparing to install $BINARY_NAME into ${HELM_INSTALL_DIR}"
|
||||
runAsRoot cp "$HELM_TMP_BIN" "$HELM_INSTALL_DIR/$BINARY_NAME"
|
||||
echo "$BINARY_NAME installed into $HELM_INSTALL_DIR/$BINARY_NAME"
|
||||
}
|
||||
|
||||
# verifyChecksum verifies the SHA256 checksum of the binary package.
|
||||
verifyChecksum() {
|
||||
printf "Verifying checksum... "
|
||||
local sum=$(openssl sha1 -sha256 ${HELM_TMP_FILE} | awk '{print $2}')
|
||||
local expected_sum=$(cat ${HELM_SUM_FILE})
|
||||
if [ "$sum" != "$expected_sum" ]; then
|
||||
echo "SHA sum of ${HELM_TMP_FILE} does not match. Aborting."
|
||||
exit 1
|
||||
fi
|
||||
echo "Done."
|
||||
}
|
||||
|
||||
# verifySignatures obtains the latest KEYS file from GitHub main branch
|
||||
# as well as the signature .asc files from the specific GitHub release,
|
||||
# then verifies that the release artifacts were signed by a maintainer's key.
|
||||
verifySignatures() {
|
||||
printf "Verifying signatures... "
|
||||
local keys_filename="KEYS"
|
||||
local github_keys_url="https://raw.githubusercontent.com/helm/helm/main/${keys_filename}"
|
||||
if [ "${HAS_CURL}" == "true" ]; then
|
||||
curl -SsL "${github_keys_url}" -o "${HELM_TMP_ROOT}/${keys_filename}"
|
||||
elif [ "${HAS_WGET}" == "true" ]; then
|
||||
wget -q -O "${HELM_TMP_ROOT}/${keys_filename}" "${github_keys_url}"
|
||||
fi
|
||||
local gpg_keyring="${HELM_TMP_ROOT}/keyring.gpg"
|
||||
local gpg_homedir="${HELM_TMP_ROOT}/gnupg"
|
||||
mkdir -p -m 0700 "${gpg_homedir}"
|
||||
local gpg_stderr_device="/dev/null"
|
||||
if [ "${DEBUG}" == "true" ]; then
|
||||
gpg_stderr_device="/dev/stderr"
|
||||
fi
|
||||
gpg --batch --quiet --homedir="${gpg_homedir}" --import "${HELM_TMP_ROOT}/${keys_filename}" 2> "${gpg_stderr_device}"
|
||||
gpg --batch --no-default-keyring --keyring "${gpg_homedir}/${GPG_PUBRING}" --export > "${gpg_keyring}"
|
||||
local github_release_url="https://github.com/helm/helm/releases/download/${TAG}"
|
||||
if [ "${HAS_CURL}" == "true" ]; then
|
||||
curl -SsL "${github_release_url}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc" -o "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc"
|
||||
curl -SsL "${github_release_url}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc" -o "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc"
|
||||
elif [ "${HAS_WGET}" == "true" ]; then
|
||||
wget -q -O "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc" "${github_release_url}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc"
|
||||
wget -q -O "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc" "${github_release_url}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc"
|
||||
fi
|
||||
local error_text="If you think this might be a potential security issue,"
|
||||
error_text="${error_text}\nplease see here: https://github.com/helm/community/blob/master/SECURITY.md"
|
||||
local num_goodlines_sha=$(gpg --verify --keyring="${gpg_keyring}" --status-fd=1 "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc" 2> "${gpg_stderr_device}" | grep -c -E '^\[GNUPG:\] (GOODSIG|VALIDSIG)')
|
||||
if [[ ${num_goodlines_sha} -lt 2 ]]; then
|
||||
echo "Unable to verify the signature of helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256!"
|
||||
echo -e "${error_text}"
|
||||
exit 1
|
||||
fi
|
||||
local num_goodlines_tar=$(gpg --verify --keyring="${gpg_keyring}" --status-fd=1 "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc" 2> "${gpg_stderr_device}" | grep -c -E '^\[GNUPG:\] (GOODSIG|VALIDSIG)')
|
||||
if [[ ${num_goodlines_tar} -lt 2 ]]; then
|
||||
echo "Unable to verify the signature of helm-${TAG}-${OS}-${ARCH}.tar.gz!"
|
||||
echo -e "${error_text}"
|
||||
exit 1
|
||||
fi
|
||||
echo "Done."
|
||||
}
|
||||
|
||||
# fail_trap is executed if an error occurs.
|
||||
fail_trap() {
|
||||
result=$?
|
||||
if [ "$result" != "0" ]; then
|
||||
if [[ -n "$INPUT_ARGUMENTS" ]]; then
|
||||
echo "Failed to install $BINARY_NAME with the arguments provided: $INPUT_ARGUMENTS"
|
||||
help
|
||||
else
|
||||
echo "Failed to install $BINARY_NAME"
|
||||
fi
|
||||
echo -e "\tFor support, go to https://github.com/helm/helm."
|
||||
fi
|
||||
cleanup
|
||||
exit $result
|
||||
}
|
||||
|
||||
# testVersion tests the installed client to make sure it is working.
|
||||
testVersion() {
|
||||
set +e
|
||||
HELM="$(command -v $BINARY_NAME)"
|
||||
if [ "$?" = "1" ]; then
|
||||
echo "$BINARY_NAME not found. Is $HELM_INSTALL_DIR on your "'$PATH?'
|
||||
exit 1
|
||||
fi
|
||||
set -e
|
||||
}
|
||||
|
||||
# help provides possible cli installation arguments
|
||||
help () {
|
||||
echo "Accepted cli arguments are:"
|
||||
echo -e "\t[--help|-h ] ->> prints this help"
|
||||
echo -e "\t[--version|-v <desired_version>] . When not defined it fetches the latest release tag from the Helm CDN"
|
||||
echo -e "\te.g. --version v3.0.0 or -v canary"
|
||||
echo -e "\t[--no-sudo] ->> install without sudo"
|
||||
}
|
||||
|
||||
# cleanup temporary files to avoid https://github.com/helm/helm/issues/2977
|
||||
cleanup() {
|
||||
if [[ -d "${HELM_TMP_ROOT:-}" ]]; then
|
||||
rm -rf "$HELM_TMP_ROOT"
|
||||
fi
|
||||
}
|
||||
|
||||
# Execution
|
||||
|
||||
#Stop execution on any error
|
||||
trap "fail_trap" EXIT
|
||||
set -e
|
||||
|
||||
# Set debug if desired
|
||||
if [ "${DEBUG}" == "true" ]; then
|
||||
set -x
|
||||
fi
|
||||
|
||||
# Parsing input arguments (if any)
|
||||
export INPUT_ARGUMENTS="${@}"
|
||||
set -u
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
'--version'|-v)
|
||||
shift
|
||||
if [[ $# -ne 0 ]]; then
|
||||
export DESIRED_VERSION="${1}"
|
||||
if [[ "$1" != "v"* ]]; then
|
||||
echo "Expected version arg ('${DESIRED_VERSION}') to begin with 'v', fixing..."
|
||||
export DESIRED_VERSION="v${1}"
|
||||
fi
|
||||
else
|
||||
echo -e "Please provide the desired version. e.g. --version v3.0.0 or -v canary"
|
||||
exit 0
|
||||
fi
|
||||
;;
|
||||
'--no-sudo')
|
||||
USE_SUDO="false"
|
||||
;;
|
||||
'--help'|-h)
|
||||
help
|
||||
exit 0
|
||||
;;
|
||||
*) exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
set +u
|
||||
|
||||
initArch
|
||||
initOS
|
||||
verifySupported
|
||||
checkDesiredVersion
|
||||
if ! checkHelmInstalledVersion; then
|
||||
downloadFile
|
||||
verifyFile
|
||||
installFile
|
||||
fi
|
||||
testVersion
|
||||
cleanup
|
@@ -0,0 +1,103 @@
|
||||
---
|
||||
# Certificate for the dashboard
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: kubernetes-dashboard-tls
|
||||
namespace: kubernetes-dashboard
|
||||
spec:
|
||||
secretName: kubernetes-dashboard-tls
|
||||
issuerRef:
|
||||
name: letsencrypt-prod
|
||||
kind: ClusterIssuer
|
||||
dnsNames:
|
||||
- "dashboard.internal.${DOMAIN}"
|
||||
duration: 2160h # 90 days
|
||||
renewBefore: 360h # 15 days
|
||||
privateKey:
|
||||
algorithm: RSA
|
||||
size: 2048
|
||||
|
||||
---
|
||||
# Internal-only middleware
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: internal-only
|
||||
namespace: kubernetes-dashboard
|
||||
spec:
|
||||
ipWhiteList:
|
||||
# Restrict to local private network ranges
|
||||
sourceRange:
|
||||
- 127.0.0.1/32 # localhost
|
||||
- 10.0.0.0/8 # Private network
|
||||
- 172.16.0.0/12 # Private network
|
||||
- 192.168.0.0/16 # Private network
|
||||
|
||||
---
|
||||
# HTTPS redirect middleware
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: dashboard-redirect-scheme
|
||||
namespace: kubernetes-dashboard
|
||||
spec:
|
||||
redirectScheme:
|
||||
scheme: https
|
||||
permanent: true
|
||||
|
||||
---
|
||||
# IngressRoute for Dashboard
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: kubernetes-dashboard-https
|
||||
namespace: kubernetes-dashboard
|
||||
spec:
|
||||
entryPoints:
|
||||
- websecure
|
||||
routes:
|
||||
- match: Host(`dashboard.internal.${DOMAIN}`)
|
||||
kind: Rule
|
||||
middlewares:
|
||||
- name: internal-only
|
||||
namespace: kubernetes-dashboard
|
||||
services:
|
||||
- name: kubernetes-dashboard
|
||||
port: 443
|
||||
serversTransport: dashboard-transport
|
||||
tls:
|
||||
secretName: kubernetes-dashboard-tls
|
||||
|
||||
---
|
||||
# HTTP to HTTPS redirect
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: kubernetes-dashboard-http
|
||||
namespace: kubernetes-dashboard
|
||||
spec:
|
||||
entryPoints:
|
||||
- web
|
||||
routes:
|
||||
- match: Host(`dashboard.internal.${DOMAIN}`)
|
||||
kind: Rule
|
||||
middlewares:
|
||||
- name: dashboard-redirect-scheme
|
||||
namespace: kubernetes-dashboard
|
||||
services:
|
||||
- name: kubernetes-dashboard
|
||||
port: 443
|
||||
serversTransport: dashboard-transport
|
||||
|
||||
---
|
||||
# ServersTransport for HTTPS backend with skip verify
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: ServersTransport
|
||||
metadata:
|
||||
name: dashboard-transport
|
||||
namespace: kubernetes-dashboard
|
||||
spec:
|
||||
insecureSkipVerify: true
|
||||
serverName: dashboard.internal.${DOMAIN}
|
||||
|
21
infrastructure_setup/metallb/metallb-config.yaml
Normal file
21
infrastructure_setup/metallb/metallb-config.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
# Define IP address pool for MetalLB
|
||||
apiVersion: metallb.io/v1beta1
|
||||
kind: IPAddressPool
|
||||
metadata:
|
||||
name: production
|
||||
namespace: metallb-system
|
||||
spec:
|
||||
addresses:
|
||||
- ${CLUSTER_LOAD_BALANCER_RANGE}
|
||||
|
||||
---
|
||||
# Define Layer 2 advertisement for the IP pool
|
||||
apiVersion: metallb.io/v1beta1
|
||||
kind: L2Advertisement
|
||||
metadata:
|
||||
name: l2-advertisement
|
||||
namespace: metallb-system
|
||||
spec:
|
||||
ipAddressPools:
|
||||
- production
|
16
infrastructure_setup/metallb/metallb-helm-config.yaml
Normal file
16
infrastructure_setup/metallb/metallb-helm-config.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
apiVersion: helm.cattle.io/v1
|
||||
kind: HelmChartConfig
|
||||
metadata:
|
||||
name: metallb
|
||||
namespace: kube-system
|
||||
spec:
|
||||
valuesContent: |-
|
||||
# The new configuration format for MetalLB v0.13.0+
|
||||
apiVersion: v1
|
||||
# We'll use IPAddressPool and L2Advertisement CRs instead of the deprecated configInline
|
||||
# Need to install the CRDs separately
|
||||
crds:
|
||||
enabled: true
|
||||
# Disable controller.configInline since it's deprecated
|
||||
controller:
|
||||
configInline: null
|
21
infrastructure_setup/metallb/metallb-pool.yaml
Normal file
21
infrastructure_setup/metallb/metallb-pool.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
# Define IP address pool for MetalLB using the new format
|
||||
apiVersion: metallb.io/v1beta1
|
||||
kind: IPAddressPool
|
||||
metadata:
|
||||
name: production
|
||||
namespace: metallb-system
|
||||
spec:
|
||||
addresses:
|
||||
- 192.168.8.240-192.168.8.250
|
||||
|
||||
---
|
||||
# Define Layer 2 advertisement for the IP pool using the new format
|
||||
apiVersion: metallb.io/v1beta1
|
||||
kind: L2Advertisement
|
||||
metadata:
|
||||
name: l2-advertisement
|
||||
namespace: metallb-system
|
||||
spec:
|
||||
ipAddressPools:
|
||||
- production
|
46
infrastructure_setup/setup-all.sh
Executable file
46
infrastructure_setup/setup-all.sh
Executable file
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Navigate to script directory
|
||||
SCRIPT_PATH="$(realpath "${BASH_SOURCE[0]}")"
|
||||
SCRIPT_DIR="$(dirname "$SCRIPT_PATH")"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
echo "Setting up infrastructure components for k3s..."
|
||||
|
||||
# Make all script files executable
|
||||
chmod +x *.sh
|
||||
|
||||
# Utils
|
||||
./setup-utils.sh
|
||||
|
||||
# Setup MetalLB (must be first for IP allocation)
|
||||
./setup-metallb.sh
|
||||
|
||||
# Setup Traefik
|
||||
./setup-traefik.sh
|
||||
|
||||
# Setup CoreDNS
|
||||
./setup-coredns.sh
|
||||
|
||||
# Setup cert-manager
|
||||
./setup-cert-manager.sh
|
||||
|
||||
# Setup ExternalDNS
|
||||
./setup-externaldns.sh
|
||||
|
||||
# Setup Kubernetes Dashboard
|
||||
./setup-dashboard.sh
|
||||
|
||||
echo "Infrastructure setup complete!"
|
||||
echo
|
||||
echo "Next steps:"
|
||||
echo "1. Install Helm charts for non-infrastructure components"
|
||||
echo "2. Access the dashboard at: https://dashboard.internal.${DOMAIN}"
|
||||
echo "3. Get the dashboard token with: ./bin/dashboard-token"
|
||||
echo
|
||||
echo "To verify components, run:"
|
||||
echo "- kubectl get pods -n cert-manager"
|
||||
echo "- kubectl get pods -n externaldns"
|
||||
echo "- kubectl get pods -n kubernetes-dashboard"
|
||||
echo "- kubectl get clusterissuers"
|
102
infrastructure_setup/setup-cert-manager.sh
Executable file
102
infrastructure_setup/setup-cert-manager.sh
Executable file
@@ -0,0 +1,102 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Navigate to script directory
|
||||
SCRIPT_PATH="$(realpath "${BASH_SOURCE[0]}")"
|
||||
SCRIPT_DIR="$(dirname "$SCRIPT_PATH")"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
# Source environment variables
|
||||
if [[ -f "../load-env.sh" ]]; then
|
||||
source ../load-env.sh
|
||||
fi
|
||||
|
||||
echo "Setting up cert-manager..."
|
||||
|
||||
# Create cert-manager namespace
|
||||
kubectl create namespace cert-manager --dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
# Install cert-manager using the official installation method
|
||||
# This installs CRDs, controllers, and webhook components
|
||||
echo "Installing cert-manager components..."
|
||||
# Using stable URL for cert-manager installation
|
||||
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.1/cert-manager.yaml || \
|
||||
kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.13.1/cert-manager.yaml
|
||||
|
||||
# Wait for cert-manager to be ready
|
||||
echo "Waiting for cert-manager to be ready..."
|
||||
kubectl wait --for=condition=Available deployment/cert-manager -n cert-manager --timeout=120s
|
||||
kubectl wait --for=condition=Available deployment/cert-manager-cainjector -n cert-manager --timeout=120s
|
||||
kubectl wait --for=condition=Available deployment/cert-manager-webhook -n cert-manager --timeout=120s
|
||||
|
||||
# Add delay to allow webhook to be fully ready
|
||||
echo "Waiting additional time for cert-manager webhook to be fully operational..."
|
||||
sleep 30
|
||||
|
||||
# Setup Cloudflare API token for DNS01 challenges
|
||||
if [[ -n "${CLOUDFLARE_API_TOKEN}" ]]; then
|
||||
echo "Creating Cloudflare API token secret in cert-manager namespace..."
|
||||
kubectl create secret generic cloudflare-api-token \
|
||||
--namespace cert-manager \
|
||||
--from-literal=api-token="${CLOUDFLARE_API_TOKEN}" \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
# Create internal namespace if it doesn't exist
|
||||
echo "Creating internal namespace if it doesn't exist..."
|
||||
kubectl create namespace internal --dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
# Create the same secret in the internal namespace
|
||||
echo "Creating Cloudflare API token secret in internal namespace..."
|
||||
kubectl create secret generic cloudflare-api-token \
|
||||
--namespace internal \
|
||||
--from-literal=api-token="${CLOUDFLARE_API_TOKEN}" \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
else
|
||||
echo "Warning: CLOUDFLARE_API_TOKEN not set. DNS01 challenges will not work."
|
||||
fi
|
||||
|
||||
# Apply Let's Encrypt issuers
|
||||
echo "Creating Let's Encrypt issuers..."
|
||||
cat ${SCRIPT_DIR}/cert-manager/letsencrypt-staging-dns01.yaml | envsubst | kubectl apply -f -
|
||||
cat ${SCRIPT_DIR}/cert-manager/letsencrypt-prod-dns01.yaml | envsubst | kubectl apply -f -
|
||||
|
||||
# Wait for issuers to be ready
|
||||
echo "Waiting for Let's Encrypt issuers to be ready..."
|
||||
sleep 10
|
||||
|
||||
# Apply wildcard certificates
|
||||
echo "Creating wildcard certificates..."
|
||||
cat ${SCRIPT_DIR}/cert-manager/internal-wildcard-certificate.yaml | envsubst | kubectl apply -f -
|
||||
cat ${SCRIPT_DIR}/cert-manager/wildcard-certificate.yaml | envsubst | kubectl apply -f -
|
||||
echo "Wildcard certificate creation initiated. This may take some time to complete depending on DNS propagation."
|
||||
|
||||
# Wait for the certificates to be issued (with a timeout)
|
||||
echo "Waiting for wildcard certificates to be ready (this may take several minutes)..."
|
||||
kubectl wait --for=condition=Ready certificate wildcard-soverign-cloud -n default --timeout=300s || true
|
||||
kubectl wait --for=condition=Ready certificate wildcard-internal-sovereign-cloud -n internal --timeout=300s || true
|
||||
|
||||
# Copy the internal wildcard certificate to example-admin namespace
|
||||
echo "Copying internal wildcard certificate to example-admin namespace..."
|
||||
if kubectl get namespace example-admin &>/dev/null; then
|
||||
# Create example-admin namespace if it doesn't exist
|
||||
kubectl create namespace example-admin --dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
# Get the internal wildcard certificate secret and copy it to example-admin namespace
|
||||
if kubectl get secret wildcard-internal-sovereign-cloud-tls -n internal &>/dev/null; then
|
||||
kubectl get secret wildcard-internal-sovereign-cloud-tls -n internal -o yaml | \
|
||||
sed 's/namespace: internal/namespace: example-admin/' | \
|
||||
kubectl apply -f -
|
||||
echo "Certificate copied to example-admin namespace"
|
||||
else
|
||||
echo "Internal wildcard certificate not ready yet. Please manually copy it later with:"
|
||||
echo " kubectl get secret wildcard-internal-sovereign-cloud-tls -n internal -o yaml | \\"
|
||||
echo " sed 's/namespace: internal/namespace: example-admin/' | \\"
|
||||
echo " kubectl apply -f -"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "cert-manager setup complete!"
|
||||
echo ""
|
||||
echo "To verify the installation:"
|
||||
echo " kubectl get pods -n cert-manager"
|
||||
echo " kubectl get clusterissuers"
|
35
infrastructure_setup/setup-coredns.sh
Executable file
35
infrastructure_setup/setup-coredns.sh
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
SCRIPT_PATH="$(realpath "${BASH_SOURCE[0]}")"
|
||||
SCRIPT_DIR="$(dirname "$SCRIPT_PATH")"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
# Source environment variables
|
||||
if [[ -f "../load-env.sh" ]]; then
|
||||
source ../load-env.sh
|
||||
fi
|
||||
|
||||
echo "Setting up CoreDNS for k3s..."
|
||||
echo "Script directory: ${SCRIPT_DIR}"
|
||||
echo "Current directory: $(pwd)"
|
||||
|
||||
# Apply the custom config for the k3s-provided CoreDNS
|
||||
echo "Applying CoreDNS configuration..."
|
||||
echo "Looking for file: ${SCRIPT_DIR}/coredns/coredns-config.yaml"
|
||||
# Simply use envsubst for variable expansion and apply
|
||||
cat "${SCRIPT_DIR}/coredns/coredns-config.yaml" | envsubst | kubectl apply -f -
|
||||
|
||||
# Apply the split-horizon configuration
|
||||
echo "Applying split-horizon DNS configuration..."
|
||||
cat "${SCRIPT_DIR}/coredns/split-horizon.yaml" | envsubst | kubectl apply -f -
|
||||
|
||||
# Apply the LoadBalancer service for external access to CoreDNS
|
||||
echo "Applying CoreDNS service configuration..."
|
||||
cat "${SCRIPT_DIR}/coredns/coredns-service.yaml" | envsubst | kubectl apply -f -
|
||||
|
||||
# Restart CoreDNS pods to apply the changes
|
||||
echo "Restarting CoreDNS pods to apply changes..."
|
||||
kubectl delete pod -n kube-system -l k8s-app=kube-dns
|
||||
|
||||
echo "CoreDNS setup complete!"
|
94
infrastructure_setup/setup-dashboard.sh
Executable file
94
infrastructure_setup/setup-dashboard.sh
Executable file
@@ -0,0 +1,94 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Store the script directory path for later use
|
||||
SCRIPT_PATH="$(realpath "${BASH_SOURCE[0]}")"
|
||||
SCRIPT_DIR="$(dirname "$SCRIPT_PATH")"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
# Source environment variables
|
||||
if [[ -f "../load-env.sh" ]]; then
|
||||
source ../load-env.sh
|
||||
fi
|
||||
|
||||
echo "Setting up Kubernetes Dashboard..."
|
||||
|
||||
# Apply the official dashboard installation
|
||||
echo "Installing Kubernetes Dashboard core components..."
|
||||
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml
|
||||
|
||||
# Create admin service account and token
|
||||
cat << EOF | kubectl apply -f -
|
||||
---
|
||||
# Service Account and RBAC
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: dashboard-admin
|
||||
namespace: kubernetes-dashboard
|
||||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: dashboard-admin
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: dashboard-admin
|
||||
namespace: kubernetes-dashboard
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: cluster-admin
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
|
||||
---
|
||||
# Token for dashboard-admin
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: dashboard-admin-token
|
||||
namespace: kubernetes-dashboard
|
||||
annotations:
|
||||
kubernetes.io/service-account.name: dashboard-admin
|
||||
type: kubernetes.io/service-account-token
|
||||
EOF
|
||||
|
||||
# Clean up any existing IngressRoute resources that might conflict
|
||||
echo "Cleaning up any existing dashboard resources to prevent conflicts..."
|
||||
# Clean up all IngressRoutes related to dashboard in both namespaces
|
||||
kubectl delete ingressroute -n kubernetes-dashboard --all --ignore-not-found
|
||||
kubectl delete ingressroute -n kube-system kubernetes-dashboard --ignore-not-found
|
||||
kubectl delete ingressroute -n kube-system kubernetes-dashboard-alt --ignore-not-found
|
||||
kubectl delete ingressroute -n kube-system kubernetes-dashboard-http --ignore-not-found
|
||||
kubectl delete ingressroute -n kube-system kubernetes-dashboard-alt-http --ignore-not-found
|
||||
|
||||
# Clean up middleware in both namespaces
|
||||
kubectl delete middleware -n kubernetes-dashboard --all --ignore-not-found
|
||||
kubectl delete middleware -n kube-system dashboard-internal-only --ignore-not-found
|
||||
kubectl delete middleware -n kube-system dashboard-redirect-scheme --ignore-not-found
|
||||
|
||||
# Clean up ServersTransport in both namespaces
|
||||
kubectl delete serverstransport -n kubernetes-dashboard dashboard-transport --ignore-not-found
|
||||
kubectl delete serverstransport -n kube-system dashboard-transport --ignore-not-found
|
||||
|
||||
# Apply the dashboard configuration
|
||||
echo "Applying dashboard configuration in kube-system namespace..."
|
||||
# Use just the kube-system version since it works better with Traefik
|
||||
cat "${SCRIPT_DIR}/kubernetes-dashboard/dashboard-kube-system.yaml" | envsubst | kubectl apply -f -
|
||||
|
||||
# No need to manually update the CoreDNS ConfigMap anymore
|
||||
# The setup-coredns.sh script now handles variable substitution correctly
|
||||
|
||||
# Restart CoreDNS to pick up the changes
|
||||
kubectl delete pods -n kube-system -l k8s-app=kube-dns
|
||||
echo "Restarted CoreDNS to pick up DNS changes"
|
||||
|
||||
# Wait for dashboard to be ready
|
||||
echo "Waiting for Kubernetes Dashboard to be ready..."
|
||||
kubectl rollout status deployment/kubernetes-dashboard -n kubernetes-dashboard --timeout=60s
|
||||
|
||||
echo "Kubernetes Dashboard setup complete!"
|
||||
echo "Access the dashboard at: https://dashboard.internal.${DOMAIN}"
|
||||
echo ""
|
||||
echo "To get the authentication token, run:"
|
||||
echo "./bin/dashboard-token"
|
55
infrastructure_setup/setup-externaldns.sh
Executable file
55
infrastructure_setup/setup-externaldns.sh
Executable file
@@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Navigate to script directory
|
||||
SCRIPT_PATH="$(realpath "${BASH_SOURCE[0]}")"
|
||||
SCRIPT_DIR="$(dirname "$SCRIPT_PATH")"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
# Source environment variables
|
||||
if [[ -f "../load-env.sh" ]]; then
|
||||
source ../load-env.sh
|
||||
fi
|
||||
|
||||
echo "Setting up ExternalDNS..."
|
||||
|
||||
# Create externaldns namespace
|
||||
kubectl create namespace externaldns --dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
# Setup Cloudflare API token secret for ExternalDNS
|
||||
if [[ -n "${CLOUDFLARE_API_TOKEN}" ]]; then
|
||||
echo "Creating Cloudflare API token secret..."
|
||||
kubectl create secret generic cloudflare-api-token \
|
||||
--namespace externaldns \
|
||||
--from-literal=api-token="${CLOUDFLARE_API_TOKEN}" \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
else
|
||||
echo "Error: CLOUDFLARE_API_TOKEN not set. ExternalDNS will not work correctly."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Apply ExternalDNS manifests with environment variables
|
||||
echo "Deploying ExternalDNS..."
|
||||
cat ${SCRIPT_DIR}/externaldns/externaldns.yaml | envsubst | kubectl apply -f -
|
||||
|
||||
# Wait for ExternalDNS to be ready
|
||||
echo "Waiting for ExternalDNS to be ready..."
|
||||
kubectl rollout status deployment/external-dns -n externaldns --timeout=60s
|
||||
|
||||
# Deploy test services if --test flag is provided
|
||||
if [[ "$1" == "--test" ]]; then
|
||||
echo "Deploying test services to verify ExternalDNS..."
|
||||
cat ${SCRIPT_DIR}/externaldns/test-service.yaml | envsubst | kubectl apply -f -
|
||||
cat ${SCRIPT_DIR}/externaldns/test-cname-service.yaml | envsubst | kubectl apply -f -
|
||||
|
||||
echo "Test services deployed at:"
|
||||
echo "- test.${DOMAIN}"
|
||||
echo "- test-cname.${DOMAIN} (CNAME record)"
|
||||
echo "DNS records should be automatically created in Cloudflare within a few minutes."
|
||||
fi
|
||||
|
||||
echo "ExternalDNS setup complete!"
|
||||
echo ""
|
||||
echo "To verify the installation:"
|
||||
echo " kubectl get pods -n externaldns"
|
||||
echo " kubectl logs -n externaldns -l app=external-dns -f"
|
36
infrastructure_setup/setup-metallb.sh
Executable file
36
infrastructure_setup/setup-metallb.sh
Executable file
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
SCRIPT_PATH="$(realpath "${BASH_SOURCE[0]}")"
|
||||
SCRIPT_DIR="$(dirname "$SCRIPT_PATH")"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
# Source environment variables
|
||||
if [[ -f "../load-env.sh" ]]; then
|
||||
source ../load-env.sh
|
||||
fi
|
||||
|
||||
echo "Setting up MetalLB..."
|
||||
|
||||
# TODO: Remove the helm config in preference to a native config.
|
||||
|
||||
echo "Deploying MetalLB..."
|
||||
cat ${SCRIPT_DIR}/metallb/metallb-helm-config.yaml | envsubst | kubectl apply -f -
|
||||
|
||||
echo "Waiting for MetalLB to be deployed..."
|
||||
kubectl wait --for=condition=complete job -l helm.sh/chart=metallb -n kube-system --timeout=120s || echo "Warning: Timeout waiting for MetalLB Helm job"
|
||||
|
||||
echo "Waiting for MetalLB controller to be ready..."
|
||||
kubectl get namespace metallb-system &>/dev/null || (echo "Waiting for metallb-system namespace to be created..." && sleep 30)
|
||||
kubectl wait --for=condition=Available deployment -l app.kubernetes.io/instance=metallb -n metallb-system --timeout=60s || echo "Warning: Timeout waiting for controller deployment"
|
||||
|
||||
echo "Configuring MetalLB IP address pool..."
|
||||
kubectl get namespace metallb-system &>/dev/null && \
|
||||
kubectl apply -f "${SCRIPT_DIR}/metallb/metallb-pool.yaml" || \
|
||||
echo "Warning: metallb-system namespace not ready yet. Pool configuration will be skipped. Run this script again in a few minutes."
|
||||
|
||||
echo "✅ MetalLB installed and configured"
|
||||
echo ""
|
||||
echo "To verify the installation:"
|
||||
echo " kubectl get pods -n metallb-system"
|
||||
echo " kubectl get ipaddresspools.metallb.io -n metallb-system"
|
18
infrastructure_setup/setup-traefik.sh
Executable file
18
infrastructure_setup/setup-traefik.sh
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
SCRIPT_PATH="$(realpath "${BASH_SOURCE[0]}")"
|
||||
SCRIPT_DIR="$(dirname "$SCRIPT_PATH")"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
# Source environment variables
|
||||
if [[ -f "../load-env.sh" ]]; then
|
||||
source ../load-env.sh
|
||||
fi
|
||||
|
||||
echo "Setting up Traefik service and middleware for k3s..."
|
||||
|
||||
cat ${SCRIPT_DIR}/traefik/traefik-service.yaml | envsubst | kubectl apply -f -
|
||||
cat ${SCRIPT_DIR}/traefik/internal-middleware.yaml | envsubst | kubectl apply -f -
|
||||
|
||||
echo "Traefik setup complete!"
|
15
infrastructure_setup/setup-utils.sh
Executable file
15
infrastructure_setup/setup-utils.sh
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
SCRIPT_PATH="$(realpath "${BASH_SOURCE[0]}")"
|
||||
SCRIPT_DIR="$(dirname "$SCRIPT_PATH")"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
# Install gomplate
|
||||
if command -v gomplate &> /dev/null; then
|
||||
echo "gomplate is already installed."
|
||||
exit 0
|
||||
fi
|
||||
curl -sSL https://github.com/hairyhenderson/gomplate/releases/latest/download/gomplate_linux-amd64 -o $HOME/.local/bin/gomplate
|
||||
chmod +x $HOME/.local/bin/gomplate
|
||||
echo "gomplate installed successfully."
|
13
infrastructure_setup/traefik/internal-middleware.yaml
Normal file
13
infrastructure_setup/traefik/internal-middleware.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: internal-only
|
||||
namespace: kube-system
|
||||
spec:
|
||||
ipWhiteList:
|
||||
# Restrict to local private network ranges - adjust these to match your network
|
||||
sourceRange:
|
||||
- 127.0.0.1/32 # localhost
|
||||
- 10.0.0.0/8 # Private network
|
||||
- 172.16.0.0/12 # Private network
|
||||
- 192.168.0.0/16 # Private network
|
27
infrastructure_setup/traefik/traefik-service.yaml
Normal file
27
infrastructure_setup/traefik/traefik-service.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
# Traefik service configuration with static LoadBalancer IP
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: traefik
|
||||
namespace: kube-system
|
||||
annotations:
|
||||
metallb.universe.tf/address-pool: production
|
||||
metallb.universe.tf/allow-shared-ip: traefik-lb
|
||||
labels:
|
||||
app.kubernetes.io/instance: traefik-kube-system
|
||||
app.kubernetes.io/name: traefik
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
loadBalancerIP: 192.168.8.240
|
||||
selector:
|
||||
app.kubernetes.io/instance: traefik-kube-system
|
||||
app.kubernetes.io/name: traefik
|
||||
ports:
|
||||
- name: web
|
||||
port: 80
|
||||
targetPort: web
|
||||
- name: websecure
|
||||
port: 443
|
||||
targetPort: websecure
|
||||
externalTrafficPolicy: Local
|
1073
infrastructure_setup/validate_setup.sh
Executable file
1073
infrastructure_setup/validate_setup.sh
Executable file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user