Can now create a node worker
This commit is contained in:
@@ -51,7 +51,7 @@ rm -r "$ROOTFS/build"
|
|||||||
echo "##################################################### Packaging RootFS "$( du -sh "$ROOTFS/" )
|
echo "##################################################### Packaging RootFS "$( du -sh "$ROOTFS/" )
|
||||||
|
|
||||||
IMG=output.img
|
IMG=output.img
|
||||||
SIZE=2048MB
|
SIZE=3072MB
|
||||||
|
|
||||||
dd if=/dev/zero of="$IMG" bs=1 count=0 seek=$SIZE
|
dd if=/dev/zero of="$IMG" bs=1 count=0 seek=$SIZE
|
||||||
|
|
||||||
|
|||||||
@@ -11,4 +11,3 @@ rc-update add fancontrol boot
|
|||||||
rc-update add loopback boot
|
rc-update add loopback boot
|
||||||
rc-update add hostname boot
|
rc-update add hostname boot
|
||||||
rc-update add localmount boot
|
rc-update add localmount boot
|
||||||
rc-update add crio default
|
|
||||||
|
|||||||
@@ -31,9 +31,6 @@ if [ $? -ne 0 ]; then
|
|||||||
exit $?
|
exit $?
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mv /etc/cni/net.d/10-crio-bridge.conflist.disabled \
|
|
||||||
/etc/cni/net.d/10-crio-bridge.conflist
|
|
||||||
|
|
||||||
sed -i "s/default_runtime = \"crun\"/\0\ncgroup_manager = \"cgroupfs\"/g" /etc/crio/crio.conf.d/10-crio.conf
|
sed -i "s/default_runtime = \"crun\"/\0\ncgroup_manager = \"cgroupfs\"/g" /etc/crio/crio.conf.d/10-crio.conf
|
||||||
grep cgroup_manager /etc/crio/crio.conf.d/10-crio.conf || exit 1
|
grep cgroup_manager /etc/crio/crio.conf.d/10-crio.conf || exit 1
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ DEVPATH=hwmon0=devices/platform/soc/2180000.i2c/i2c-0/i2c-7/7-002e hwmon1=device
|
|||||||
DEVNAME=hwmon0=emc2305 hwmon1=ddr_thermal
|
DEVNAME=hwmon0=emc2305 hwmon1=ddr_thermal
|
||||||
FCTEMPS=hwmon0/pwm2=hwmon1/temp1_input
|
FCTEMPS=hwmon0/pwm2=hwmon1/temp1_input
|
||||||
FCFANS= hwmon0/pwm2=hwmon0/fan1_input
|
FCFANS= hwmon0/pwm2=hwmon0/fan1_input
|
||||||
MINTEMP=hwmon0/pwm2=40
|
MINTEMP=hwmon0/pwm2=35
|
||||||
MAXTEMP=hwmon0/pwm2=60
|
MAXTEMP=hwmon0/pwm2=60
|
||||||
MINSTART=hwmon0/pwm2=60
|
MINSTART=hwmon0/pwm2=60
|
||||||
MINSTOP=hwmon0/pwm2=45
|
MINSTOP=hwmon0/pwm2=45
|
||||||
|
|||||||
149
alpine/rootfs-extra/opt/scripts/apply-node-config.sh
Normal file
149
alpine/rootfs-extra/opt/scripts/apply-node-config.sh
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
CONFIG_DIR="${CONFIG_DIR:-/opt/monok8s/config}"
|
||||||
|
NODE_ENV="${NODE_ENV:-$CONFIG_DIR/node.env}"
|
||||||
|
|
||||||
|
log() {
|
||||||
|
echo "[monok8s-node] $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "[monok8s-node] ERROR: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
need_cmd() {
|
||||||
|
command -v "$1" >/dev/null 2>&1 || fail "missing required command: $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
require_file() {
|
||||||
|
[ -f "$1" ] || fail "required file not found: $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
load_config() {
|
||||||
|
require_file "$NODE_ENV"
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
. "$NODE_ENV"
|
||||||
|
|
||||||
|
HOSTNAME="${HOSTNAME:-}"
|
||||||
|
MGMT_IFACE="${MGMT_IFACE:-}"
|
||||||
|
MGMT_ADDRESS="${MGMT_ADDRESS:-}"
|
||||||
|
MGMT_GATEWAY="${MGMT_GATEWAY:-}"
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_config() {
|
||||||
|
[ -n "$HOSTNAME" ] || fail "HOSTNAME is required"
|
||||||
|
[ -n "$MGMT_IFACE" ] || fail "MGMT_IFACE is required"
|
||||||
|
[ -n "$MGMT_ADDRESS" ] || fail "MGMT_ADDRESS is required"
|
||||||
|
|
||||||
|
case "$MGMT_ADDRESS" in
|
||||||
|
*/*)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
fail "MGMT_ADDRESS must include a CIDR prefix, example: 10.0.0.13/24"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
check_prereqs() {
|
||||||
|
need_cmd ip
|
||||||
|
need_cmd hostname
|
||||||
|
need_cmd grep
|
||||||
|
need_cmd awk
|
||||||
|
need_cmd cut
|
||||||
|
need_cmd mkdir
|
||||||
|
need_cmd printf
|
||||||
|
need_cmd cat
|
||||||
|
}
|
||||||
|
|
||||||
|
configure_mgmt_interface() {
|
||||||
|
local addr_ip
|
||||||
|
|
||||||
|
ip link show "$MGMT_IFACE" >/dev/null 2>&1 || fail "interface not found: $MGMT_IFACE"
|
||||||
|
|
||||||
|
log "bringing up interface: $MGMT_IFACE"
|
||||||
|
ip link set "$MGMT_IFACE" up
|
||||||
|
|
||||||
|
addr_ip="${MGMT_ADDRESS%/*}"
|
||||||
|
|
||||||
|
if ip -o addr show dev "$MGMT_IFACE" | awk '{print $4}' | cut -d/ -f1 | grep -Fx "$addr_ip" >/dev/null 2>&1; then
|
||||||
|
log "address already present on $MGMT_IFACE: $MGMT_ADDRESS"
|
||||||
|
else
|
||||||
|
log "assigning $MGMT_ADDRESS to $MGMT_IFACE"
|
||||||
|
ip addr add "$MGMT_ADDRESS" dev "$MGMT_IFACE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "${MGMT_GATEWAY:-}" ]; then
|
||||||
|
log "ensuring default route via $MGMT_GATEWAY"
|
||||||
|
ip route replace default via "$MGMT_GATEWAY" dev "$MGMT_IFACE"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
set_hostname_if_needed() {
|
||||||
|
local current_hostname
|
||||||
|
|
||||||
|
current_hostname="$(hostname 2>/dev/null || true)"
|
||||||
|
|
||||||
|
if [ "$current_hostname" != "$HOSTNAME" ]; then
|
||||||
|
log "setting hostname to $HOSTNAME"
|
||||||
|
|
||||||
|
hostname "$HOSTNAME"
|
||||||
|
|
||||||
|
mkdir -p /etc
|
||||||
|
printf '%s\n' "$HOSTNAME" > /etc/hostname
|
||||||
|
|
||||||
|
if [ -f /etc/hosts ]; then
|
||||||
|
if ! grep -Eq "[[:space:]]$HOSTNAME([[:space:]]|$)" /etc/hosts; then
|
||||||
|
printf '127.0.0.1\tlocalhost %s\n' "$HOSTNAME" >> /etc/hosts
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
cat > /etc/hosts <<EOF
|
||||||
|
127.0.0.1 localhost $HOSTNAME
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log "hostname already set: $HOSTNAME"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_ip_forward() {
|
||||||
|
local current
|
||||||
|
|
||||||
|
current="$(cat /proc/sys/net/ipv4/ip_forward 2>/dev/null || echo 0)"
|
||||||
|
if [ "$current" != "1" ]; then
|
||||||
|
log "enabling IPv4 forwarding"
|
||||||
|
echo 1 > /proc/sys/net/ipv4/ip_forward
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p /etc/sysctl.d
|
||||||
|
cat > /etc/sysctl.d/99-monok8s.conf <<'EOF'
|
||||||
|
net.ipv4.ip_forward = 1
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
print_summary() {
|
||||||
|
log "node configuration applied"
|
||||||
|
log "hostname: $HOSTNAME"
|
||||||
|
log "interface: $MGMT_IFACE"
|
||||||
|
log "address: $MGMT_ADDRESS"
|
||||||
|
if [ -n "${MGMT_GATEWAY:-}" ]; then
|
||||||
|
log "gateway: $MGMT_GATEWAY"
|
||||||
|
else
|
||||||
|
log "gateway: <not set>"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
load_config
|
||||||
|
validate_config
|
||||||
|
check_prereqs
|
||||||
|
|
||||||
|
ensure_ip_forward
|
||||||
|
configure_mgmt_interface
|
||||||
|
set_hostname_if_needed
|
||||||
|
print_summary
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
436
alpine/rootfs-extra/opt/scripts/bootstrap-cluster.sh
Executable file
436
alpine/rootfs-extra/opt/scripts/bootstrap-cluster.sh
Executable file
@@ -0,0 +1,436 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
CONFIG_DIR="${CONFIG_DIR:-/opt/monok8s/config}"
|
||||||
|
CLUSTER_ENV="${CONFIG_DIR}/cluster.env"
|
||||||
|
KUBEADM_CONFIG_OUT="${KUBEADM_CONFIG_OUT:-/tmp/kubeadm-init.yaml}"
|
||||||
|
|
||||||
|
log() {
|
||||||
|
echo "[monok8s] $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "[monok8s] ERROR: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
need_cmd() {
|
||||||
|
command -v "$1" >/dev/null 2>&1 || fail "missing required command: $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
require_file() {
|
||||||
|
[ -f "$1" ] || fail "required file not found: $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
load_config() {
|
||||||
|
require_file "$CLUSTER_ENV"
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
. "$CLUSTER_ENV"
|
||||||
|
|
||||||
|
: "${KUBERNETES_VERSION:?KUBERNETES_VERSION is required}"
|
||||||
|
: "${NODE_NAME:?NODE_NAME is required}"
|
||||||
|
: "${APISERVER_ADVERTISE_ADDRESS:?APISERVER_ADVERTISE_ADDRESS is required}"
|
||||||
|
|
||||||
|
POD_SUBNET="${POD_SUBNET:-10.244.0.0/16}"
|
||||||
|
SERVICE_SUBNET="${SERVICE_SUBNET:-10.96.0.0/12}"
|
||||||
|
CLUSTER_NAME="${CLUSTER_NAME:-monok8s}"
|
||||||
|
CLUSTER_DOMAIN="${CLUSTER_DOMAIN:-cluster.local}"
|
||||||
|
CONTAINER_RUNTIME_ENDPOINT="${CONTAINER_RUNTIME_ENDPOINT:-unix:///var/run/crio/crio.sock}"
|
||||||
|
SANS="${SANS:-}"
|
||||||
|
ALLOW_SCHEDULING_ON_CONTROL_PLANE="${ALLOW_SCHEDULING_ON_CONTROL_PLANE:-yes}"
|
||||||
|
SKIP_IMAGE_CHECK="${SKIP_IMAGE_CHECK:-no}"
|
||||||
|
KUBECONFIG_USER_HOME="${KUBECONFIG_USER_HOME:-/root}"
|
||||||
|
|
||||||
|
BOOTSTRAP_MODE="${BOOTSTRAP_MODE:-init}"
|
||||||
|
JOIN_KIND="${JOIN_KIND:-worker}"
|
||||||
|
API_SERVER_ENDPOINT="${API_SERVER_ENDPOINT:-}"
|
||||||
|
BOOTSTRAP_TOKEN="${BOOTSTRAP_TOKEN:-}"
|
||||||
|
DISCOVERY_TOKEN_CA_CERT_HASH="${DISCOVERY_TOKEN_CA_CERT_HASH:-}"
|
||||||
|
CONTROL_PLANE_CERT_KEY="${CONTROL_PLANE_CERT_KEY:-}"
|
||||||
|
CNI_PLUGIN="${CNI_PLUGIN:-none}"
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_config() {
|
||||||
|
case "$BOOTSTRAP_MODE" in
|
||||||
|
init)
|
||||||
|
;;
|
||||||
|
join)
|
||||||
|
: "${API_SERVER_ENDPOINT:?API_SERVER_ENDPOINT is required for join mode}"
|
||||||
|
: "${BOOTSTRAP_TOKEN:?BOOTSTRAP_TOKEN is required for join mode}"
|
||||||
|
: "${DISCOVERY_TOKEN_CA_CERT_HASH:?DISCOVERY_TOKEN_CA_CERT_HASH is required for join mode}"
|
||||||
|
|
||||||
|
case "$JOIN_KIND" in
|
||||||
|
worker|control-plane)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
fail "JOIN_KIND must be 'worker' or 'control-plane'"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ "$JOIN_KIND" = "control-plane" ]; then
|
||||||
|
: "${CONTROL_PLANE_CERT_KEY:?CONTROL_PLANE_CERT_KEY is required for JOIN_KIND=control-plane}"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
fail "BOOTSTRAP_MODE must be 'init' or 'join'"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
check_prereqs() {
|
||||||
|
need_cmd kubeadm
|
||||||
|
need_cmd kubelet
|
||||||
|
need_cmd kubectl
|
||||||
|
need_cmd crictl
|
||||||
|
need_cmd rc-service
|
||||||
|
need_cmd awk
|
||||||
|
need_cmd ip
|
||||||
|
need_cmd grep
|
||||||
|
need_cmd sed
|
||||||
|
need_cmd hostname
|
||||||
|
}
|
||||||
|
|
||||||
|
check_apiserver_reachable() {
|
||||||
|
host="${API_SERVER_ENDPOINT%:*}"
|
||||||
|
port="${API_SERVER_ENDPOINT##*:}"
|
||||||
|
|
||||||
|
need_cmd nc
|
||||||
|
|
||||||
|
log "checking API server reachability: ${host}:${port}"
|
||||||
|
|
||||||
|
for _ in $(seq 1 20); do
|
||||||
|
if nc -z "$host" "$port" >/dev/null 2>&1; then
|
||||||
|
log "API server is reachable"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
fail "cannot reach API server at ${host}:${port}"
|
||||||
|
}
|
||||||
|
|
||||||
|
start_crio() {
|
||||||
|
rc-service crio start
|
||||||
|
}
|
||||||
|
|
||||||
|
check_crio_running() {
|
||||||
|
log "waiting for CRI-O to become ready..."
|
||||||
|
|
||||||
|
last_status="unknown"
|
||||||
|
|
||||||
|
for _ in $(seq 1 30); do
|
||||||
|
if rc-service crio status >/dev/null 2>&1; then
|
||||||
|
last_status="service-running"
|
||||||
|
if crictl --runtime-endpoint "$CONTAINER_RUNTIME_ENDPOINT" info >/dev/null 2>&1; then
|
||||||
|
log "CRI-O is up"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
last_status="service-running-but-runtime-not-ready"
|
||||||
|
else
|
||||||
|
last_status="service-not-running"
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
fail "CRI-O did not become ready in time (${last_status})"
|
||||||
|
}
|
||||||
|
|
||||||
|
image_present() {
|
||||||
|
wanted="$1"
|
||||||
|
|
||||||
|
repo="${wanted%:*}"
|
||||||
|
tag="${wanted##*:}"
|
||||||
|
|
||||||
|
crictl --runtime-endpoint "$CONTAINER_RUNTIME_ENDPOINT" images \
|
||||||
|
| awk 'NR>1 { print $1 ":" $2 }' \
|
||||||
|
| grep -Fx "$repo:$tag" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
check_required_images() {
|
||||||
|
[ "$SKIP_IMAGE_CHECK" = "yes" ] && {
|
||||||
|
log "skipping image check (SKIP_IMAGE_CHECK=yes)"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
log "checking required Kubernetes images for $KUBERNETES_VERSION..."
|
||||||
|
|
||||||
|
missing_any=0
|
||||||
|
|
||||||
|
for img in $(kubeadm config images list --kubernetes-version "$KUBERNETES_VERSION"); do
|
||||||
|
if image_present "$img"; then
|
||||||
|
log "found image: $img"
|
||||||
|
else
|
||||||
|
echo "[monok8s] MISSING image: $img" >&2
|
||||||
|
missing_any=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
[ "$missing_any" -eq 0 ] || fail "preload the Kubernetes images before bootstrapping"
|
||||||
|
|
||||||
|
log "all required images are present"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_not_already_bootstrapped() {
|
||||||
|
case "$BOOTSTRAP_MODE" in
|
||||||
|
init)
|
||||||
|
if [ -f /etc/kubernetes/admin.conf ]; then
|
||||||
|
fail "cluster already appears initialized (/etc/kubernetes/admin.conf exists)"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
join)
|
||||||
|
if [ -f /etc/kubernetes/kubelet.conf ]; then
|
||||||
|
fail "node already appears joined (/etc/kubernetes/kubelet.conf exists)"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
run_kubeadm_join() {
|
||||||
|
log "running kubeadm join..."
|
||||||
|
|
||||||
|
case "$JOIN_KIND" in
|
||||||
|
worker)
|
||||||
|
kubeadm join "${API_SERVER_ENDPOINT}" \
|
||||||
|
--token "${BOOTSTRAP_TOKEN}" \
|
||||||
|
--discovery-token-ca-cert-hash "${DISCOVERY_TOKEN_CA_CERT_HASH}" \
|
||||||
|
--node-name "${NODE_NAME}" \
|
||||||
|
--cri-socket "${CONTAINER_RUNTIME_ENDPOINT}"
|
||||||
|
;;
|
||||||
|
control-plane)
|
||||||
|
kubeadm join "${API_SERVER_ENDPOINT}" \
|
||||||
|
--token "${BOOTSTRAP_TOKEN}" \
|
||||||
|
--discovery-token-ca-cert-hash "${DISCOVERY_TOKEN_CA_CERT_HASH}" \
|
||||||
|
--control-plane \
|
||||||
|
--certificate-key "${CONTROL_PLANE_CERT_KEY}" \
|
||||||
|
--apiserver-advertise-address "${APISERVER_ADVERTISE_ADDRESS}" \
|
||||||
|
--node-name "${NODE_NAME}" \
|
||||||
|
--cri-socket "${CONTAINER_RUNTIME_ENDPOINT}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_kubeadm_config() {
|
||||||
|
log "generating kubeadm config at $KUBEADM_CONFIG_OUT..."
|
||||||
|
|
||||||
|
SAN_LINES=""
|
||||||
|
if [ -n "${SANS:-}" ]; then
|
||||||
|
old_ifs="$IFS"
|
||||||
|
IFS=','
|
||||||
|
for san in $SANS; do
|
||||||
|
san_trimmed="$(echo "$san" | sed 's/^ *//;s/ *$//')"
|
||||||
|
[ -n "$san_trimmed" ] && SAN_LINES="${SAN_LINES} - \"${san_trimmed}\"
|
||||||
|
"
|
||||||
|
done
|
||||||
|
IFS="$old_ifs"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat > "$KUBEADM_CONFIG_OUT" <<EOF
|
||||||
|
apiVersion: kubeadm.k8s.io/v1beta4
|
||||||
|
kind: InitConfiguration
|
||||||
|
localAPIEndpoint:
|
||||||
|
advertiseAddress: ${APISERVER_ADVERTISE_ADDRESS}
|
||||||
|
bindPort: 6443
|
||||||
|
nodeRegistration:
|
||||||
|
name: ${NODE_NAME}
|
||||||
|
criSocket: ${CONTAINER_RUNTIME_ENDPOINT}
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
kubeletExtraArgs:
|
||||||
|
- name: hostname-override
|
||||||
|
value: "${NODE_NAME}"
|
||||||
|
- name: node-ip
|
||||||
|
value: "${APISERVER_ADVERTISE_ADDRESS}"
|
||||||
|
- name: pod-manifest-path
|
||||||
|
value: "/etc/kubernetes/manifests"
|
||||||
|
---
|
||||||
|
apiVersion: kubeadm.k8s.io/v1beta4
|
||||||
|
kind: ClusterConfiguration
|
||||||
|
clusterName: ${CLUSTER_NAME}
|
||||||
|
kubernetesVersion: ${KUBERNETES_VERSION}
|
||||||
|
networking:
|
||||||
|
podSubnet: ${POD_SUBNET}
|
||||||
|
serviceSubnet: ${SERVICE_SUBNET}
|
||||||
|
dnsDomain: ${CLUSTER_DOMAIN}
|
||||||
|
apiServer:
|
||||||
|
certSANs:
|
||||||
|
- "${APISERVER_ADVERTISE_ADDRESS}"
|
||||||
|
${SAN_LINES}---
|
||||||
|
apiVersion: kubelet.config.k8s.io/v1beta1
|
||||||
|
kind: KubeletConfiguration
|
||||||
|
cgroupDriver: cgroupfs
|
||||||
|
containerRuntimeEndpoint: ${CONTAINER_RUNTIME_ENDPOINT}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
run_kubeadm_init() {
|
||||||
|
log "running kubeadm init..."
|
||||||
|
kubeadm init --config "$KUBEADM_CONFIG_OUT"
|
||||||
|
}
|
||||||
|
|
||||||
|
require_local_ip() {
|
||||||
|
wanted_ip="$1"
|
||||||
|
|
||||||
|
ip -o addr show | awk '{print $4}' | cut -d/ -f1 | grep -Fx "$wanted_ip" >/dev/null 2>&1 \
|
||||||
|
|| fail "required local IP is not present on any interface: $wanted_ip"
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_network_requirements() {
|
||||||
|
case "$BOOTSTRAP_MODE" in
|
||||||
|
init)
|
||||||
|
require_local_ip "$APISERVER_ADVERTISE_ADDRESS"
|
||||||
|
;;
|
||||||
|
join)
|
||||||
|
require_local_ip "$APISERVER_ADVERTISE_ADDRESS"
|
||||||
|
check_apiserver_reachable
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
fail "unsupported BOOTSTRAP_MODE: $BOOTSTRAP_MODE"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_local_kubectl() {
|
||||||
|
kube_dir="${KUBECONFIG_USER_HOME}/.kube"
|
||||||
|
log "setting up local kubectl config in ${kube_dir}/config..."
|
||||||
|
|
||||||
|
mkdir -p "$kube_dir"
|
||||||
|
cp /etc/kubernetes/admin.conf "${kube_dir}/config"
|
||||||
|
chmod 600 "${kube_dir}/config"
|
||||||
|
|
||||||
|
if [ "$KUBECONFIG_USER_HOME" = "/root" ]; then
|
||||||
|
mkdir -p /etc/profile.d
|
||||||
|
cat > /etc/profile.d/kubeconfig.sh <<'EOF'
|
||||||
|
export KUBECONFIG=/root/.kube/config
|
||||||
|
EOF
|
||||||
|
chmod 644 /etc/profile.d/kubeconfig.sh
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_node() {
|
||||||
|
log "waiting for node registration: $NODE_NAME"
|
||||||
|
|
||||||
|
for _ in $(seq 1 60); do
|
||||||
|
if kubectl --kubeconfig /etc/kubernetes/admin.conf get node "$NODE_NAME" >/dev/null 2>&1; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
fail "node $NODE_NAME did not register in time"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_local_node_metadata_if_possible() {
|
||||||
|
if [ "$BOOTSTRAP_MODE" != "init" ]; then
|
||||||
|
log "skipping node labels/annotations from this node (not control-plane init mode)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
wait_for_node
|
||||||
|
|
||||||
|
if [ -n "${NODE_ANNOTATIONS:-}" ]; then
|
||||||
|
kubectl --kubeconfig /etc/kubernetes/admin.conf annotate node "$NODE_NAME" $(printf '%s' "$NODE_ANNOTATIONS" | tr ',' ' ') --overwrite
|
||||||
|
fi
|
||||||
|
if [ -n "${NODE_LABELS:-}" ]; then
|
||||||
|
kubectl --kubeconfig /etc/kubernetes/admin.conf label node "$NODE_NAME" $(printf '%s' "$NODE_LABELS" | tr ',' ' ') --overwrite
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
install_cni_if_requested() {
|
||||||
|
case "${CNI_PLUGIN}" in
|
||||||
|
none)
|
||||||
|
if [ -f /etc/cni/net.d/10-crio-bridge.conflist ]; then
|
||||||
|
mv /etc/cni/net.d/10-crio-bridge.conflist \
|
||||||
|
/etc/cni/net.d/10-crio-bridge.conflist.disabled
|
||||||
|
fi
|
||||||
|
log "bootstrap bridge CNI disabled; install a cluster CNI (e.g., flannel) for pod networking"
|
||||||
|
;;
|
||||||
|
bridge)
|
||||||
|
if [ -f /etc/cni/net.d/10-crio-bridge.conflist.disabled ]; then
|
||||||
|
mv /etc/cni/net.d/10-crio-bridge.conflist.disabled \
|
||||||
|
/etc/cni/net.d/10-crio-bridge.conflist
|
||||||
|
fi
|
||||||
|
log "bootstrap bridge CNI enabled"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
fail "unsupported CNI_PLUGIN: ${CNI_PLUGIN}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
allow_single_node_scheduling() {
|
||||||
|
if [ "$ALLOW_SCHEDULING_ON_CONTROL_PLANE" != "yes" ]; then
|
||||||
|
log "leaving control-plane taint in place"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "removing control-plane taint so this single node can schedule workloads..."
|
||||||
|
kubectl --kubeconfig /etc/kubernetes/admin.conf taint nodes "$NODE_NAME" node-role.kubernetes.io/control-plane- >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
|
print_next_steps() {
|
||||||
|
echo
|
||||||
|
echo "[monok8s] bootstrap complete"
|
||||||
|
echo
|
||||||
|
|
||||||
|
case "$BOOTSTRAP_MODE" in
|
||||||
|
init)
|
||||||
|
cat <<EOF
|
||||||
|
Try these now:
|
||||||
|
|
||||||
|
export KUBECONFIG=/root/.kube/config
|
||||||
|
kubectl get nodes -o wide
|
||||||
|
kubectl describe nodes
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- On a fresh cluster without a CNI, nodes may stay NotReady.
|
||||||
|
- If you want pods to run on this same node, keep ALLOW_SCHEDULING_ON_CONTROL_PLANE=yes.
|
||||||
|
EOF
|
||||||
|
;;
|
||||||
|
join)
|
||||||
|
cat <<EOF
|
||||||
|
This node has attempted to join the cluster.
|
||||||
|
|
||||||
|
Check from the control-plane node:
|
||||||
|
|
||||||
|
kubectl get nodes -o wide
|
||||||
|
kubectl describe node ${NODE_NAME}
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- This node can join without a CNI.
|
||||||
|
- Without a cluster CNI, the node may remain NotReady.
|
||||||
|
EOF
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
load_config
|
||||||
|
validate_config
|
||||||
|
|
||||||
|
check_prereqs
|
||||||
|
validate_network_requirements
|
||||||
|
check_not_already_bootstrapped
|
||||||
|
|
||||||
|
install_cni_if_requested
|
||||||
|
start_crio
|
||||||
|
check_crio_running
|
||||||
|
|
||||||
|
case "$BOOTSTRAP_MODE" in
|
||||||
|
init)
|
||||||
|
check_required_images
|
||||||
|
generate_kubeadm_config
|
||||||
|
run_kubeadm_init
|
||||||
|
rc-service kubelet restart
|
||||||
|
setup_local_kubectl
|
||||||
|
apply_local_node_metadata_if_possible
|
||||||
|
allow_single_node_scheduling
|
||||||
|
;;
|
||||||
|
join)
|
||||||
|
run_kubeadm_join
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
print_next_steps
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
@@ -1,377 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
CONFIG_DIR="${CONFIG_DIR:-/opt/monok8s/config}"
|
|
||||||
CLUSTER_ENV="${CONFIG_DIR}/cluster.env"
|
|
||||||
KUBEADM_CONFIG_OUT="${KUBEADM_CONFIG_OUT:-/tmp/kubeadm-init.yaml}"
|
|
||||||
|
|
||||||
log() {
|
|
||||||
echo "[monok8s] $*"
|
|
||||||
}
|
|
||||||
|
|
||||||
fail() {
|
|
||||||
echo "[monok8s] ERROR: $*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
need_cmd() {
|
|
||||||
command -v "$1" >/dev/null 2>&1 || fail "missing required command: $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
require_file() {
|
|
||||||
[ -f "$1" ] || fail "required file not found: $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
load_config() {
|
|
||||||
require_file "$CLUSTER_ENV"
|
|
||||||
|
|
||||||
# shellcheck disable=SC1090
|
|
||||||
. "$CLUSTER_ENV"
|
|
||||||
|
|
||||||
: "${KUBERNETES_VERSION:?KUBERNETES_VERSION is required}"
|
|
||||||
: "${NODE_NAME:?NODE_NAME is required}"
|
|
||||||
: "${APISERVER_ADVERTISE_ADDRESS:?APISERVER_ADVERTISE_ADDRESS is required}"
|
|
||||||
|
|
||||||
POD_SUBNET="${POD_SUBNET:-10.244.0.0/16}"
|
|
||||||
SERVICE_SUBNET="${SERVICE_SUBNET:-10.96.0.0/12}"
|
|
||||||
CLUSTER_NAME="${CLUSTER_NAME:-monok8s}"
|
|
||||||
CLUSTER_DOMAIN="${CLUSTER_DOMAIN:-cluster.local}"
|
|
||||||
CONTAINER_RUNTIME_ENDPOINT="${CONTAINER_RUNTIME_ENDPOINT:-unix:///var/run/crio/crio.sock}"
|
|
||||||
SANS="${SANS:-}"
|
|
||||||
ALLOW_SCHEDULING_ON_CONTROL_PLANE="${ALLOW_SCHEDULING_ON_CONTROL_PLANE:-yes}"
|
|
||||||
SKIP_IMAGE_CHECK="${SKIP_IMAGE_CHECK:-no}"
|
|
||||||
KUBECONFIG_USER_HOME="${KUBECONFIG_USER_HOME:-/root}"
|
|
||||||
}
|
|
||||||
|
|
||||||
check_prereqs() {
|
|
||||||
need_cmd kubeadm
|
|
||||||
need_cmd kubelet
|
|
||||||
need_cmd kubectl
|
|
||||||
need_cmd crictl
|
|
||||||
need_cmd rc-service
|
|
||||||
need_cmd awk
|
|
||||||
need_cmd grep
|
|
||||||
need_cmd sed
|
|
||||||
need_cmd hostname
|
|
||||||
|
|
||||||
[ -S /var/run/crio/crio.sock ] || fail "CRI-O socket not found at /var/run/crio/crio.sock"
|
|
||||||
}
|
|
||||||
|
|
||||||
set_hostname_if_needed() {
|
|
||||||
current_hostname="$(hostname 2>/dev/null || true)"
|
|
||||||
|
|
||||||
if [ "$current_hostname" != "$NODE_NAME" ]; then
|
|
||||||
log "setting hostname to $NODE_NAME"
|
|
||||||
|
|
||||||
hostname "$NODE_NAME"
|
|
||||||
|
|
||||||
mkdir -p /etc
|
|
||||||
printf '%s\n' "$NODE_NAME" > /etc/hostname
|
|
||||||
|
|
||||||
# Keep localhost mapping sane even without DNS.
|
|
||||||
if [ -f /etc/hosts ]; then
|
|
||||||
if ! grep -Eq "[[:space:]]$NODE_NAME([[:space:]]|\$)" /etc/hosts; then
|
|
||||||
printf '127.0.0.1\tlocalhost %s\n' "$NODE_NAME" >> /etc/hosts
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
cat > /etc/hosts <<EOF
|
|
||||||
127.0.0.1 localhost $NODE_NAME
|
|
||||||
EOF
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
ensure_ip_forward() {
|
|
||||||
current="$(cat /proc/sys/net/ipv4/ip_forward 2>/dev/null || echo 0)"
|
|
||||||
if [ "$current" != "1" ]; then
|
|
||||||
log "enabling IPv4 forwarding"
|
|
||||||
echo 1 > /proc/sys/net/ipv4/ip_forward
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p /etc/sysctl.d
|
|
||||||
cat > /etc/sysctl.d/99-monok8s.conf <<'EOF'
|
|
||||||
net.ipv4.ip_forward = 1
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
ensure_kubelet_binary() {
|
|
||||||
if command -v kubelet >/dev/null 2>&1; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -x /usr/local/bin/kubelet ]; then
|
|
||||||
export PATH="/usr/local/bin:$PATH"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -x /opt/cni/bin/kubelet ]; then
|
|
||||||
export PATH="/opt/cni/bin:$PATH"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
fail "kubelet binary not found; install kubelet into /usr/local/bin or make sure PATH includes it"
|
|
||||||
}
|
|
||||||
|
|
||||||
ensure_kubelet_service() {
|
|
||||||
if ! rc-service kubelet status >/dev/null 2>&1; then
|
|
||||||
if [ ! -f /etc/init.d/kubelet ]; then
|
|
||||||
fail "kubelet OpenRC service does not exist at /etc/init.d/kubelet"
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "starting kubelet service"
|
|
||||||
rc-service kubelet start || fail "failed to start kubelet"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
check_crio_running() {
|
|
||||||
log "checking CRI-O status..."
|
|
||||||
|
|
||||||
if ! rc-service crio status >/dev/null 2>&1; then
|
|
||||||
fail "crio service is not running"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! crictl --runtime-endpoint "$CONTAINER_RUNTIME_ENDPOINT" info >/dev/null 2>&1; then
|
|
||||||
fail "crictl cannot talk to CRI-O via $CONTAINER_RUNTIME_ENDPOINT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "CRI-O is up"
|
|
||||||
}
|
|
||||||
|
|
||||||
normalize_image_ref() {
|
|
||||||
# Add docker.io/library/ for bare images like pause:3.10 if needed.
|
|
||||||
# kubeadm usually emits full refs already, so this is just defensive.
|
|
||||||
ref="$1"
|
|
||||||
case "$ref" in
|
|
||||||
*/* ) echo "$ref" ;;
|
|
||||||
* ) echo "docker.io/library/$ref" ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
image_present() {
|
|
||||||
wanted="$1"
|
|
||||||
|
|
||||||
repo="${wanted%:*}"
|
|
||||||
tag="${wanted##*:}"
|
|
||||||
|
|
||||||
crictl --runtime-endpoint "$CONTAINER_RUNTIME_ENDPOINT" images \
|
|
||||||
| awk 'NR>1 { print $1 ":" $2 }' \
|
|
||||||
| grep -Fx "$repo:$tag" >/dev/null 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
check_required_images() {
|
|
||||||
[ "$SKIP_IMAGE_CHECK" = "yes" ] && {
|
|
||||||
log "skipping image check (SKIP_IMAGE_CHECK=yes)"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
log "checking required Kubernetes images for $KUBERNETES_VERSION..."
|
|
||||||
|
|
||||||
missing_any=0
|
|
||||||
|
|
||||||
for img in $(kubeadm config images list --kubernetes-version "$KUBERNETES_VERSION"); do
|
|
||||||
if image_present "$img"; then
|
|
||||||
log "found image: $img"
|
|
||||||
else
|
|
||||||
echo "[monok8s] MISSING image: $img" >&2
|
|
||||||
missing_any=1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
[ "$missing_any" -eq 0 ] || fail "preload the Kubernetes images before bootstrapping"
|
|
||||||
|
|
||||||
log "all required images are present"
|
|
||||||
}
|
|
||||||
|
|
||||||
check_node_name() {
|
|
||||||
current_hostname="$(hostname)"
|
|
||||||
if [ "$current_hostname" != "$NODE_NAME" ]; then
|
|
||||||
log "warning: hostname is '$current_hostname' but NODE_NAME is '$NODE_NAME'"
|
|
||||||
log "kubeadm will use NODE_NAME from config"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
check_already_initialized() {
|
|
||||||
if [ -f /etc/kubernetes/admin.conf ]; then
|
|
||||||
fail "cluster already appears initialized (/etc/kubernetes/admin.conf exists)"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
generate_kubeadm_config() {
|
|
||||||
log "generating kubeadm config at $KUBEADM_CONFIG_OUT..."
|
|
||||||
|
|
||||||
SAN_LINES=""
|
|
||||||
if [ -n "${SANS:-}" ]; then
|
|
||||||
old_ifs="$IFS"
|
|
||||||
IFS=','
|
|
||||||
for san in $SANS; do
|
|
||||||
san_trimmed="$(echo "$san" | sed 's/^ *//;s/ *$//')"
|
|
||||||
[ -n "$san_trimmed" ] && SAN_LINES="${SAN_LINES} - \"${san_trimmed}\"
|
|
||||||
"
|
|
||||||
done
|
|
||||||
IFS="$old_ifs"
|
|
||||||
fi
|
|
||||||
|
|
||||||
cat > "$KUBEADM_CONFIG_OUT" <<EOF
|
|
||||||
apiVersion: kubeadm.k8s.io/v1beta4
|
|
||||||
kind: InitConfiguration
|
|
||||||
localAPIEndpoint:
|
|
||||||
advertiseAddress: ${APISERVER_ADVERTISE_ADDRESS}
|
|
||||||
bindPort: 6443
|
|
||||||
nodeRegistration:
|
|
||||||
name: ${NODE_NAME}
|
|
||||||
criSocket: ${CONTAINER_RUNTIME_ENDPOINT}
|
|
||||||
imagePullPolicy: IfNotPresent
|
|
||||||
kubeletExtraArgs:
|
|
||||||
- name: hostname-override
|
|
||||||
value: "${NODE_NAME}"
|
|
||||||
- name: node-ip
|
|
||||||
value: "${APISERVER_ADVERTISE_ADDRESS}"
|
|
||||||
- name: pod-manifest-path
|
|
||||||
value: "/etc/kubernetes/manifests"
|
|
||||||
---
|
|
||||||
apiVersion: kubeadm.k8s.io/v1beta4
|
|
||||||
kind: ClusterConfiguration
|
|
||||||
clusterName: ${CLUSTER_NAME}
|
|
||||||
kubernetesVersion: ${KUBERNETES_VERSION}
|
|
||||||
networking:
|
|
||||||
podSubnet: ${POD_SUBNET}
|
|
||||||
serviceSubnet: ${SERVICE_SUBNET}
|
|
||||||
dnsDomain: ${CLUSTER_DOMAIN}
|
|
||||||
apiServer:
|
|
||||||
certSANs:
|
|
||||||
- "${APISERVER_ADVERTISE_ADDRESS}"
|
|
||||||
${SAN_LINES}---
|
|
||||||
apiVersion: kubelet.config.k8s.io/v1beta1
|
|
||||||
kind: KubeletConfiguration
|
|
||||||
cgroupDriver: cgroupfs
|
|
||||||
containerRuntimeEndpoint: ${CONTAINER_RUNTIME_ENDPOINT}
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
run_kubeadm_init() {
|
|
||||||
log "running kubeadm init..."
|
|
||||||
kubeadm init --config "$KUBEADM_CONFIG_OUT"
|
|
||||||
}
|
|
||||||
|
|
||||||
warn_dummy_interface() {
|
|
||||||
YELLOW="$(printf '\033[1;33m')"
|
|
||||||
RESET="$(printf '\033[0m')"
|
|
||||||
|
|
||||||
echo
|
|
||||||
printf '%s' "$YELLOW"
|
|
||||||
echo "[monok8s] WARNING: No suitable network interface found for API server address."
|
|
||||||
echo "[monok8s] Creating a temporary dummy interface for bootstrap."
|
|
||||||
echo "[monok8s] This configuration is intended for local/testing use only."
|
|
||||||
echo "[monok8s] Please configure a real network interface for production."
|
|
||||||
printf '%s' "$RESET"
|
|
||||||
echo
|
|
||||||
}
|
|
||||||
|
|
||||||
ensure_apiserver_address_present() {
|
|
||||||
: "${APISERVER_ADVERTISE_ADDRESS:?APISERVER_ADVERTISE_ADDRESS is required}"
|
|
||||||
|
|
||||||
DUMMY_IFACE="${DUMMY_IFACE:-dummy0}"
|
|
||||||
DUMMY_PREFIX_LEN="${DUMMY_PREFIX_LEN:-32}"
|
|
||||||
|
|
||||||
# If the address already exists on an interface, we're done.
|
|
||||||
if ip -o addr show | awk '{print $4}' | cut -d/ -f1 | grep -Fx "$APISERVER_ADVERTISE_ADDRESS" >/dev/null 2>&1; then
|
|
||||||
log "API server advertise address already present: $APISERVER_ADVERTISE_ADDRESS"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
warn_dummy_interface
|
|
||||||
|
|
||||||
if ! command -v modprobe >/dev/null 2>&1; then
|
|
||||||
fail "modprobe not found; cannot create dummy interface"
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "loading dummy kernel module..."
|
|
||||||
modprobe dummy || fail "failed to load dummy kernel module"
|
|
||||||
|
|
||||||
if ! ip link show "$DUMMY_IFACE" >/dev/null 2>&1; then
|
|
||||||
log "creating dummy interface: $DUMMY_IFACE"
|
|
||||||
ip link add "$DUMMY_IFACE" type dummy || fail "failed to create dummy interface $DUMMY_IFACE"
|
|
||||||
else
|
|
||||||
log "dummy interface already exists: $DUMMY_IFACE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "assigning ${APISERVER_ADVERTISE_ADDRESS}/${DUMMY_PREFIX_LEN} to $DUMMY_IFACE"
|
|
||||||
ip addr add "${APISERVER_ADVERTISE_ADDRESS}/${DUMMY_PREFIX_LEN}" dev "$DUMMY_IFACE" 2>/dev/null || true
|
|
||||||
|
|
||||||
log "bringing up $DUMMY_IFACE"
|
|
||||||
ip link set "$DUMMY_IFACE" up || fail "failed to bring up $DUMMY_IFACE"
|
|
||||||
|
|
||||||
if ! ip -o addr show dev "$DUMMY_IFACE" | awk '{print $4}' | cut -d/ -f1 | grep -Fx "$APISERVER_ADVERTISE_ADDRESS" >/dev/null 2>&1; then
|
|
||||||
fail "dummy interface came up, but ${APISERVER_ADVERTISE_ADDRESS} is still not present"
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "dummy interface ready: $DUMMY_IFACE -> ${APISERVER_ADVERTISE_ADDRESS}/${DUMMY_PREFIX_LEN}"
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_local_kubectl() {
|
|
||||||
kube_dir="${KUBECONFIG_USER_HOME}/.kube"
|
|
||||||
log "setting up local kubectl config in ${kube_dir}/config..."
|
|
||||||
|
|
||||||
mkdir -p "$kube_dir"
|
|
||||||
cp /etc/kubernetes/admin.conf "${kube_dir}/config"
|
|
||||||
chmod 600 "${kube_dir}/config"
|
|
||||||
|
|
||||||
if [ "$KUBECONFIG_USER_HOME" = "/root" ]; then
|
|
||||||
mkdir -p /etc/profile.d
|
|
||||||
cat > /etc/profile.d/kubeconfig.sh <<'EOF'
|
|
||||||
export KUBECONFIG=/root/.kube/config
|
|
||||||
EOF
|
|
||||||
chmod 644 /etc/profile.d/kubeconfig.sh
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
allow_single_node_scheduling() {
|
|
||||||
if [ "$ALLOW_SCHEDULING_ON_CONTROL_PLANE" != "yes" ]; then
|
|
||||||
log "leaving control-plane taint in place"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "removing control-plane taint so this single node can schedule workloads..."
|
|
||||||
kubectl --kubeconfig /etc/kubernetes/admin.conf taint nodes "$NODE_NAME" node-role.kubernetes.io/control-plane- >/dev/null 2>&1 || true
|
|
||||||
}
|
|
||||||
|
|
||||||
print_next_steps() {
|
|
||||||
cat <<EOF
|
|
||||||
|
|
||||||
[monok8s] bootstrap complete
|
|
||||||
|
|
||||||
Try these now:
|
|
||||||
|
|
||||||
export KUBECONFIG=/root/.kube/config
|
|
||||||
kubectl get nodes -o wide
|
|
||||||
kubectl describe nodes
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
- On a fresh single-node control plane, the node may stay NotReady until you install a CNI.
|
|
||||||
- 'kubectl describe nodes' will still work as soon as the API server is up and your kubeconfig is installed.
|
|
||||||
- If you want pods to run on this same node, keep ALLOW_SCHEDULING_ON_CONTROL_PLANE=yes.
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
main() {
|
|
||||||
load_config
|
|
||||||
set_hostname_if_needed
|
|
||||||
ensure_ip_forward
|
|
||||||
ensure_apiserver_address_present
|
|
||||||
|
|
||||||
check_prereqs
|
|
||||||
check_already_initialized
|
|
||||||
check_crio_running
|
|
||||||
check_required_images
|
|
||||||
|
|
||||||
generate_kubeadm_config
|
|
||||||
run_kubeadm_init
|
|
||||||
|
|
||||||
rc-service kubelet restart
|
|
||||||
|
|
||||||
setup_local_kubectl
|
|
||||||
allow_single_node_scheduling
|
|
||||||
print_next_steps
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
||||||
@@ -9,6 +9,24 @@ POD_SUBNET=10.244.0.0/16
|
|||||||
SERVICE_SUBNET=10.96.0.0/12
|
SERVICE_SUBNET=10.96.0.0/12
|
||||||
CLUSTER_DOMAIN=cluster.local
|
CLUSTER_DOMAIN=cluster.local
|
||||||
|
|
||||||
|
# Bootstrap mode: init, join
|
||||||
|
BOOTSTRAP_MODE=init
|
||||||
|
|
||||||
|
# For join mode: worker, control-plane
|
||||||
|
JOIN_KIND=worker
|
||||||
|
API_SERVER_ENDPOINT=
|
||||||
|
BOOTSTRAP_TOKEN=
|
||||||
|
DISCOVERY_TOKEN_CA_CERT_HASH=
|
||||||
|
CONTROL_PLANE_CERT_KEY=
|
||||||
|
|
||||||
|
# none: Install manually
|
||||||
|
# bridge: CRIO's default bridge CNI
|
||||||
|
CNI_PLUGIN=none
|
||||||
|
|
||||||
|
# Node registration metadata
|
||||||
|
NODE_LABELS=topology.kubernetes.io/zone=lab,node.kubernetes.io/instance-type=mono-gateway
|
||||||
|
NODE_ANNOTATIONS=mono.si/board=ls1046a,mono.si/image-version=dev
|
||||||
|
|
||||||
# Optional
|
# Optional
|
||||||
# Extra API server SANs, comma-separated
|
# Extra API server SANs, comma-separated
|
||||||
SANS=127.0.0.1,localhost,monok8s-master
|
SANS=127.0.0.1,localhost,monok8s-master
|
||||||
|
|||||||
7
configs/node.env.default
Normal file
7
configs/node.env.default
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
HOSTNAME=monok8s-master
|
||||||
|
|
||||||
|
# RJ45 Ports from left to right (interface name inside the OS)
|
||||||
|
# - eth1, eth2, eth0
|
||||||
|
MGMT_IFACE=eth0
|
||||||
|
MGMT_ADDRESS=10.0.0.13/24
|
||||||
|
MGMT_GATEWAY=10.0.0.1
|
||||||
@@ -2,13 +2,69 @@
|
|||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
MNT=/mnt
|
MNT=/mnt
|
||||||
|
ROOT_MNT=/mnt-root
|
||||||
USB_DEV=/dev/sda
|
USB_DEV=/dev/sda
|
||||||
EMMC_DEV=/dev/mmcblk0
|
EMMC_DEV=/dev/mmcblk0
|
||||||
DEFAULT_GLOB="${MNT}/$${BUILD_TAG}_RELEASE_IMAGE"
|
EMMC_ROOT_PART=/dev/mmcblk0p1
|
||||||
|
CONFIG_DIR=/opt/monok8s/config
|
||||||
|
|
||||||
mkdir -p "$MNT"
|
DEFAULT_GLOB="${MNT}/${BUILD_TAG}_RELEASE_IMAGE"
|
||||||
|
|
||||||
echo "[*] Mounting USB..."
|
cleanup() {
|
||||||
|
umount "$ROOT_MNT" 2>/dev/null || true
|
||||||
|
umount "$MNT" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
log() {
|
||||||
|
echo "[*] $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
warn() {
|
||||||
|
echo "[!] $*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
die() {
|
||||||
|
echo "[!] ERROR: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
reread_partitions() {
|
||||||
|
log "Re-reading partition table..."
|
||||||
|
blockdev --rereadpt "$EMMC_DEV" 2>/dev/null || true
|
||||||
|
partprobe "$EMMC_DEV" 2>/dev/null || true
|
||||||
|
partx -u "$EMMC_DEV" 2>/dev/null || true
|
||||||
|
mdev -s 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
get_sectors() {
|
||||||
|
devname="${1##*/}"
|
||||||
|
cat "/sys/class/block/$devname/size" 2>/dev/null || echo 0
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_partition_ready() {
|
||||||
|
part="$1"
|
||||||
|
timeout="${2:-15}"
|
||||||
|
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt "$timeout" ]; do
|
||||||
|
if [ -b "$part" ]; then
|
||||||
|
sectors="$(get_sectors "$part")"
|
||||||
|
if [ "${sectors:-0}" -gt 0 ]; then
|
||||||
|
log "Partition $part is present with $sectors sectors"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
i=$((i + 1))
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir -p "$MNT" "$ROOT_MNT"
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
log "Mounting USB..."
|
||||||
mount "$USB_DEV" "$MNT"
|
mount "$USB_DEV" "$MNT"
|
||||||
|
|
||||||
# Find a default image (first match)
|
# Find a default image (first match)
|
||||||
@@ -23,45 +79,83 @@ else
|
|||||||
fi
|
fi
|
||||||
echo
|
echo
|
||||||
|
|
||||||
# Prompt user
|
|
||||||
printf "Image Location (%s): " "${DEFAULT_IMG:-/mnt/...}"
|
printf "Image Location (%s): " "${DEFAULT_IMG:-/mnt/...}"
|
||||||
read -r IMG
|
read -r IMG
|
||||||
|
|
||||||
# Use default if empty
|
|
||||||
if [ -z "$IMG" ]; then
|
if [ -z "$IMG" ]; then
|
||||||
IMG="$DEFAULT_IMG"
|
IMG="$DEFAULT_IMG"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Validate
|
[ -n "$IMG" ] || die "No image selected"
|
||||||
if [ -z "$IMG" ] || [ ! -f "$IMG" ]; then
|
[ -f "$IMG" ] || die "Invalid image: $IMG"
|
||||||
echo "Invalid image: $IMG"
|
|
||||||
umount "$MNT" || true
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo
|
echo
|
||||||
|
echo "Looking for env files on USB..."
|
||||||
|
ENV_FILES="$(find "$MNT" -maxdepth 1 -type f -name '*.env' | sort || true)"
|
||||||
|
|
||||||
|
if [ -n "$ENV_FILES" ]; then
|
||||||
|
echo "Found env files:"
|
||||||
|
echo "$ENV_FILES" | sed 's/^/ /'
|
||||||
|
else
|
||||||
|
echo "No *.env files found in $MNT"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
|
||||||
echo "About to write:"
|
echo "About to write:"
|
||||||
echo " Image: $IMG"
|
echo " Image: $IMG"
|
||||||
echo " Target: $EMMC_DEV"
|
echo " Target: $EMMC_DEV"
|
||||||
echo
|
echo
|
||||||
|
|
||||||
printf "Type 'YES' to continue: "
|
printf "Type 'YES' to continue: "
|
||||||
read -r CONFIRM
|
read -r CONFIRM
|
||||||
|
|
||||||
if [ "$CONFIRM" != "YES" ]; then
|
[ "$CONFIRM" = "YES" ] || die "Aborted."
|
||||||
echo "Aborted."
|
|
||||||
umount "$MNT" || true
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "[*] Flashing..."
|
log "Flashing image to eMMC..."
|
||||||
gunzip -c "$IMG" > "$EMMC_DEV"
|
gunzip -c "$IMG" > "$EMMC_DEV"
|
||||||
|
|
||||||
echo "[*] Syncing..."
|
log "Syncing flash write..."
|
||||||
sync
|
sync
|
||||||
|
|
||||||
echo "[*] Done."
|
reread_partitions
|
||||||
umount "$MNT" || true
|
|
||||||
|
|
||||||
echo "[*] Rebooting..."
|
log "Waiting for partition nodes..."
|
||||||
|
if ! wait_for_partition_ready "$EMMC_ROOT_PART" 15; then
|
||||||
|
warn "Initial partition scan did not settle; retrying after another reread..."
|
||||||
|
reread_partitions
|
||||||
|
sleep 2
|
||||||
|
wait_for_partition_ready "$EMMC_ROOT_PART" 15 || die "root partition not ready: $EMMC_ROOT_PART"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Partition sizes:"
|
||||||
|
echo " $(basename "$EMMC_DEV"): $(get_sectors "$EMMC_DEV") sectors"
|
||||||
|
echo " $(basename "$EMMC_ROOT_PART"): $(get_sectors "$EMMC_ROOT_PART") sectors"
|
||||||
|
|
||||||
|
if [ -n "$ENV_FILES" ]; then
|
||||||
|
log "Mounting flashed root partition..."
|
||||||
|
mount "$EMMC_ROOT_PART" "$ROOT_MNT" || {
|
||||||
|
warn "Mount failed; retrying once after partition reread..."
|
||||||
|
reread_partitions
|
||||||
|
sleep 2
|
||||||
|
mount "$EMMC_ROOT_PART" "$ROOT_MNT"
|
||||||
|
}
|
||||||
|
|
||||||
|
log "Creating config dir: $CONFIG_DIR"
|
||||||
|
mkdir -p "$ROOT_MNT$CONFIG_DIR"
|
||||||
|
|
||||||
|
log "Copying env files..."
|
||||||
|
for f in $ENV_FILES; do
|
||||||
|
base="$(basename "$f")"
|
||||||
|
cp "$f" "$ROOT_MNT$CONFIG_DIR/$base"
|
||||||
|
echo " copied: $base"
|
||||||
|
done
|
||||||
|
|
||||||
|
log "Syncing copied config..."
|
||||||
|
sync
|
||||||
|
|
||||||
|
umount "$ROOT_MNT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Done."
|
||||||
|
log "Rebooting..."
|
||||||
reboot -f
|
reboot -f
|
||||||
@@ -482,18 +482,21 @@
|
|||||||
ethernet@e8000 {
|
ethernet@e8000 {
|
||||||
phy-handle = <&sgmii_phy0>;
|
phy-handle = <&sgmii_phy0>;
|
||||||
phy-connection-type = "sgmii";
|
phy-connection-type = "sgmii";
|
||||||
|
managed = "in-band-status";
|
||||||
status = "okay";
|
status = "okay";
|
||||||
};
|
};
|
||||||
|
|
||||||
ethernet@ea000 {
|
ethernet@ea000 {
|
||||||
phy-handle = <&sgmii_phy1>;
|
phy-handle = <&sgmii_phy1>;
|
||||||
phy-connection-type = "sgmii";
|
phy-connection-type = "sgmii";
|
||||||
|
managed = "in-band-status";
|
||||||
status = "okay";
|
status = "okay";
|
||||||
};
|
};
|
||||||
|
|
||||||
ethernet@e2000 {
|
ethernet@e2000 {
|
||||||
phy-handle = <&sgmii_phy2>;
|
phy-handle = <&sgmii_phy2>;
|
||||||
phy-connection-type = "sgmii";
|
phy-connection-type = "sgmii";
|
||||||
|
managed = "in-band-status";
|
||||||
status = "okay";
|
status = "okay";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
17
makefile
17
makefile
@@ -20,6 +20,8 @@ CONFIGS_DIR := configs
|
|||||||
SCRIPTS_DIR := scripts
|
SCRIPTS_DIR := scripts
|
||||||
CLUSTER_ENV_DEFAULT := $(CONFIGS_DIR)/cluster.env.default
|
CLUSTER_ENV_DEFAULT := $(CONFIGS_DIR)/cluster.env.default
|
||||||
CLUSTER_ENV := $(OUT_DIR)/cluster.env
|
CLUSTER_ENV := $(OUT_DIR)/cluster.env
|
||||||
|
NODE_ENV_DEFAULT := configs/node.env.default
|
||||||
|
NODE_ENV := $(OUT_DIR)/node.env
|
||||||
|
|
||||||
BOARD_ITB := $(OUT_DIR)/board.itb
|
BOARD_ITB := $(OUT_DIR)/board.itb
|
||||||
INITRAMFS := $(OUT_DIR)/initramfs.cpio.gz
|
INITRAMFS := $(OUT_DIR)/initramfs.cpio.gz
|
||||||
@@ -211,14 +213,25 @@ check-functions:
|
|||||||
in_main && /^[[:space:]]*[a-zA-Z_][a-zA-Z0-9_]*[[:space:]]*$$/ { \
|
in_main && /^[[:space:]]*[a-zA-Z_][a-zA-Z0-9_]*[[:space:]]*$$/ { \
|
||||||
gsub(/^[ \t]+/, "", $$0); \
|
gsub(/^[ \t]+/, "", $$0); \
|
||||||
print $$0 \
|
print $$0 \
|
||||||
}' ./alpine/rootfs-extra/opt/scripts/bootstrap-control-plane.sh \
|
}' ./alpine/rootfs-extra/opt/scripts/bootstrap-cluster.sh \
|
||||||
| sort -u > /tmp/called.txt
|
| sort -u > /tmp/called.txt
|
||||||
@grep -E '^[a-zA-Z_][a-zA-Z0-9_]*\(\)' ./alpine/rootfs-extra/opt/scripts/bootstrap-control-plane.sh \
|
@grep -E '^[a-zA-Z_][a-zA-Z0-9_]*\(\)' ./alpine/rootfs-extra/opt/scripts/bootstrap-cluster.sh \
|
||||||
| sed 's/().*//' \
|
| sed 's/().*//' \
|
||||||
| sort -u > /tmp/defined.txt
|
| sort -u > /tmp/defined.txt
|
||||||
@echo "Missing functions:"
|
@echo "Missing functions:"
|
||||||
@comm -23 /tmp/called.txt /tmp/defined.txt || true
|
@comm -23 /tmp/called.txt /tmp/defined.txt || true
|
||||||
|
|
||||||
|
# ---- node targets ------------------------------------------------------------
|
||||||
|
|
||||||
|
node-config: $(NODE_ENV_DEFAULT) $(SCRIPTS_DIR)/merge-env.sh | $(OUT_DIR)
|
||||||
|
sh $(SCRIPTS_DIR)/merge-env.sh $(NODE_ENV_DEFAULT) $(NODE_ENV)
|
||||||
|
|
||||||
|
node-defconfig: $(NODE_ENV_DEFAULT) | $(OUT_DIR)
|
||||||
|
cp $(NODE_ENV_DEFAULT) $(NODE_ENV)
|
||||||
|
|
||||||
|
node-print:
|
||||||
|
@cat $(NODE_ENV)
|
||||||
|
|
||||||
# ---- cluster targets ------------------------------------------------------------
|
# ---- cluster targets ------------------------------------------------------------
|
||||||
|
|
||||||
cluster-config: $(CLUSTER_ENV_DEFAULT) $(SCRIPTS_DIR)/merge-env.sh | $(OUT_DIR)
|
cluster-config: $(CLUSTER_ENV_DEFAULT) $(SCRIPTS_DIR)/merge-env.sh | $(OUT_DIR)
|
||||||
|
|||||||
Reference in New Issue
Block a user