Verified Commit 3b9603fc authored by Sebastian Schüpbach's avatar Sebastian Schüpbach
Browse files

initial commit

parents
Pipeline #31675 passed with stages
in 15 minutes and 33 seconds
stages:
- test
- build
- publish
default:
image: rust:alpine
variables:
BIN_NAME: metrics-client
test:
stage: test
before_script:
- rustup self update
- rustup component add clippy
- apk update
- apk add cmake openssl-dev build-base
script:
- cargo test --bin $BIN_NAME
- cargo clippy --bin $BIN_NAME
except:
- tags
build:
stage: build
before_script:
- apk update
- apk add musl-dev upx build-base cmake openssl-dev
script:
- cargo build --release --bin $BIN_NAME
- strip target/release/$BIN_NAME
- upx --best -q --lzma target/release/$BIN_NAME
- mv target/release/$BIN_NAME app
artifacts:
name: "${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}"
paths:
- app
expire_in: 1 hour
public: false
when: on_success
.build-image:
stage: publish
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
script:
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile.ci --destination $IMAGE_TAG
build-tagged-image:
extends: .build-image
variables:
IMAGE_TAG: "$CI_REGISTRY_IMAGE:$BIN_NAME-$CI_COMMIT_TAG"
only:
- tags
build-latest-image:
extends: .build-image
variables:
IMAGE_TAG: "$CI_REGISTRY_IMAGE:$BIN_NAME-latest"
only:
- master
build-feature-branch-image:
extends: .build-image
variables:
IMAGE_TAG: "$CI_REGISTRY_IMAGE:$BIN_NAME-$CI_COMMIT_REF_NAME"
except:
- master
- tags
pages:
stage: publish
only:
- master
before_script:
- apk update
- apk add cmake openssl-dev build-base
script:
- cargo doc --no-deps
- mv target/doc public
artifacts:
paths:
- public
This diff is collapsed.
[package]
name = "metrics-client"
version = "0.0.1-SNAPSHOT"
authors = ["Sebastian Schüpbach <sebastian.schuepbach@unibas.ch>"]
edition = "2018"
description = "k8s metrics client"
documentation = "https://sschuepbach.pages.switch.ch/metrics-client/metrics-client"
readme = "README.md"
homepage = "https://gitlab.switch.ch/sschuepbach/metrics-client"
repository = "https://gitlab.switch.ch/sschuepbach/metrics-client"
license-file = "LICENSE"
keywords = ["command-line-utilities", "k8s", "metrics"]
categories = ["command-line-utilities"]
exclude = ["/target"]
[dependencies]
anyhow = "1.0.43"
clap = "2.33.3"
env_logger = "0.9.0"
k8s-openapi = { version = "0.13.0", features = ["v1_20"], default-features = false }
kube = "0.59.0"
kube-runtime = "0.59.0"
log = "0.4.14"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.66"
tokio = { version = "1.10.0", features = ["full"] }
[profile.release]
# Optimize for size
opt-level = 'z'
lto = true
codegen-units = 1
panic = 'abort'
FROM index.docker.io/library/rust:alpine AS build
ARG APPNAME=kafkaesque
RUN apk update && apk add musl-dev upx build-base cmake openssl-dev
WORKDIR /buildenv
RUN USER=root cargo new -q --vcs none --bin $APPNAME
WORKDIR /buildenv/$APPNAME
COPY Cargo.toml .
COPY Cargo.lock .
RUN cargo build --release
RUN rm -r src && rm -f ./target/release/deps/$APPNAME*
COPY src/ src/
RUN cargo build --release && strip ./target/release/$APPNAME && upx --best -q --lzma ./target/release/$APPNAME
RUN mkdir /build && mv ./target/release/$APPNAME /build/app
FROM scratch
WORKDIR /app
COPY --from=build /build/app app
ENTRYPOINT ["./app"]
FROM scratch
WORKDIR /app
COPY app app
ENTRYPOINT ["/app/app"]
This diff is collapsed.
# K8s Metrics Client
This application is an MVP (Minimal Viable Product) of a k8s metrics client.
Its task is to simply fetch metrics of pods on a regular basis and to send the
numbers to a Kafka topic, from which it could be processed for visualisation
purposes.
## Configuration
Settings are defined via environmental variables:
* `RETENTION_TIME_MIN`:
* `TOPIC_NAME`
* `UPDATE_INTERVAL_MIN`:
use std::time::Duration;
use anyhow::{Context, Result};
use api::Pod;
use clap::{App, Arg, ArgMatches};
use k8s_openapi::api::core::v1 as api;
use k8s_openapi::apimachinery::pkg::api::resource::Quantity;
use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta;
use k8s_openapi::{Metadata, NamespaceResourceScope, Resource};
use kube::api::ListParams;
use kube::{Api, Client};
use log::info;
use serde::{Deserialize, Serialize};
fn parse_config() -> ArgMatches<'static> {
App::new("k8s metrics client")
.version("0.0.1-SNAPSHOT")
.author("Sebastian Schüpbach <sebastian.schuepbach@unibas.ch>")
.about("Fetches k8s pod metrics and publishes the numbers in a Kafka topic")
.arg(
Arg::with_name("bootstrap_servers")
.short("b")
.long("bootstrap-servers")
.value_name("HOST:PORT[,HOST:PORT]")
.help("Sets one or more Kafka bootstrap servers")
.takes_value(true)
.required(true),
)
.arg(
Arg::with_name("topic")
.short("k")
.long("topic")
.value_name("NAME")
.help("Sets Kafka topic name")
.takes_value(true)
.required(true),
)
.arg(
Arg::with_name("namespace")
.short("n")
.long("namespace")
.value_name("NAME")
.help("k8s namespace to watch")
.takes_value(true)
.required(true),
)
.arg(
Arg::with_name("retention_time")
.short("r")
.long("retention-time")
.value_name("MIN")
.help("Retention time for metrics (in minutes, default: 10080 (1 week))")
.takes_value(true),
)
.arg(
Arg::with_name("update_interval")
.short("u")
.long("update-interval")
.value_name("MIN")
.help("Metrics update interval (in minutes, default: 1)")
.takes_value(true),
)
.arg(
Arg::with_name("producer_configs")
.short("p")
.long("producer-config")
.value_name("key=value")
.help("Defines a Kafka producer configuration")
.takes_value(true)
.multiple(true)
.number_of_values(1),
)
.get_matches()
}
#[derive(Debug, Serialize)]
struct KafkaMessage {
pod: String,
timestamp: usize,
cpu: u16,
ram: u16,
}
#[derive(Deserialize, Clone, Debug)]
pub struct PodMetricsContainer {
pub name: String,
pub usage: PodMetricsContainerUsage,
}
#[derive(serde::Deserialize, Clone, Debug)]
pub struct PodMetricsContainerUsage {
pub cpu: Quantity,
pub memory: Quantity,
}
#[derive(serde::Deserialize, Clone, Debug)]
pub struct PodMetrics {
pub metadata: ObjectMeta,
pub timestamp: String,
pub window: String,
pub containers: Vec<PodMetricsContainer>,
}
impl Resource for PodMetrics {
type Scope = NamespaceResourceScope;
const GROUP: &'static str = "metrics.k8s.io";
const KIND: &'static str = "pod";
const VERSION: &'static str = "v1beta1";
const API_VERSION: &'static str = "metrics.k8s.io/v1beta1";
const URL_PATH_SEGMENT: &'static str = "";
}
impl Metadata for PodMetrics {
type Ty = ObjectMeta;
fn metadata(&self) -> &Self::Ty {
&self.metadata
}
fn metadata_mut(&mut self) -> &mut Self::Ty {
&mut self.metadata
}
}
#[tokio::main]
async fn main() -> Result<()> {
env_logger::init();
info!("Starting application...");
let matches = parse_config();
// Use unwrap() where values are required
// FIXME
let _bootstrap_servers = matches
.value_of("bootstrap_servers")
.unwrap()
.split(',')
.collect::<Vec<&str>>();
// FIXME
let _topic = matches.value_of("topic").unwrap();
let namespace = matches.value_of("namespace").unwrap();
// FIXME
let _retention_time = matches
.value_of("retention_time")
.and_then(|rt| rt.parse().ok())
.unwrap_or(10080);
let update_interval = matches
.value_of("update_interval")
.and_then(|ui| ui.parse().ok())
.unwrap_or(1);
// let producer_configs = matches.value_of("producer_configs");
let client = Client::try_default().await.context("building client")?;
// FIXME
let pods: Api<Pod> = Api::namespaced(client, namespace);
loop {
let list_params = ListParams::default();
let pods_list = pods.list(&list_params).await.context("listing pods")?;
for pod in pods_list {
println!("Pod detected: {}", pod.metadata.name.unwrap());
}
std::thread::sleep(Duration::from_secs(update_interval * 60));
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment