diff --git a/.dockerignore b/.dockerignore index 97d8388c98d..92a82b6670c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -17,10 +17,12 @@ !dump-data.sh !Dockerfile !frontend.sh +!index.sh !initial-data.sh !jest.config.js !package.json !refresh-data.sh +!test.sql.gz !yarn.lock # ...but DO ignore these others diff --git a/.github/workflows/helm.yml b/.github/workflows/helm.yml new file mode 100644 index 00000000000..f68c15bcde6 --- /dev/null +++ b/.github/workflows/helm.yml @@ -0,0 +1,21 @@ +name: Helm testing +on: + pull_request: + paths: + - helm/** + +jobs: + helm: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Install Helm + uses: azure/setup-helm@v4 + + - name: Install Helm dependencies + run: helm dependency update helm + + - name: Lint Helm chart + run: helm lint --strict -f helm/values.local.yaml helm diff --git a/.gitignore b/.gitignore index 9c126cc48c5..c6b8dc59498 100755 --- a/.gitignore +++ b/.gitignore @@ -145,6 +145,10 @@ cfgov/regulations3k/jinja2/regulations3k/regulations3k-service-worker.js.map cfgov/regulations3k/jinja2/regulations3k/workbox-*.js cfgov/regulations3k/jinja2/regulations3k/workbox-*.js.map +# Helm Charts # +helm/apache/ +helm/charts/ + # Apache # ########## cfgov/apache/logs diff --git a/.prettierignore b/.prettierignore index 4d66f089cd3..d0a0b33903a 100644 --- a/.prettierignore +++ b/.prettierignore @@ -6,6 +6,7 @@ cfgov/retirement_api/data/ cfgov/static_built/ **/fixtures/ collectstatic/ +helm/ # Ignore all HTML files: *.html diff --git a/Dockerfile b/Dockerfile index c34b623afa7..c6ae0fb3735 100644 --- a/Dockerfile +++ b/Dockerfile @@ -150,6 +150,10 @@ ENV ALLOWED_HOSTS '["*"]' # Copy the application code over COPY cfgov ./cfgov/ COPY static.in ./static.in/ +COPY refresh-data.sh . +COPY initial-data.sh . +COPY index.sh . +COPY test.sql.gz . # Copy our static build over from node-builder COPY --from=node-builder ${APP_HOME} ${APP_HOME} @@ -170,4 +174,4 @@ RUN chown -R ${USERNAME}:${USERNAME} ${APP_HOME} USER $USERNAME # Run Gunicorn -CMD gunicorn cfgov.wsgi:application -b :8000 +CMD gunicorn --reload cfgov.wsgi:application -b :8000 diff --git a/cfgov/apache/dockerfile b/cfgov/apache/dockerfile new file mode 100644 index 00000000000..f387b7306e9 --- /dev/null +++ b/cfgov/apache/dockerfile @@ -0,0 +1,24 @@ +FROM httpd:2.4-alpine + +# Copy the Apache configuration file +COPY ./conf /usr/local/apache2/conf +COPY ./conf.d /usr/local/apache2/conf.d +COPY ./conf.modules.d /usr/local/apache2/conf.modules.d + +# Add Apache ENV variables +ENV APACHE_PORT="80" +ENV APACHE_USER="www-data" +ENV APACHE_GROUP="www-data" +ENV APACHE_SERVER_ROOT="/usr/local/apache2/" +ENV APACHE_UPLOADS_F_ALIAS="/src/consumerfinance.gov/cfgov/f/" +ENV STATIC_PATH="/tmp" +ENV ERROR_LOG="/proc/self/fd/2" +ENV ACCESS_LOG="/proc/self/fd/1" +ENV LIMIT_REQUEST_BODY="0" +ENV APACHE_STATUS_ALLOW_FROM="127.0.0.1" +ENV APACHE_PROCESS_COUNT="4" +ENV CFGOV_APPLICATION_HOST="localhost" + +EXPOSE 80 + +CMD ["httpd-foreground"] \ No newline at end of file diff --git a/cfgov/cfgov/settings/base.py b/cfgov/cfgov/settings/base.py index 405f9e2229f..e6eeecae5a8 100644 --- a/cfgov/cfgov/settings/base.py +++ b/cfgov/cfgov/settings/base.py @@ -368,6 +368,7 @@ os.getenv("ES_USER", "admin"), os.getenv("ES_PASS", "admin"), ), + "use_ssl": True, "verify_certs": False, } } diff --git a/cfgov/cfgov/settings/local.py b/cfgov/cfgov/settings/local.py index 96e4ed856e5..ec6b82b9cc8 100644 --- a/cfgov/cfgov/settings/local.py +++ b/cfgov/cfgov/settings/local.py @@ -102,3 +102,6 @@ # If DEPLOY_ENVIRONMENT hasn't been set by the environment in base.py, # default it to local. DEPLOY_ENVIRONMENT = DEPLOY_ENVIRONMENT or "local" + +# Disable use_ssl for functional tests +OPENSEARCH_DSL["default"]["use_ssl"] = False \ No newline at end of file diff --git a/cfgov/cfgov/settings/test.py b/cfgov/cfgov/settings/test.py index 3ac5ec358a0..a50cf13331b 100644 --- a/cfgov/cfgov/settings/test.py +++ b/cfgov/cfgov/settings/test.py @@ -66,6 +66,10 @@ OPENSEARCH_DSL_AUTO_REFRESH = False OPENSEARCH_DSL_AUTOSYNC = False +# Disable `use_ssl` for unit tests +OPENSEARCH_DSL["default"]["use_ssl"] = False + + SKIP_DJANGO_MIGRATIONS = os.getenv("SKIP_DJANGO_MIGRATIONS", False) if SKIP_DJANGO_MIGRATIONS: for _db in DATABASES.values(): diff --git a/docs/running-k8s.md b/docs/running-k8s.md new file mode 100644 index 00000000000..88ad8836026 --- /dev/null +++ b/docs/running-k8s.md @@ -0,0 +1,113 @@ +# Running in Kubernetes + +This document provides a comprehensive guide to running in Kubernetes using Helm. + +## Table of Contents + +1. [Introduction](#introduction) +2. [Requirements](#requirements) +3. [Helm Chart](#helm-chart) + - [chart.yaml](#chartyaml) + - [values.yaml](#valuesyaml) + - [templates/NOTES.txt](#templatesnotestxt) +4. [Deploying the Helm Chart](#deploying-the-helm-chart) +5. [Uninstalling the Helm Deployment](#uninstalling-the-helm-deployment) + +## Introduction + +This guide explains the setup and deployment process for the CFPB Helm Chart, which includes PostgreSQL, OpenSearch, and the CFGOV application. The Helm chart is configured to support local deployments. + +## Requirements + +1. **Local Kubernetes Cluster**: + + - Ensure you have a local Kubernetes cluster running. We only support [Docker Desktop](https://www.docker.com/products/docker-desktop) and [colima](https://github.com/abiosoft/colima). + +2. **kubectl**: + + - Install `kubectl`, the Kubernetes command-line tool, by following the [official installation guide](https://kubernetes.io/docs/tasks/tools/install-kubectl/). + +3. **Helm**: + + - Install Helm, the package manager for Kubernetes, by following the [official installation guide](https://helm.sh/docs/intro/install/). + +## Helm Chart + +### chart.yaml + +In our `chart.yaml` we bring two Helm chart dependencies: + +- Opensearch +- Postgresql + +### values.yaml + +The `values.yaml` file contains our local configuration for the deployment of CFGOV. + +#### Init Containers + +We have two init containers: + +1. Busybox: + + - Starts up once we have our Postgresql and Opensearch Pods running. + - Serves as a pre-cursor to our cfgov-migrations container. + +2. cfgov-migrations + + - Runs the django migrations and the `refresh-data.sh` script and populates it with test data `test.sql.gz`. + - Indexes our database to Opensearch. + +#### Containers + +1. cfgov: + + - Uses the cfgov image, found in the repo's root `dockerfile` + - Serves up our Django application on Port 8000 of the cfgov pod + +2. cfgov-apache: + + - Built from `cfgov-apache` image built from the `apache/dockerfile` + - Serves as a webserver to proxy to our cfgov container + +### notes.txt + +Our [notes.txt](https://helm.sh/docs/chart_template_guide/notes_files/) is split to work for local deployments and deployments to production. + +```txt +{{- if .Values.localDeployment }} +{{ .Files.Get "notes/NOTES-local.txt" }} +{{- else }} +{{ .Files.Get "notes/NOTES-production.txt" }} +{{- end }} +``` + +## Deploying the CFGOV Helm Chart + +Deploying the CFGOV Helm chart is simple with the `helm-init.sh` script. + +To deploy the CFGOV Helm Chart you must: + +1. make sure you have a the cfgov and cfgov-apache image built +2. have a local K8s cluster running (docker desktop, colima) + +If these criteria are not met, the `helm-init.sh` script will not run. + +## Viewing the Helm Chart + +You can either user a K8s IDE ([lens](https://k8slens.dev/), [k9s](https://k9scli.io)) or manually portfward to view the application running. + +Review the output of your deployment for more information on manually portforwarding. + +| Pod | Container | Port | +| ---------- | ------------ | ---------------- | +| cfgov | cfgov | 8000 | +| cfgov | cfogv-apache | 80 | +| postgresql | postgresql | 5432 | +| opensearch | opensearch | 9200 (http) | +| | | 9300 (transport) | +| | | 9600 (metrics) | + +## Uninstall the CFGOV Helm Chart + +Running `helm-uninstall.sh` will uninstall the helm deploymennt of the CFGOV Helm Chart, as well as remove Persistent Volume Claims for the Open Search Pod and the Postgresql Pod. diff --git a/helm-init.sh b/helm-init.sh new file mode 100755 index 00000000000..c9c771cc6fe --- /dev/null +++ b/helm-init.sh @@ -0,0 +1,95 @@ +#!/bin/bash + +# Fail if any command fails. +set -e + +# Define color codes for pretty output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +OUTPUT_FILE=$(mktemp) + +# Check if helm is installed +if ! command -v helm &> /dev/null; then + echo -e "${RED}Helm could not be found. Please install it before running this script.${NC}" + exit 1 +fi + +# Check if kubectl is installed +if ! command -v kubectl &> /dev/null; then + echo -e "${RED}Kubectl could not be found. Please install it before running this script.${NC}" + exit 1 +fi + +if ! docker images -q --filter=reference='cfgov' | grep -q .; then + echo -e "${RED}Docker image 'cfgov' could not be found." + docker build . -t cfgov +fi + +if ! docker images -q --filter=reference='cfgov-apache' | grep -q .; then + echo -e "${RED}Docker image 'cfgov-apache' could not be found." + docker build ./cfgov/apache/. -t cfgov-apache +fi + +# Get the current Kubernetes cluster context +CLUSTER=$(kubectl config current-context) + +# Check if a Kubernetes cluster is configured +if [ -z "$CLUSTER" ]; then + echo -e "${RED}No Kubernetes cluster is currently configured. Please configure a cluster before running this script.${NC}" + exit 1 +fi + +echo -e "${GREEN}Current Kubernetes cluster: $CLUSTER${NC}" + +# Check if the current cluster is a local cluster (docker-desktop or colima) +if [[ "$CLUSTER" != "docker-desktop" && "$CLUSTER" != "colima" ]]; then + echo -e "${YELLOW}" + echo -e "This script is intended to be run on a local Kubernetes cluster (i.e. docker-desktop or colima)." + echo -e "Your current cluster is ${RED}$CLUSTER${YELLOW}." + echo -e "Please switch to docker-desktop or colima before running this script." + echo -e "${NC}" + echo -e "${RED}Exiting helm-init${NC}" + exit 1 +fi + +# Define the Helm directory +HELM_DIR="./helm" + + +# Check if the ./cfgov/apache directory exists +if [ -d "./cfgov/apache" ]; then + echo -e "${GREEN}Moving ./cfgov/apache to $HELM_DIR${NC}" + cp -r ./cfgov/apache $HELM_DIR +else + echo -e "${RED}Directory ./cfgov/apache does not exist.${NC}" + exit 1 +fi + +# Check if the ./helm/charts directory exists +if [ ! -d "./helm/charts" ]; then + echo -e "${GREEN}Building Helm charts...${NC}" + helm dependency update $HELM_DIR + helm dependency build $HELM_DIR +else + # Update Helm dependencies if SKIP_DEP_UPDATE is not set + if [ -z "$SKIP_DEP_UPDATE" ]; then + echo -e "${GREEN}Updating Helm dependencies...${NC}" + helm dependency update $HELM_DIR + fi +fi + +# Upgrade or install the Helm release +echo -e "${GREEN}Upgrading/Installing Helm release...${NC}" +helm upgrade --install cfgov $HELM_DIR -f $HELM_DIR/values.local.yaml >> $OUTPUT_FILE 2>&1 + +# Remove the copied apache directory +echo -e "${GREEN}Cleaning up...${NC}" +rm -r $HELM_DIR/apache + +echo -e "${GREEN}Helm initialization script completed successfully.${NC}" + +cat $OUTPUT_FILE +rm $OUTPUT_FILE diff --git a/helm-uninstall.sh b/helm-uninstall.sh new file mode 100755 index 00000000000..0a4f2075407 --- /dev/null +++ b/helm-uninstall.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +# Fail if any command fails. +set -e + +# Define color codes for pretty output +RED='\033[0;31m' +GREEN='\033[0;32m' +NC='\033[0m' # No Color + +# Check if helm is installed +if ! command -v helm &> /dev/null; then + echo -e "${RED}Helm could not be found. Please install it before running this script.${NC}" + exit 1 +fi + +# Define the release name and namespace +RELEASE_NAME="cfgov" +NAMESPACE="default" + +# Uninstall the Helm release +echo -e "${GREEN}Uninstalling Helm release ${RELEASE_NAME}...${NC}" +helm uninstall $RELEASE_NAME --namespace $NAMESPACE + +# Uninstall the PVCs for the PostgreSQL and OpenSearch pods +kubectl delete pvc data-cfgov-postgresql-0 --namespace $NAMESPACE +kubectl delete pvc opensearch-cluster-master-opensearch-cluster-master-0 --namespace $NAMESPACE + +# Check if the uninstall was successful +if [ $? -eq 0 ]; then + echo -e "${GREEN}Helm release ${RELEASE_NAME} uninstalled successfully.${NC}" +else + echo -e "${RED}Failed to uninstall Helm release ${RELEASE_NAME}.${NC}" + exit 1 +fi + +# Optionally, delete the namespace if it was created specifically for this release +# echo -e "${GREEN}Deleting namespace ${NAMESPACE}...${NC}" +# kubectl delete namespace $NAMESPACE + +echo -e "${GREEN}Helm uninstallation script completed successfully.${NC}" diff --git a/helm/.helmignore b/helm/.helmignore new file mode 100644 index 00000000000..0e8a0eb36f4 --- /dev/null +++ b/helm/.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/Chart.lock b/helm/Chart.lock new file mode 100644 index 00000000000..4a1f9399b9e --- /dev/null +++ b/helm/Chart.lock @@ -0,0 +1,9 @@ +dependencies: +- name: postgresql + repository: https://charts.bitnami.com/bitnami + version: 16.2.3 +- name: opensearch + repository: https://opensearch-project.github.io/helm-charts/ + version: 1.31.3 +digest: sha256:9a8a22b89a2f2c3c3d9ab178dc0b751c75d95e37215baf42a27b80982469efe0 +generated: "2024-12-10T15:35:50.786246-07:00" diff --git a/helm/Chart.yaml b/helm/Chart.yaml new file mode 100644 index 00000000000..a5635190897 --- /dev/null +++ b/helm/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: cfgov +description: A Helm chart for consumerfinance.gov +type: application +icon: https://www.consumerfinance.gov/static/icon.svg + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: '1.16.0' + +dependencies: + - name: postgresql + version: 16.2.3 + repository: https://charts.bitnami.com/bitnami + - name: opensearch + version: 1.31.3 + repository: https://opensearch-project.github.io/helm-charts/ diff --git a/helm/notes/NOTES-local.txt b/helm/notes/NOTES-local.txt new file mode 100644 index 00000000000..7112ea73a6c --- /dev/null +++ b/helm/notes/NOTES-local.txt @@ -0,0 +1,37 @@ +The following pods have been deployed to your cluster: + +PostgreSQL: + - Service Name: postgres + - Internal Port: 5432 + + To access PostgreSQL, run the following commands: + export POSTGRES_POD=$(kubectl get pods -l "statefulset.kubernetes.io/pod-name=cfgov-postgresql-0" -o jsonpath="{.items[0].metadata.name}") + export POSTGRES_PORT=$(kubectl get pod $POSTGRES_POD -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "PostgreSQL is running on internal port $POSTGRES_PORT" + kubectl port-forward $POSTGRES_POD 5432:$POSTGRES_PORT + Visit PostgreSQL at: http://127.0.0.1:5432 + +OpenSearch: + - Service Name: opensearch + - Http Internal Port: 9200 + - Transport Internal Port: 9300 + - Metrics Internal Port: 9600 + + To access OpenSearch Metrics, run the following commands: + export OPENSEARCH_POD=$(kubectl get pods -l "statefulset.kubernetes.io/pod-name=opensearch-cluster-master-0" -o jsonpath="{.items[0].metadata.name}") + export OPENSEARCH_PORT=$(kubectl get pod $OPENSEARCH_POD -o jsonpath="{.spec.containers[0].ports[2].containerPort}") + echo "OpenSearch Metrics is running on internal port $OPENSEARCH_PORT" + kubectl port-forward $OPENSEARCH_POD 9600:$OPENSEARCH_PORT + Visit OpenSearch at: http://127.0.0.1:9600 + +cfgov: + - Service Name: cfgov + - Application Port: 8000 + - Apache Port: 80 + + To access cfgov, run the following commands: + export CFGOV_POD=$(kubectl get pods -l "app.kubernetes.io/name=cfgov" -o jsonpath="{.items[0].metadata.name}") + export CFGOV_PORT=$(kubectl get pod $CFGOV_POD -o jsonpath="{.spec.containers[1].ports[0].containerPort}") + echo "cfgov is running on internal port $CFGOV_PORT" + kubectl port-forward $CFGOV_POD 8080:$CFGOV_PORT + Visit cfgov at: http://127.0.0.1:8080 diff --git a/helm/notes/NOTES-production.txt b/helm/notes/NOTES-production.txt new file mode 100644 index 00000000000..1ab4bed12ad --- /dev/null +++ b/helm/notes/NOTES-production.txt @@ -0,0 +1 @@ +Uh-oh, we're not in production yet. :P \ No newline at end of file diff --git a/helm/templates/NOTES.txt b/helm/templates/NOTES.txt new file mode 100644 index 00000000000..74fc207e47c --- /dev/null +++ b/helm/templates/NOTES.txt @@ -0,0 +1,5 @@ +{{- if .Values.localDeployment }} +{{ .Files.Get "notes/NOTES-local.txt" }} +{{- else }} +{{ .Files.Get "notes/NOTES-production.txt" }} +{{- end }} \ No newline at end of file diff --git a/helm/templates/_helpers.tpl b/helm/templates/_helpers.tpl new file mode 100644 index 00000000000..b815b91e607 --- /dev/null +++ b/helm/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "cfgov.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 "cfgov.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 "cfgov.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "cfgov.labels" -}} +cfgov.sh/chart: {{ include "cfgov.chart" . }} +{{ include "cfgov.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "cfgov.selectorLabels" -}} +app.kubernetes.io/name: {{ include "cfgov.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "cfgov.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "cfgov.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/helm/templates/deployment.yaml b/helm/templates/deployment.yaml new file mode 100644 index 00000000000..249e43bf49c --- /dev/null +++ b/helm/templates/deployment.yaml @@ -0,0 +1,94 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "cfgov.fullname" . }} + labels: + {{- include "cfgov.labels" . | nindent 4 }} + +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "cfgov.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "cfgov.labels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "cfgov.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + initContainers: + {{- range .Values.initContainers }} + - name: {{ .name }} + image: "{{ .image.repository }}:{{ .image.tag }}" + imagePullPolicy: {{ .image.pullPolicy }} + env: + {{- range .env }} + - name: {{ .name }} + value: {{ .value | quote }} + {{- end }} + command: + {{- range .command }} + - {{ . }} + {{- end }} + {{- end }} + containers: + {{- range .Values.containers }} + - name: {{ .name }} + image: "{{ .image.repository }}:{{ .image.tag }}" + imagePullPolicy: {{ .image.pullPolicy }} + ports: + - name: http + containerPort: {{ .port }} + protocol: TCP + {{- if .env }} + env: + {{- range .env }} + - name: {{ .name }} + value: {{ .value | quote }} + {{- end }} + {{- end }} + {{- if .command}} + command: + {{- range .command }} + - {{ . }} + {{- end }} + {{- end }} + {{- if .readinessProbe }} + readinessProbe: {{ toYaml .readinessProbe | nindent 12 }} + {{- end }} + {{- if .volumeMounts }} + volumeMounts: + {{- toYaml .volumeMounts | nindent 12 }} + {{- end }} + {{- end }} + {{- with .Values.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- 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/templates/hpa.yaml b/helm/templates/hpa.yaml new file mode 100644 index 00000000000..02850eec8fa --- /dev/null +++ b/helm/templates/hpa.yaml @@ -0,0 +1,32 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "cfgov.fullname" . }} + labels: + {{- include "cfgov.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "cfgov.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/helm/templates/ingress.yaml b/helm/templates/ingress.yaml new file mode 100644 index 00000000000..29d5a94f8e3 --- /dev/null +++ b/helm/templates/ingress.yaml @@ -0,0 +1,43 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "cfgov.fullname" . }} + labels: + {{- include "cfgov.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- with .Values.ingress.className }} + ingressClassName: {{ . }} + {{- 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 }} + {{- with .pathType }} + pathType: {{ . }} + {{- end }} + backend: + service: + name: {{ include "cfgov.fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/helm/templates/service.yaml b/helm/templates/service.yaml new file mode 100644 index 00000000000..2d955d2ac78 --- /dev/null +++ b/helm/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "cfgov.fullname" . }} + labels: + {{- include "cfgov.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "cfgov.selectorLabels" . | nindent 4 }} diff --git a/helm/templates/serviceaccount.yaml b/helm/templates/serviceaccount.yaml new file mode 100644 index 00000000000..d31dc7d94a5 --- /dev/null +++ b/helm/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "cfgov.serviceAccountName" . }} + labels: + {{- include "cfgov.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automount }} +{{- end }} diff --git a/helm/templates/tests/test-connection.yaml b/helm/templates/tests/test-connection.yaml new file mode 100644 index 00000000000..54b79849fea --- /dev/null +++ b/helm/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "cfgov.fullname" . }}-test-connection" + labels: + {{- include "cfgov.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "cfgov.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/helm/values.local.yaml b/helm/values.local.yaml new file mode 100644 index 00000000000..b9a501ec73c --- /dev/null +++ b/helm/values.local.yaml @@ -0,0 +1,195 @@ +# Default values for cfgov. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# This will set the replicaset count more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/ +replicaCount: 1 + +tests: + enabled: true + +localDeployment: true + +initContainers: + - name: wait-for-db + image: + repository: busybox + tag: "latest" + command: + - 'sh' + - '-c' + - 'until nc -z cfgov-postgresql 5432; do echo waiting for database; sleep 10; done;' + - 'until nc -z opensearch-cluster-master 9200; do echo waiting for opensearch; sleep 10; done;' + - name: cfgov-migrations + image: + repository: cfgov + pullPolicy: Never + tag: "latest" + env: + - name: DATABASE_URL + value: "postgres://cfpb:cfpb@cfgov-postgresql:5432/cfgov" + - name: SECRET_KEY + value: "cfgov" + - name: ES_HOST + value: "opensearch-cluster-master" + - name: ES_PORT + value: "9200" + command: + - 'sh' + - '-c' + - './refresh-data.sh test.sql.gz' + + +containers: + - name: cfgov + image: + repository: cfgov + pullPolicy: Never + tag: "latest" + port: 8000 + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 1 + failedThreshold: 3 + env: + - name: DATABASE_URL + value: "postgres://cfpb:cfpb@cfgov-postgresql:5432/cfgov" + - name: SECRET_KEY + value: "cfgov" + - name: ES_HOST + value: "opensearch-cluster-master" + - name: ES_PORT + value: "9200" + - name: cfgov-apache + image: + repository: cfgov-apache + pullPolicy: Never + tag: "latest" + port: 80 + +# This is for the secretes for pulling an image from a private repository more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ +imagePullSecrets: [] +# This is to override the chart name. +nameOverride: "" +fullnameOverride: "" + +# This section builds out the service account more information can be found here: https://kubernetes.io/docs/concepts/security/service-accounts/ +serviceAccount: + # Specifies whether a service account should be created + create: true + # Automatically mount a ServiceAccount's API credentials? + automount: 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: "" + +# This is for setting Kubernetes Annotations to a Pod. +# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ +# +# Always restart containers, even if the image tag hasn't changed. +# This is currently needed because the crawler image tag is always "main". +# https://v3.helm.sh/docs/howto/charts_tips_and_tricks/#automatically-roll-deployments +podAnnotations: {"rollme": "{{ randAlphaNum 5 | quote }}"} + +# This is for setting Kubernetes Labels to a Pod. +# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ +podLabels: {"app.kubernetes.io/name": "cfgov"} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +# This is for setting up a service more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/ +service: + # This sets the service type more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types + type: ClusterIP + # This sets the ports more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports + port: 80 + +# This block is for setting up the ingress for more information can be found here: https://kubernetes.io/docs/concepts/services-networking/ingress/ +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 + +# This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ +livenessProbe: + httpGet: + path: / + port: http + +# This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/ +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +# Additional volumeMounts on the output Deployment definition. +volumeMounts: [] +# - name: foo +# mountPath: "/etc/foo" +# readOnly: true + +nodeSelector: {} + +tolerations: [] + +affinity: {} + + +postgresql: + labels: + app: cfgov-postgresql + global: + postgresql: + auth: + database: cfgov + username: cfpb + password: cfpb + + +pgadmin4: + env: + email: test@domain.com + password: test + +opensearch: + replicas: 1 diff --git a/helm/values.yaml b/helm/values.yaml new file mode 100644 index 00000000000..33447f52ca2 --- /dev/null +++ b/helm/values.yaml @@ -0,0 +1,4 @@ +# This file is deliberately empty. +# In order to pass "helm lint", a values.yaml file must exist. +# Per https://helm.sh/docs/topics/charts/#values-files, +# "The default values file included inside of a chart must be named values.yaml." diff --git a/mkdocs.yml b/mkdocs.yml index 3edd2ef008a..fc7f67b0933 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -8,6 +8,7 @@ nav: - Running this Project: - Running in a Virtual Environment: running-virtualenv.md - Running in Docker: running-docker.md + - Running in Kubernetes: running-k8s.md - Running documentation site locally: running-docs.md - Debugging and Monitoring: debugging-monitoring.md - Artifacts and Deployment: deployment.md