diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d8b19ab --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +target/debug +target/release/.fingerprint +target/release/build +target/release/deps +target/release/examples +target/release/incremental \ No newline at end of file diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..d3b4849 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,7 @@ +#!/bin/bash + +main() { + echo "Running Pre-Commit Hooks" +} + +main diff --git a/.githooks/pre-push b/.githooks/pre-push new file mode 100755 index 0000000..d987ed7 --- /dev/null +++ b/.githooks/pre-push @@ -0,0 +1,7 @@ +#!/bin/bash + +main() { + echo "Running Pre-Push Hooks" +} + +main diff --git a/.gitignore b/.gitignore index 088ba6b..628bdbc 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,7 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk + +.helm +.idea +tilt_modules/ \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..db64660 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,4 @@ +[workspace] +members = [ + "crates/*", +] \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6782097 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM rust +ARG binary_location +ARG binary_name +COPY $binary_location /bin/$binary_name +ENV binary_name $binary_name +CMD /bin/$binary_name diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4774456 --- /dev/null +++ b/Makefile @@ -0,0 +1,32 @@ +# ---------------------------------------- +# Build all crates in release mode +# ---------------------------------------- +cargo-build: + cargo build --release + +# ---------------------------------------- +# # Set Git Config for Git Hooks +# ---------------------------------------- +hooks: + $(shell git config --local core.hooksPath .githooks) + +# ---------------------------------------- +# API Gateway +# ---------------------------------------- +pack-api-gateway: + docker build -t api-gateway --build-arg binary_location=target/release/api-gateway --build-arg binary_name=api-gateway . + minikube image load api-gateway:latest + +# ---------------------------------------- +# Consumer +# ---------------------------------------- +pack-consumer: + docker build -t consumer --build-arg binary_location=target/release/consumer --build-arg binary_name=consumer . + minikube image load consumer:latest + +# ---------------------------------------- +# Producer +# ---------------------------------------- +pack-producer: + docker build -t producer --build-arg binary_location=target/release/producer --build-arg binary_name=producer . + minikube image load producer:latest \ No newline at end of file diff --git a/README.md b/README.md index e8c2ece..d2cb4e1 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,40 @@ -# connected-home -A distributed multi-service application for collecting, aggregating, and visualizing data from various sensors +# Connected Home +___ + +A distributed multi-service application written in Rust and deployed to Kubernetes. Useful for collecting, aggregating, +and visualizing data from various sensors around the home. Disclaimer: There is little reason to be using Kubernetes +other than the fact that I want to learn it (along with helm and Tilt) better. I suppose you could argue that the +readiness / health probes are worth it, but I'm sure using Docker compose would have been easier and just as practical. + + +## What does it do? +___ + +Not much yet... Currently, I am working on the design, creating the base structure, evaluating and learning tools / +technologies to use in the project. + +## TODO +___ + +* [ ] Set up persistent volume for MariaDB (consider PostgreSQL) +* [ ] Evaluate DB options for logging sensor data (no SQL) +* [ ] Create mqtt consumer that can deserialize incoming messages into a struct and serialize to DB +* [ ] Create Raspberry Pi (or similar) w/sensor and create base image + producer application + * [ ] Create mock producer for testing locally and for integration testing +* [ ] Create useful ready/healthy probes for services +* [ ] Define pre-commit and pre-push hooks +* [ ] Create CI/CD pipeline +* [ ] gRPC for synchronous inter-service communication (maybe) +* [ ] GraphQL API (maybe) +* [ ] Frontend for visualizing data + * [ ] Define user/groups/permission model and create administrator frontend for provisioning (mobile app?) + * [ ] Create a dashboard where users can view and filter data from their sensors +* [ ] Create alarms/alerts and trigger user customizable actions (send an email / Slack message). + +## Requirements +___ + +* [rust](https://www.rust-lang.org/tools/install) +* [docker](https://www.docker.com/) +* [minikube](https://minikube.sigs.k8s.io/docs/start/) +* [tilt](https://tilt.dev/) diff --git a/Tiltfile b/Tiltfile new file mode 100644 index 0000000..c1d709f --- /dev/null +++ b/Tiltfile @@ -0,0 +1,75 @@ +# Tiltfile +load('ext://helm_remote', 'helm_remote') + +local_resource('cargo build', 'make cargo-build', trigger_mode=TRIGGER_MODE_MANUAL) + +local_resource( + 'pack api gateway', + 'make pack-api-gateway', + trigger_mode=TRIGGER_MODE_AUTO, + resource_deps=[ + 'cargo build', + ], +) + +local_resource( + 'pack consumer', + 'make pack-consumer', + trigger_mode=TRIGGER_MODE_AUTO, + resource_deps=[ + 'cargo build', + ], +) + +local_resource( + 'pack producer', + 'make pack-producer', + trigger_mode=TRIGGER_MODE_AUTO, + resource_deps=[ + 'cargo build', + ], +) + +k8s_yaml(helm('./helm/api-gateway')) +k8s_resource( + workload='api-gateway', + resource_deps=[ + 'cargo build', + 'pack api gateway', + ], + port_forwards=[ + port_forward(8082, 8082, name='API Gateway'), + ] +) + +k8s_yaml(helm('./helm/producer')) +k8s_resource( + workload='producer', + resource_deps=[ + 'cargo build', + 'pack producer', + ], +) + +k8s_yaml(helm('./helm/consumer')) +k8s_resource( + workload='consumer', + resource_deps=[ + 'cargo build', + 'pack consumer', + ], +) + +helm_remote( + 'mariadb', + repo_name='bitnami', + repo_url='https://charts.bitnami.com/bitnami', + version='9.5.1', +) + +helm_remote( + 'rabbitmq', + repo_name='bitnami', + repo_url='https://charts.bitnami.com/bitnami', + version='8.22.0', +) \ No newline at end of file diff --git a/crates/api-gateway/.gitignore b/crates/api-gateway/.gitignore new file mode 100644 index 0000000..3eb9b5b --- /dev/null +++ b/crates/api-gateway/.gitignore @@ -0,0 +1,2 @@ +/target +./idea diff --git a/crates/api-gateway/Cargo.toml b/crates/api-gateway/Cargo.toml new file mode 100644 index 0000000..2483b87 --- /dev/null +++ b/crates/api-gateway/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "api-gateway" +version = "0.1.0" +authors = ["Steve Sampson "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +pretty_env_logger = "0.4" +futures = { version = "0.3", default-features = false, features = ["alloc"] } +tokio = { version = "1.0", features = ["fs", "sync", "time", "io-util"] } +tokio-stream = "0.1.1" +tokio-util = { version = "0.6", features = ["io"] } +serde = { version = "1.0", features = ["derive"] } +tracing = { version = "0.1.21", default-features = false, features = ["std"] } +warp = "0.3" + + +[dev-dependencies] +tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "io-util"] } diff --git a/crates/api-gateway/src/lib.rs b/crates/api-gateway/src/lib.rs new file mode 100644 index 0000000..924473d --- /dev/null +++ b/crates/api-gateway/src/lib.rs @@ -0,0 +1,134 @@ +// #![deny(warnings)] +use std::collections::HashMap; +use std::sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, +}; + +use futures::{SinkExt, StreamExt, TryFutureExt}; +use tokio::sync::{mpsc, RwLock}; +use tokio_stream::wrappers::UnboundedReceiverStream; +use warp::ws::{Message, WebSocket}; + +static NEXT_USER_ID: AtomicUsize = AtomicUsize::new(1); +pub type Users = Arc>>>; + +pub async fn user_connected(ws: WebSocket, users: Users) { + // Use a counter to assign a new unique ID for this user. + let my_id = NEXT_USER_ID.fetch_add(1, Ordering::Relaxed); + + eprintln!("new chat user: {}", my_id); + + // Split the socket into a sender and receive of messages. + let (mut user_ws_tx, mut user_ws_rx) = ws.split(); + + // Use an unbounded channel to handle buffering and flushing of messages + // to the websocket... + let (tx, rx) = mpsc::unbounded_channel(); + let mut rx = UnboundedReceiverStream::new(rx); + + tokio::task::spawn(async move { + while let Some(message) = rx.next().await { + user_ws_tx + .send(message) + .unwrap_or_else(|e| { + eprintln!("websocket send error: {}", e); + }) + .await; + } + }); + + // Save the sender in our list of connected users. + users.write().await.insert(my_id, tx); + + // Return a `Future` that is basically a state machine managing + // this specific user's connection. + + // Every time the user sends a message, broadcast it to + // all other users... + while let Some(result) = user_ws_rx.next().await { + let msg = match result { + Ok(msg) => msg, + Err(e) => { + eprintln!("websocket error(uid={}): {}", my_id, e); + break; + } + }; + user_message(my_id, msg, &users).await; + } + + // user_ws_rx stream will keep processing as long as the user stays + // connected. Once they disconnect, then... + user_disconnected(my_id, &users).await; +} + +pub async fn user_message(my_id: usize, msg: Message, users: &Users) { + // Skip any non-Text messages... + let msg = if let Ok(s) = msg.to_str() { + s + } else { + return; + }; + + let new_msg = format!(": {}", my_id, msg); + + // New message from this user, send it to everyone else (except same uid)... + for (&uid, tx) in users.read().await.iter() { + if my_id != uid { + if let Err(_disconnected) = tx.send(Message::text(new_msg.clone())) { + // The tx is disconnected, our `user_disconnected` code + // should be happening in another task, nothing more to + // do here. + } + } + } +} + +pub async fn user_disconnected(my_id: usize, users: &Users) { + eprintln!("good bye user: {}", my_id); + + // Stream closed up, so remove from the user list + users.write().await.remove(&my_id); +} + +pub static INDEX_HTML: &str = r#" + + + Warp Chat + + +

Warp chat

+
+

Connecting...

+
+ + + + + +"#; \ No newline at end of file diff --git a/crates/api-gateway/src/main.rs b/crates/api-gateway/src/main.rs new file mode 100644 index 0000000..a8314f6 --- /dev/null +++ b/crates/api-gateway/src/main.rs @@ -0,0 +1,40 @@ +#![deny(warnings)] +use api_gateway::{user_connected, Users, INDEX_HTML}; +use serde::Serialize; +use warp::Filter; + +#[derive(Serialize)] +enum Status { + Ok, +} + +#[tokio::main] +async fn main() { + pretty_env_logger::init(); + let users = Users::default(); + let users = warp::any().map(move || users.clone()); + + let chat = warp::path("chat") + .and(warp::get()) + .and(warp::ws()) + .and(users) + .map(|ws: warp::ws::Ws, users| ws.on_upgrade(move |socket| user_connected(socket, users))); + + let home = warp::path("home") + .and(warp::get()) + .map(|| warp::reply::html(INDEX_HTML)); + + let ready = warp::path("ready") + .and(warp::get()) + .map(|| warp::reply::json(&Status::Ok)); + + let healthy = warp::path("healthy") + .and(warp::get()) + .map(|| warp::reply::json(&Status::Ok)); + + let routes = home.or(chat).or(healthy).or(ready); + + // we listen on all interfaces because we will be inside of a container + // and we do not know what IP we will be assigned + warp::serve(routes).run(([0, 0, 0, 0], 8082)).await; +} diff --git a/crates/consuimer/.gitignore b/crates/consuimer/.gitignore new file mode 100644 index 0000000..3eb9b5b --- /dev/null +++ b/crates/consuimer/.gitignore @@ -0,0 +1,2 @@ +/target +./idea diff --git a/crates/consuimer/Cargo.toml b/crates/consuimer/Cargo.toml new file mode 100644 index 0000000..ed1351d --- /dev/null +++ b/crates/consuimer/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "consumer" +version = "0.1.0" +authors = ["Steve Sampson "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +hyper = "0.14" +routerify = "2.2.0" +tokio = { version = "1.0", features = ["fs", "sync", "time", "io-util"] } diff --git a/crates/consuimer/src/main.rs b/crates/consuimer/src/main.rs new file mode 100644 index 0000000..18caae2 --- /dev/null +++ b/crates/consuimer/src/main.rs @@ -0,0 +1,52 @@ +use hyper::{Body, Request, Response, Server}; +use routerify::prelude::*; +use routerify::{Middleware, Router, RouterService}; +use std::{convert::Infallible, net::SocketAddr}; + +struct State { + _val: u64, +} + +async fn ready_handler(_req: Request) -> Result, Infallible> { + //let state = req.data::().unwrap(); + //println!("State value: {}", state.0); + Ok(Response::new(Body::from("OK"))) +} + +async fn healthy_handler(_req: Request) -> Result, Infallible> { + //let state = req.data::().unwrap(); + //println!("State value: {}", state.0); + Ok(Response::new(Body::from("OK"))) +} + +async fn logger(req: Request) -> Result, Infallible> { + println!( + "{} {} {}", + req.remote_addr(), + req.method(), + req.uri().path() + ); + Ok(req) +} + +fn router() -> Router { + Router::builder() + .data(State { _val: 100 }) + .middleware(Middleware::pre(logger)) + .get("/ready", ready_handler) + .get("/healthy", healthy_handler) + .build() + .unwrap() +} + +#[tokio::main] +async fn main() { + let router = router(); + let service = RouterService::new(router).unwrap(); + let addr = SocketAddr::from(([0, 0, 0, 0], 8082)); + let server = Server::bind(&addr).serve(service); + println!("Consumer is running on: {}", addr); + if let Err(err) = server.await { + eprintln!("Server error: {}", err); + } +} diff --git a/crates/producer/.gitignore b/crates/producer/.gitignore new file mode 100644 index 0000000..3eb9b5b --- /dev/null +++ b/crates/producer/.gitignore @@ -0,0 +1,2 @@ +/target +./idea diff --git a/crates/producer/Cargo.toml b/crates/producer/Cargo.toml new file mode 100644 index 0000000..8f7c1ef --- /dev/null +++ b/crates/producer/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "producer" +version = "0.1.0" +authors = ["Steve Sampson "] +edition = "2018" + +[dependencies] +hyper = "0.14" +routerify = "2.2.0" +tokio = { version = "1.0", features = ["fs", "sync", "time", "io-util"] } + diff --git a/crates/producer/src/main.rs b/crates/producer/src/main.rs new file mode 100644 index 0000000..2888ee9 --- /dev/null +++ b/crates/producer/src/main.rs @@ -0,0 +1,52 @@ +use hyper::{Body, Request, Response, Server}; +use routerify::prelude::*; +use routerify::{Middleware, Router, RouterService}; +use std::{convert::Infallible, net::SocketAddr}; + +struct State { + _val: u64, +} + +async fn ready_handler(_req: Request) -> Result, Infallible> { + //let state = req.data::().unwrap(); + //println!("State value: {}", state.0); + Ok(Response::new(Body::from("OK"))) +} + +async fn healthy_handler(_req: Request) -> Result, Infallible> { + //let state = req.data::().unwrap(); + //println!("State value: {}", state.0); + Ok(Response::new(Body::from("OK"))) +} + +async fn logger(req: Request) -> Result, Infallible> { + println!( + "{} {} {}", + req.remote_addr(), + req.method(), + req.uri().path() + ); + Ok(req) +} + +fn router() -> Router { + Router::builder() + .data(State { _val: 100 }) + .middleware(Middleware::pre(logger)) + .get("/ready", ready_handler) + .get("/healthy", healthy_handler) + .build() + .unwrap() +} + +#[tokio::main] +async fn main() { + let router = router(); + let service = RouterService::new(router).unwrap(); + let addr = SocketAddr::from(([0, 0, 0, 0], 8082)); + let server = Server::bind(&addr).serve(service); + println!("Producer is running on: {}", addr); + if let Err(err) = server.await { + eprintln!("Server error: {}", err); + } +} diff --git a/helm/api-gateway/.helmignore b/helm/api-gateway/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/helm/api-gateway/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/helm/api-gateway/Chart.yaml b/helm/api-gateway/Chart.yaml new file mode 100644 index 0000000..86e78ca --- /dev/null +++ b/helm/api-gateway/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: api-gateway +description: Our API Gateway +type: application +version: 0.1.0 +appVersion: "0.1.0" diff --git a/helm/api-gateway/templates/NOTES.txt b/helm/api-gateway/templates/NOTES.txt new file mode 100644 index 0000000..4f97756 --- /dev/null +++ b/helm/api-gateway/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "api-gateway.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "api-gateway.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "api-gateway.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "api-gateway.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/helm/api-gateway/templates/_helpers.tpl b/helm/api-gateway/templates/_helpers.tpl new file mode 100644 index 0000000..6d5e146 --- /dev/null +++ b/helm/api-gateway/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "api-gateway.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "api-gateway.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "api-gateway.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "api-gateway.labels" -}} +helm.sh/chart: {{ include "api-gateway.chart" . }} +{{ include "api-gateway.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "api-gateway.selectorLabels" -}} +app.kubernetes.io/name: {{ include "api-gateway.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "api-gateway.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "api-gateway.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/helm/api-gateway/templates/deployment.yaml b/helm/api-gateway/templates/deployment.yaml new file mode 100644 index 0000000..88d0f0b --- /dev/null +++ b/helm/api-gateway/templates/deployment.yaml @@ -0,0 +1,63 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "api-gateway.fullname" . }} + labels: + {{- include "api-gateway.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "api-gateway.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "api-gateway.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "api-gateway.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 8082 + protocol: TCP + livenessProbe: + initialDelaySeconds: 5 + httpGet: + path: /healthy + port: 8082 + readinessProbe: + initialDelaySeconds: 5 + httpGet: + path: /ready + port: 8082 + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm/api-gateway/templates/hpa.yaml b/helm/api-gateway/templates/hpa.yaml new file mode 100644 index 0000000..c74ca05 --- /dev/null +++ b/helm/api-gateway/templates/hpa.yaml @@ -0,0 +1,28 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "api-gateway.fullname" . }} + labels: + {{- include "api-gateway.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "api-gateway.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/helm/api-gateway/templates/ingress.yaml b/helm/api-gateway/templates/ingress.yaml new file mode 100644 index 0000000..f1e02da --- /dev/null +++ b/helm/api-gateway/templates/ingress.yaml @@ -0,0 +1,61 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "api-gateway.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "api-gateway.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/helm/api-gateway/templates/service.yaml b/helm/api-gateway/templates/service.yaml new file mode 100644 index 0000000..545d6f0 --- /dev/null +++ b/helm/api-gateway/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "api-gateway.fullname" . }} + labels: + {{- include "api-gateway.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "api-gateway.selectorLabels" . | nindent 4 }} diff --git a/helm/api-gateway/templates/serviceaccount.yaml b/helm/api-gateway/templates/serviceaccount.yaml new file mode 100644 index 0000000..d707caa --- /dev/null +++ b/helm/api-gateway/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "api-gateway.serviceAccountName" . }} + labels: + {{- include "api-gateway.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm/api-gateway/templates/tests/test-connection.yaml b/helm/api-gateway/templates/tests/test-connection.yaml new file mode 100644 index 0000000..9867cce --- /dev/null +++ b/helm/api-gateway/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "api-gateway.fullname" . }}-test-connection" + labels: + {{- include "api-gateway.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "api-gateway.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/helm/api-gateway/values.yaml b/helm/api-gateway/values.yaml new file mode 100644 index 0000000..9eefd56 --- /dev/null +++ b/helm/api-gateway/values.yaml @@ -0,0 +1,87 @@ +# Default values for api-gateway. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + #name: api-gateway + #repository: 172.17.0.1:5000/api-gateway + repository: api-gateway + #pullPolicy: IfNotPresent + pullPolicy: Never + # Overrides the image tag whose default is the chart appVersion. + tag: "latest" + #tag: "" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "api-gateway" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 8082 + +ingress: + enabled: false + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} + diff --git a/helm/consumer/.helmignore b/helm/consumer/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/helm/consumer/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/helm/consumer/Chart.yaml b/helm/consumer/Chart.yaml new file mode 100644 index 0000000..74ace55 --- /dev/null +++ b/helm/consumer/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: consumer +description: Our Consumer +type: application +version: 0.1.0 +appVersion: "0.1.0" diff --git a/helm/consumer/templates/NOTES.txt b/helm/consumer/templates/NOTES.txt new file mode 100644 index 0000000..343a581 --- /dev/null +++ b/helm/consumer/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "consumer.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "consumer.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "consumer.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "consumer.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/helm/consumer/templates/_helpers.tpl b/helm/consumer/templates/_helpers.tpl new file mode 100644 index 0000000..ea72605 --- /dev/null +++ b/helm/consumer/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "consumer.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "consumer.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "consumer.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "consumer.labels" -}} +helm.sh/chart: {{ include "consumer.chart" . }} +{{ include "consumer.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "consumer.selectorLabels" -}} +app.kubernetes.io/name: {{ include "consumer.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "consumer.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "consumer.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/helm/consumer/templates/deployment.yaml b/helm/consumer/templates/deployment.yaml new file mode 100644 index 0000000..e92df76 --- /dev/null +++ b/helm/consumer/templates/deployment.yaml @@ -0,0 +1,63 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "consumer.fullname" . }} + labels: + {{- include "consumer.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "consumer.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "consumer.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "consumer.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 8082 + protocol: TCP + livenessProbe: + initialDelaySeconds: 5 + httpGet: + path: /healthy + port: 8082 + readinessProbe: + initialDelaySeconds: 5 + httpGet: + path: /ready + port: 8082 + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm/consumer/templates/hpa.yaml b/helm/consumer/templates/hpa.yaml new file mode 100644 index 0000000..e0e63ce --- /dev/null +++ b/helm/consumer/templates/hpa.yaml @@ -0,0 +1,28 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "consumer.fullname" . }} + labels: + {{- include "consumer.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "consumer.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/helm/consumer/templates/ingress.yaml b/helm/consumer/templates/ingress.yaml new file mode 100644 index 0000000..8747830 --- /dev/null +++ b/helm/consumer/templates/ingress.yaml @@ -0,0 +1,61 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "consumer.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "consumer.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/helm/consumer/templates/service.yaml b/helm/consumer/templates/service.yaml new file mode 100644 index 0000000..73ab5d2 --- /dev/null +++ b/helm/consumer/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "consumer.fullname" . }} + labels: + {{- include "consumer.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "consumer.selectorLabels" . | nindent 4 }} diff --git a/helm/consumer/templates/serviceaccount.yaml b/helm/consumer/templates/serviceaccount.yaml new file mode 100644 index 0000000..182f6ef --- /dev/null +++ b/helm/consumer/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "consumer.serviceAccountName" . }} + labels: + {{- include "consumer.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm/consumer/templates/tests/test-connection.yaml b/helm/consumer/templates/tests/test-connection.yaml new file mode 100644 index 0000000..89d780c --- /dev/null +++ b/helm/consumer/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "consumer.fullname" . }}-test-connection" + labels: + {{- include "consumer.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "consumer.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/helm/consumer/values.yaml b/helm/consumer/values.yaml new file mode 100644 index 0000000..be47397 --- /dev/null +++ b/helm/consumer/values.yaml @@ -0,0 +1,87 @@ +# Default values for consumer. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + #name: consumer + #repository: 172.17.0.1:5000/consumer + repository: consumer + #pullPolicy: IfNotPresent + pullPolicy: Never + # Overrides the image tag whose default is the chart appVersion. + tag: "latest" + #tag: "" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "consumer" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 8082 + +ingress: + enabled: false + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} + diff --git a/helm/producer/.helmignore b/helm/producer/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/helm/producer/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/helm/producer/Chart.yaml b/helm/producer/Chart.yaml new file mode 100644 index 0000000..12f23a9 --- /dev/null +++ b/helm/producer/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: producer +description: Our Producer +type: application +version: 0.1.0 +appVersion: "0.1.0" diff --git a/helm/producer/templates/NOTES.txt b/helm/producer/templates/NOTES.txt new file mode 100644 index 0000000..f3d8a68 --- /dev/null +++ b/helm/producer/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "producer.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "producer.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "producer.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "producer.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/helm/producer/templates/_helpers.tpl b/helm/producer/templates/_helpers.tpl new file mode 100644 index 0000000..fd0c426 --- /dev/null +++ b/helm/producer/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "producer.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "producer.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "producer.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "producer.labels" -}} +helm.sh/chart: {{ include "producer.chart" . }} +{{ include "producer.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "producer.selectorLabels" -}} +app.kubernetes.io/name: {{ include "producer.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "producer.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "producer.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/helm/producer/templates/deployment.yaml b/helm/producer/templates/deployment.yaml new file mode 100644 index 0000000..faf868e --- /dev/null +++ b/helm/producer/templates/deployment.yaml @@ -0,0 +1,63 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "producer.fullname" . }} + labels: + {{- include "producer.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "producer.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "producer.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "producer.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 8082 + protocol: TCP + livenessProbe: + initialDelaySeconds: 5 + httpGet: + path: /healthy + port: 8082 + readinessProbe: + initialDelaySeconds: 5 + httpGet: + path: /ready + port: 8082 + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm/producer/templates/hpa.yaml b/helm/producer/templates/hpa.yaml new file mode 100644 index 0000000..2cec351 --- /dev/null +++ b/helm/producer/templates/hpa.yaml @@ -0,0 +1,28 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "producer.fullname" . }} + labels: + {{- include "producer.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "producer.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/helm/producer/templates/ingress.yaml b/helm/producer/templates/ingress.yaml new file mode 100644 index 0000000..8b4890c --- /dev/null +++ b/helm/producer/templates/ingress.yaml @@ -0,0 +1,61 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "producer.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "producer.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/helm/producer/templates/service.yaml b/helm/producer/templates/service.yaml new file mode 100644 index 0000000..0b9ae69 --- /dev/null +++ b/helm/producer/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "producer.fullname" . }} + labels: + {{- include "producer.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "producer.selectorLabels" . | nindent 4 }} diff --git a/helm/producer/templates/serviceaccount.yaml b/helm/producer/templates/serviceaccount.yaml new file mode 100644 index 0000000..687c27f --- /dev/null +++ b/helm/producer/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "producer.serviceAccountName" . }} + labels: + {{- include "producer.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm/producer/templates/tests/test-connection.yaml b/helm/producer/templates/tests/test-connection.yaml new file mode 100644 index 0000000..bd28fab --- /dev/null +++ b/helm/producer/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "producer.fullname" . }}-test-connection" + labels: + {{- include "producer.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "producer.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/helm/producer/values.yaml b/helm/producer/values.yaml new file mode 100644 index 0000000..e727145 --- /dev/null +++ b/helm/producer/values.yaml @@ -0,0 +1,87 @@ +# Default values for producer. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + #name: producer + #repository: 172.17.0.1:5000/producer + repository: producer + #pullPolicy: IfNotPresent + pullPolicy: Never + # Overrides the image tag whose default is the chart appVersion. + tag: "latest" + #tag: "" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "producer" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 8082 + +ingress: + enabled: false + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} +