From 42f722d23405c85a0900f0afc26853235a70284c Mon Sep 17 00:00:00 2001 From: kernel53 Date: Thu, 4 Sep 2025 20:39:56 +0000 Subject: [PATCH] Add Cloudkitty Rating Service to Genestack --- .github/workflows/helm-cloudkitty.yaml | 50 ++++++ .../cloudkitty/cloudkitty-helm-overrides.yaml | 142 ++++++++++++++++++ .../cloudkitty/aio/kustomization.yaml | 17 +++ .../base/cloudkitty-mariadb-database.yaml | 67 +++++++++ .../base/cloudkitty-rabbitmq-queue.yaml | 76 ++++++++++ .../cloudkitty/base/hpa-cloudkitty-api.yaml | 26 ++++ .../cloudkitty/base/kustomization.yaml | 8 + bin/create-secrets.sh | 31 ++++ bin/install-cloudkitty.sh | 41 +++++ bin/setup-openstack.sh | 2 + docs/genestack-components.md | 1 + .../listeners/cloudkitty-https.json | 27 ++++ .../custom-cloudkitty-gateway-route.yaml | 21 +++ scripts/hyperconverged-lab.sh | 1 + 14 files changed, 510 insertions(+) create mode 100644 .github/workflows/helm-cloudkitty.yaml create mode 100644 base-helm-configs/cloudkitty/cloudkitty-helm-overrides.yaml create mode 100644 base-kustomize/cloudkitty/aio/kustomization.yaml create mode 100644 base-kustomize/cloudkitty/base/cloudkitty-mariadb-database.yaml create mode 100644 base-kustomize/cloudkitty/base/cloudkitty-rabbitmq-queue.yaml create mode 100644 base-kustomize/cloudkitty/base/hpa-cloudkitty-api.yaml create mode 100644 base-kustomize/cloudkitty/base/kustomization.yaml create mode 100755 bin/install-cloudkitty.sh create mode 100644 etc/gateway-api/listeners/cloudkitty-https.json create mode 100644 etc/gateway-api/routes/custom-cloudkitty-gateway-route.yaml diff --git a/.github/workflows/helm-cloudkitty.yaml b/.github/workflows/helm-cloudkitty.yaml new file mode 100644 index 000000000..4622244ae --- /dev/null +++ b/.github/workflows/helm-cloudkitty.yaml @@ -0,0 +1,50 @@ +name: Helm GitHub Actions for Cloudkitty + +on: + pull_request: + paths: + - base-helm-configs/cloudkitty/** + - base-kustomize/cloudkitty/** + - .github/workflows/helm-cloudkitty.yaml +jobs: + helm: + strategy: + matrix: + overlays: + - base + - aio + name: Helm + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - uses: azure/setup-helm@v3 + with: + version: latest + token: "${{ secrets.GITHUB_TOKEN }}" + id: helm + - name: Kubectl Install + working-directory: /usr/local/bin/ + run: | + if [ ! -f /usr/local/bin/kubectl ]; then + curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" + chmod +x ./kubectl + fi + - name: Pull OSH repositories + run: | + helm repo add openstack-helm https://tarballs.opendev.org/openstack/openstack-helm + helm repo update + - name: Run Helm Template + run: | + ${{ steps.helm.outputs.helm-path }} template cloudkitty openstack-helm/cloudkitty \ + --namespace=openstack \ + --wait \ + --timeout 120m \ + -f ${{ github.workspace }}/base-helm-configs/cloudkitty/cloudkitty-helm-overrides.yaml \ + --post-renderer ${{ github.workspace }}/base-kustomize/kustomize.sh \ + --post-renderer-args cloudkitty/${{ matrix.overlays }} > /tmp/rendered.yaml + - name: Return helm Build + uses: actions/upload-artifact@v4 + with: + name: helm-cloudkitty-artifact-${{ matrix.overlays }} + path: /tmp/rendered.yaml diff --git a/base-helm-configs/cloudkitty/cloudkitty-helm-overrides.yaml b/base-helm-configs/cloudkitty/cloudkitty-helm-overrides.yaml new file mode 100644 index 000000000..b09a84793 --- /dev/null +++ b/base-helm-configs/cloudkitty/cloudkitty-helm-overrides.yaml @@ -0,0 +1,142 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +images: + tags: + db_init: ghcr.io/rackerlabs/genestack-images/heat:2024.1-latest + db_drop: ghcr.io/rackerlabs/genestack-images/heat:2024.1-latest + ks_endpoints: ghcr.io/rackerlabs/genestack-images/heat:2024.1-latest + ks_service: ghcr.io/rackerlabs/genestack-images/heat:2024.1-latest + ks_user: ghcr.io/rackerlabs/genestack-images/heat:2024.1-latest + cloudkitty_api: quay.io/airshipit/cloudkitty:2025.1-ubuntu_noble + cloudkitty_db_sync: quay.io/airshipit/cloudkitty:2025.1-ubuntu_noble + cloudkitty_processor: quay.io/airshipit/cloudkitty:2025.1-ubuntu_noble + cloudkitty_storage_init: quay.io/airshipit/cloudkitty:2025.1-ubuntu_noble + rabbit_init: docker.io/rabbitmq:3.13-management + dep_check: ghcr.io/rackerlabs/genestack-images/kubernetes-entrypoint:latest + pull_policy: "IfNotPresent" + +endpoints: + oslo_db: + hosts: + default: mariadb-cluster-primary + host_fqdn_override: + default: mariadb-cluster-primary.openstack.svc.cluster.local + oslo_messaging: + hosts: + default: rabbitmq-nodes + host_fqdn_override: + default: rabbitmq.openstack.svc.cluster.local + oslo_cache: + hosts: + default: memcached + host_fqdn_override: + default: memcached.openstack.svc.cluster.local + +dependencies: + static: + db_sync: + jobs: null + +# NOTE: (brew) requests cpu/mem values based on a three node +# hyperconverged lab (/scripts/hyperconverged-lab.sh). +# limit values based on defaults from the openstack-helm charts unless defined +pod: + lifecycle: + upgrades: + deployments: + revision_history: 3 + pod_replacement_strategy: RollingUpdate + rolling_update: + max_unavailable: 20% + max_surge: 3 + resources: + enabled: true + cloudkitty_api: + requests: + memory: "128Mi" + cpu: "100m" + limits: + memory: "1024Mi" + cpu: "2000m" + cloudkitty_processor: + requests: + memory: "128Mi" + cpu: "100m" + limits: + memory: "1024Mi" + cpu: "2000m" +conf: + cloudkitty: + DEFAULT: + log_config_append: /etc/cloudkitty/logging.conf + api_paste_config: /etc/cloudkitty/api-paste.ini + auth_strategy: keystone + debug: false + keystone_authtoken: + auth_type: password + username: cloudkitty + service_token_roles_required: true + service_token_roles: admin,rating,service + service_type: rating + database: + max_retries: -1 + collect: + collector: gnocchi + collector_gnocchi: + auth_section: keystone_authtoken + fetcher: + backend: gnocchi + fetcher_gnocchi: + auth_section: keystone_authtoken + output: + pipeline: osrf + basepath: /var/cloudkitty/reports + backend: cloudkitty.backend.file.FileBackend + storage: + backend: sqlalchemy + version: 1 + logging: + logger_root: + level: WARNING + handlers: + - stdout + logger_cloudkitty: + level: WARNING + handlers: + - stdout + qualname: cloudkitty + +manifests: + configmap_bin: true + configmap_etc: true + deployment_api: true + deployment_processor: true + ingress_api: false + job_bootstrap: false + job_ks_user: true + job_db_sync: true + job_db_init: false + job_db_drop: false + job_ks_endpoints: true + job_ks_service: true + job_rabbit_init: false + job_storage_init: true + pdb_api: true + network_policy: false + secret_db: true + secret_rabbitmq: true + secret_keystone: true + secret_registry: true + service_api: true + secret_ks_etc: true diff --git a/base-kustomize/cloudkitty/aio/kustomization.yaml b/base-kustomize/cloudkitty/aio/kustomization.yaml new file mode 100644 index 000000000..a21dc5834 --- /dev/null +++ b/base-kustomize/cloudkitty/aio/kustomization.yaml @@ -0,0 +1,17 @@ +--- +sortOptions: + order: fifo +resources: + - ../base + +patches: + - target: + kind: HorizontalPodAutoscaler + name: cloudkitty-api + patch: |- + - op: replace + path: /spec/minReplicas + value: 1 + - op: replace + path: /spec/maxReplicas + value: 1 diff --git a/base-kustomize/cloudkitty/base/cloudkitty-mariadb-database.yaml b/base-kustomize/cloudkitty/base/cloudkitty-mariadb-database.yaml new file mode 100644 index 000000000..374fc18d3 --- /dev/null +++ b/base-kustomize/cloudkitty/base/cloudkitty-mariadb-database.yaml @@ -0,0 +1,67 @@ +--- +apiVersion: k8s.mariadb.com/v1alpha1 +kind: Database +metadata: + name: cloudkitty + namespace: openstack + labels: + app.kubernetes.io/managed-by: "Helm" + annotations: + helm.sh/resource-policy: keep + meta.helm.sh/release-name: "cloudkitty" + meta.helm.sh/release-namespace: "openstack" +spec: + # If you want the database to be created with a different name than the resource name + # name: data-custom + mariaDbRef: + name: mariadb-cluster + characterSet: utf8 + collate: utf8_general_ci + retryInterval: 5s +--- +apiVersion: k8s.mariadb.com/v1alpha1 +kind: User +metadata: + name: cloudkitty + namespace: openstack + labels: + app.kubernetes.io/managed-by: "Helm" + annotations: + helm.sh/resource-policy: keep + meta.helm.sh/release-name: "cloudkitty" + meta.helm.sh/release-namespace: "openstack" +spec: + # If you want the user to be created with a different name than the resource name + # name: user-custom + mariaDbRef: + name: mariadb-cluster + passwordSecretKeyRef: + name: cloudkitty-db-password + key: password + # This field is immutable and defaults to 10, 0 means unlimited. + maxUserConnections: 0 + host: "%" + retryInterval: 5s +--- +apiVersion: k8s.mariadb.com/v1alpha1 +kind: Grant +metadata: + name: cloudkitty-grant + namespace: openstack + labels: + app.kubernetes.io/managed-by: "Helm" + annotations: + helm.sh/resource-policy: keep + meta.helm.sh/release-name: "cloudkitty" + meta.helm.sh/release-namespace: "openstack" +spec: + mariaDbRef: + name: mariadb-cluster + privileges: + - "ALL" + database: "cloudkitty" + table: "*" + username: cloudkitty + grantOption: true + host: "%" + retryInterval: 5s diff --git a/base-kustomize/cloudkitty/base/cloudkitty-rabbitmq-queue.yaml b/base-kustomize/cloudkitty/base/cloudkitty-rabbitmq-queue.yaml new file mode 100644 index 000000000..9de5f2eaa --- /dev/null +++ b/base-kustomize/cloudkitty/base/cloudkitty-rabbitmq-queue.yaml @@ -0,0 +1,76 @@ +--- +apiVersion: rabbitmq.com/v1beta1 +kind: User +metadata: + name: cloudkitty + namespace: openstack + annotations: + helm.sh/resource-policy: keep + app.kubernetes.io/managed-by: "Helm" + meta.helm.sh/release-name: "cloudkitty" + meta.helm.sh/release-namespace: "openstack" +spec: + tags: + - management # available tags are 'management', 'policymaker', 'monitoring' and 'administrator' + - policymaker + rabbitmqClusterReference: + name: rabbitmq # rabbitmqCluster must exist in the same namespace as this resource + namespace: openstack + importCredentialsSecret: + name: cloudkitty-rabbitmq-password +--- +apiVersion: rabbitmq.com/v1beta1 +kind: Vhost +metadata: + name: cloudkitty-vhost + namespace: openstack + annotations: + helm.sh/resource-policy: keep + meta.helm.sh/release-name: "cloudkitty" + meta.helm.sh/release-namespace: "openstack" +spec: + name: "cloudkitty" # vhost name; required and cannot be updated + defaultQueueType: quorum # default queue type for this vhost; require RabbitMQ version 3.11.12 or above + rabbitmqClusterReference: + name: rabbitmq # rabbitmqCluster must exist in the same namespace as this resource + namespace: openstack +--- +apiVersion: rabbitmq.com/v1beta1 +kind: Queue +metadata: + name: cloudkitty-queue + namespace: openstack + annotations: + helm.sh/resource-policy: keep + meta.helm.sh/release-name: "cloudkitty" + meta.helm.sh/release-namespace: "openstack" +spec: + name: cloudkitty-qq # name of the queue + vhost: "cloudkitty" # default to '/' if not provided + type: quorum # without providing a queue type, rabbitmq creates a classic queue + autoDelete: false + durable: true # seting 'durable' to false means this queue won't survive a server restart + rabbitmqClusterReference: + name: rabbitmq # rabbitmqCluster must exist in the same namespace as this resource + namespace: openstack +--- +apiVersion: rabbitmq.com/v1beta1 +kind: Permission +metadata: + name: cloudkitty-permission + namespace: openstack + annotations: + helm.sh/resource-policy: keep + meta.helm.sh/release-name: "cloudkitty" + meta.helm.sh/release-namespace: "openstack" +spec: + vhost: "cloudkitty" # name of a vhost + userReference: + name: "cloudkitty" # name of a user.rabbitmq.com in the same namespace; must specify either spec.userReference or spec.user + permissions: + write: ".*" + configure: ".*" + read: ".*" + rabbitmqClusterReference: + name: rabbitmq # rabbitmqCluster must exist in the same namespace as this resource + namespace: openstack diff --git a/base-kustomize/cloudkitty/base/hpa-cloudkitty-api.yaml b/base-kustomize/cloudkitty/base/hpa-cloudkitty-api.yaml new file mode 100644 index 000000000..9f0722374 --- /dev/null +++ b/base-kustomize/cloudkitty/base/hpa-cloudkitty-api.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: cloudkitty-api + namespace: openstack +spec: + maxReplicas: 9 + minReplicas: 2 + metrics: + - resource: + name: cpu + target: + averageUtilization: 80 + type: Utilization + type: Resource + - resource: + name: memory + target: + type: Utilization + averageUtilization: 80 + type: Resource + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: cloudkitty-api diff --git a/base-kustomize/cloudkitty/base/kustomization.yaml b/base-kustomize/cloudkitty/base/kustomization.yaml new file mode 100644 index 000000000..f93267285 --- /dev/null +++ b/base-kustomize/cloudkitty/base/kustomization.yaml @@ -0,0 +1,8 @@ +--- +sortOptions: + order: fifo +resources: + - cloudkitty-mariadb-database.yaml + - cloudkitty-rabbitmq-queue.yaml + - all.yaml + - hpa-cloudkitty-api.yaml diff --git a/bin/create-secrets.sh b/bin/create-secrets.sh index 08c0e0701..e4d5c200e 100755 --- a/bin/create-secrets.sh +++ b/bin/create-secrets.sh @@ -53,6 +53,9 @@ heat_stack_user_password=$(generate_password 32) cinder_rabbitmq_password=$(generate_password 64) cinder_db_password=$(generate_password 32) cinder_admin_password=$(generate_password 32) +cloudkitty_rabbitmq_password=$(generate_password 64) +cloudkitty_db_password=$(generate_password 32) +cloudkitty_admin_password=$(generate_password 32) metadata_shared_secret_password=$(generate_password 32) placement_db_password=$(generate_password 32) placement_admin_password=$(generate_password 32) @@ -268,6 +271,34 @@ data: --- apiVersion: v1 kind: Secret +metadata: + name: cloudkitty-rabbitmq-password + namespace: openstack +type: Opaque +data: + username: $(echo -n "cloudkitty" | base64) + password: $(echo -n $cloudkitty_rabbitmq_password | base64 -w0) +--- +apiVersion: v1 +kind: Secret +metadata: + name: cloudkitty-db-password + namespace: openstack +type: Opaque +data: + password: $(echo -n $cloudkitty_db_password | base64 -w0) +--- +apiVersion: v1 +kind: Secret +metadata: + name: cloudkitty-admin + namespace: openstack +type: Opaque +data: + password: $(echo -n $cloudkitty_admin_password | base64 -w0) +--- +apiVersion: v1 +kind: Secret metadata: name: metadata-shared-secret namespace: openstack diff --git a/bin/install-cloudkitty.sh b/bin/install-cloudkitty.sh new file mode 100755 index 000000000..1e4cb6140 --- /dev/null +++ b/bin/install-cloudkitty.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +GLOBAL_OVERRIDES_DIR="/etc/genestack/helm-configs/global_overrides" +SERVICE_CONFIG_DIR="/etc/genestack/helm-configs/cloudkitty" +BASE_OVERRIDES="/opt/genestack/base-helm-configs/cloudkitty/cloudkitty-helm-overrides.yaml" + +HELM_CMD="helm upgrade --install cloudkitty openstack-helm/cloudkitty --version 2025.1.1+64dfa3fe4 \ + --namespace=openstack \ + --timeout 10m" + +HELM_CMD+=" -f ${BASE_OVERRIDES}" + +for dir in "$GLOBAL_OVERRIDES_DIR" "$SERVICE_CONFIG_DIR"; do + if compgen -G "${dir}/*.yaml" > /dev/null; then + for yaml_file in "${dir}"/*.yaml; do + HELM_CMD+=" -f ${yaml_file}" + done + fi +done + +HELM_CMD+=" --set endpoints.identity.auth.admin.password=\"\$(kubectl --namespace openstack get secret keystone-admin -o jsonpath='{.data.password}' | base64 -d)\"" +HELM_CMD+=" --set endpoints.identity.auth.cloudkitty.password=\"\$(kubectl --namespace openstack get secret cloudkitty-admin -o jsonpath='{.data.password}' | base64 -d)\"" +HELM_CMD+=" --set endpoints.oslo_db.auth.admin.password=\"\$(kubectl --namespace openstack get secret mariadb -o jsonpath='{.data.root-password}' | base64 -d)\"" +HELM_CMD+=" --set endpoints.oslo_db.auth.cloudkitty.password=\"\$(kubectl --namespace openstack get secret cloudkitty-db-password -o jsonpath='{.data.password}' | base64 -d)\"" +HELM_CMD+=" --set endpoints.oslo_cache.auth.memcache_secret_key=\"\$(kubectl --namespace openstack get secret os-memcached -o jsonpath='{.data.memcache_secret_key}' | base64 -d)\"" +HELM_CMD+=" --set conf.cloudkitty.keystone_authtoken.memcache_secret_key=\"\$(kubectl --namespace openstack get secret os-memcached -o jsonpath='{.data.memcache_secret_key}' | base64 -d)\"" +HELM_CMD+=" --set conf.cloudkitty.database.slave_connection=\"mysql+pymysql://cloudkitty:\$(kubectl --namespace openstack get secret cloudkitty-db-password -o jsonpath='{.data.password}' | base64 -d)@mariadb-cluster-secondary.openstack.svc.cluster.local:3306/cloudkitty\"" +HELM_CMD+=" --set endpoints.oslo_messaging.auth.admin.password=\"\$(kubectl --namespace openstack get secret rabbitmq-default-user -o jsonpath='{.data.password}' | base64 -d)\"" +HELM_CMD+=" --set endpoints.oslo_messaging.auth.cloudkitty.password=\"\$(kubectl --namespace openstack get secret cloudkitty-rabbitmq-password -o jsonpath='{.data.password}' | base64 -d)\"" + +HELM_CMD+=" --post-renderer /etc/genestack/kustomize/kustomize.sh" +HELM_CMD+=" --post-renderer-args cloudkitty/overlay" + +helm repo add openstack-helm https://tarballs.opendev.org/openstack/openstack-helm +helm repo update + +HELM_CMD+=" $@" + +echo "Executing Helm command:" +echo "${HELM_CMD}" +eval "${HELM_CMD}" diff --git a/bin/setup-openstack.sh b/bin/setup-openstack.sh index 4016e3eeb..96fa6a353 100755 --- a/bin/setup-openstack.sh +++ b/bin/setup-openstack.sh @@ -60,6 +60,7 @@ EOF prompt_component "masakari" "Masakari (Instance High Availability)" prompt_component "ceilometer" "Ceilometer (Telemetry)" prompt_component "gnocchi" "Gnocchi (Time Series Database)" + prompt_component "cloudkitty" "Cloudkitty (Rating and Chargeback)" prompt_component "skyline" "Skyline (Dashboard)" fi @@ -80,6 +81,7 @@ is_component_enabled "octavia" && runTrackErator /opt/genestack/bin/install-octa is_component_enabled "masakari" && runTrackErator /opt/genestack/bin/install-masakari.sh is_component_enabled "ceilometer" && runTrackErator /opt/genestack/bin/install-ceilometer.sh is_component_enabled "gnocchi" && runTrackErator /opt/genestack/bin/install-gnocchi.sh +is_component_enabled "cloudkitty" && runTrackErator /opt/genestack/bin/install-cloudkitty.sh waitErator diff --git a/docs/genestack-components.md b/docs/genestack-components.md index 2b25135f9..d46793375 100644 --- a/docs/genestack-components.md +++ b/docs/genestack-components.md @@ -40,6 +40,7 @@ and largely deployed with Helm+Kustomize against the K8s API (v1.28 and up). | OpenStack | Ironic (Helm) | Optional | | OpenStack | Magnum (Helm) | Optional | | OpenStack | Masakari (Helm) | Optional | +| OpenStack | Cloudkitty (Helm) | Optional | | OpenStack | Blazar (Helm) | Optional | | OpenStack | metal3.io | Planned | | OpenStack | PostgreSQL (Operator) | Included | diff --git a/etc/gateway-api/listeners/cloudkitty-https.json b/etc/gateway-api/listeners/cloudkitty-https.json new file mode 100644 index 000000000..8b8d29b2a --- /dev/null +++ b/etc/gateway-api/listeners/cloudkitty-https.json @@ -0,0 +1,27 @@ +[ + { + "op": "add", + "path": "/spec/listeners/-", + "value": { + "name": "cloudkitty-https", + "port": 443, + "protocol": "HTTPS", + "hostname": "cloudkitty.your.domain.tld", + "allowedRoutes": { + "namespaces": { + "from": "All" + } + }, + "tls": { + "certificateRefs": [ + { + "group": "", + "kind": "Secret", + "name": "cloudkitty-gw-tls-secret" + } + ], + "mode": "Terminate" + } + } + } +] diff --git a/etc/gateway-api/routes/custom-cloudkitty-gateway-route.yaml b/etc/gateway-api/routes/custom-cloudkitty-gateway-route.yaml new file mode 100644 index 000000000..0c655c6da --- /dev/null +++ b/etc/gateway-api/routes/custom-cloudkitty-gateway-route.yaml @@ -0,0 +1,21 @@ +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: custom-cloudkitty-gateway-route + namespace: openstack + labels: + application: gateway-api + service: HTTPRoute + route: cloudkitty +spec: + parentRefs: + - name: flex-gateway + sectionName: cloudkitty-https + namespace: nginx-gateway + hostnames: + - "cloudkitty.your.domain.tld" + rules: + - backendRefs: + - name: cloudkitty-api + port: 8089 diff --git a/scripts/hyperconverged-lab.sh b/scripts/hyperconverged-lab.sh index 0c0d7b5d5..4141d3283 100755 --- a/scripts/hyperconverged-lab.sh +++ b/scripts/hyperconverged-lab.sh @@ -953,6 +953,7 @@ components: masakari: false ceilometer: false gnocchi: false + cloudkitty: false skyline: true EOF fi