use crate::{
component::{ClusterContext, Component, Phase},
config::Config,
encryptionconfig::EncryptionConfig,
kubectl::Kubectl,
network::Network,
pki::Pki,
process::{Process, ProcessState, Stoppable},
write_if_changed,
};
use anyhow::{Context, Result};
use log::debug;
use std::{fs::create_dir_all, path::Path};
pub struct ApiServerComponent;
impl Component for ApiServerComponent {
fn name(&self) -> &str {
"API Server"
}
fn phase(&self) -> Phase {
Phase::ControlPlane
}
fn start(&self, ctx: &ClusterContext<'_>) -> ProcessState {
ApiServer::start(
ctx.config,
ctx.network,
ctx.pki,
ctx.encryptionconfig,
ctx.kubectl,
)
}
}
pub struct ApiServer {
process: Process,
}
impl ApiServer {
pub fn start(
config: &Config,
network: &Network,
pki: &Pki,
encryptionconfig: &EncryptionConfig,
kubectl: &Kubectl,
) -> ProcessState {
let dir = config.root().join("apiserver");
create_dir_all(&dir)?;
let mut process = Process::start(
&dir,
"API Server",
"kube-apiserver",
&[
"--audit-log-maxage=30",
"--audit-log-maxbackup=3",
"--audit-log-maxsize=100",
&format!("--audit-log-path={}", dir.join("audit.log").display()),
"--authorization-mode=Node,RBAC",
"--bind-address=0.0.0.0",
&format!("--client-ca-file={}", pki.ca().cert().display()),
&format!("--etcd-cafile={}", pki.ca().cert().display()),
&format!("--etcd-certfile={}", pki.apiserver().cert().display()),
&format!("--etcd-keyfile={}", pki.apiserver().key().display()),
&format!("--etcd-servers=https://{}", network.etcd_client()),
"--event-ttl=1h",
&format!(
"--encryption-provider-config={}",
encryptionconfig.path().display()
),
&format!(
"--kubelet-certificate-authority={}",
pki.ca().cert().display()
),
&format!(
"--kubelet-client-certificate={}",
pki.apiserver().cert().display()
),
&format!("--kubelet-client-key={}", pki.apiserver().key().display()),
"--runtime-config=api/all=true",
"--service-account-issuer=https://kubernetes.default.svc.cluster.local",
&format!(
"--service-account-key-file={}",
pki.service_account().cert().display()
),
&format!(
"--service-account-signing-key-file={}",
pki.service_account().key().display()
),
&format!("--service-cluster-ip-range={}", network.service_cidr()),
&format!("--tls-cert-file={}", pki.apiserver().cert().display()),
&format!("--tls-private-key-file={}", pki.apiserver().key().display()),
"--v=2",
],
)?;
process.wait_ready("Serving securely")?;
Self::setup_rbac(&dir, kubectl)?;
Ok(Box::new(Self { process }))
}
fn setup_rbac(dir: &Path, kubectl: &Kubectl) -> Result<()> {
debug!("Creating API Server RBAC rule for kubelet");
let file = dir.join("rbac.yml");
write_if_changed(&file, include_str!("assets/apiserver.yml"))?;
kubectl
.apply(&file)
.context("Unable to deploy API server RBAC rules")?;
debug!("API Server RBAC rule created");
Ok(())
}
}
impl Stoppable for ApiServer {
fn stop(&mut self) -> Result<()> {
self.process.stop()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rbac_asset_contains_expected_rules() {
let rbac = include_str!("assets/apiserver.yml");
assert!(rbac.contains("kind: ClusterRole"));
assert!(rbac.contains("kind: ClusterRoleBinding"));
assert!(rbac.contains("system:kube-apiserver-to-kubelet"));
assert!(rbac.contains("nodes/proxy"));
assert!(rbac.contains("nodes/stats"));
}
#[test]
fn component_metadata() {
let c = ApiServerComponent;
assert_eq!(c.name(), "API Server");
assert_eq!(c.phase(), Phase::ControlPlane);
}
}