Compare commits

..

15 Commits

Author SHA1 Message Date
Andrei Kvapil
3dc2f1dbde Release v0.35.4 (#1394)
This PR prepares the release `v0.35.4`.
2025-09-05 16:53:51 +02:00
cozystack-bot
2b3e9b88a2 Prepare release v0.35.4
Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com>
2025-09-05 12:31:08 +00:00
Andrei Kvapil
e55a51b6b7 [Backport release-0.35] [dx] Remove BUILDER and PLATFORM autodetection logic (#1392)
# Description
Backport of #1391 to `release-0.35`.
2025-09-05 12:55:06 +02:00
Andrei Kvapil
b041d37082 [dx] Remove BUILDER and PLATFORM autodetection logic
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
(cherry picked from commit 9f2b98d364)
2025-09-05 10:54:02 +00:00
Andrei Kvapil
2640ed5ce5 [Backport release-0.35] [seaweedfs] Fix connectivity issues for SeaweedFS (#1390)
# Description
Backport of #1386 to `release-0.35`.
2025-09-05 10:19:36 +02:00
Andrei Kvapil
d755f2cc8f [seaweedfs] Fix connectivity issues for SeaweedFS
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
(cherry picked from commit a291badbd4)
2025-09-05 08:19:06 +00:00
Andrei Kvapil
f1eead18ac [Backport release-0.35] [platform] Fix boolean override bug in Helm merge — ConfigMap values now correctly take precedence over bundle defaults (#1388)
# Description
Backport of #1385 to `release-0.35`.
2025-09-05 10:18:43 +02:00
Denis Yudin
e5872c9fd4 fix: use mergeOverwrite to properly override ConfigMap values
Fixes an issue where boolean values from bundle files were not being
properly overridden by values-<component> ConfigMap entries.

The Helm merge function has a bug when merging boolean values where
true from the first dict doesn't get overwritten by false from the
second dict. Using mergeOverwrite ensures ConfigMap values take
precedence over bundle values as intended.

Example:
- Bundle: autoDirectNodeRoutes: true
- ConfigMap values-cilium: autoDirectNodeRoutes: false
- Before: result was true (incorrect)
- After: result is false (correct)

This fix ensures that users can properly override any component
configuration using the values-<component> pattern in the cozystack
ConfigMap.

Signed-off-by: Denis Yudin <dyudin@intermedia.com>
(cherry picked from commit 52d749d46a)
2025-09-04 15:31:38 +00:00
Andrei Kvapil
5c93d5b50d [Backport release-0.35] [virtual-machine] Fix vm update hook (#1377)
# Description
Backport of #1376 to `release-0.35`.
2025-09-01 19:47:47 +02:00
Andrei Kvapil
52973ea2bb Fix vm update hook
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
(cherry picked from commit 15b213b38b)
2025-09-01 17:46:23 +00:00
Andrei Kvapil
b7bddbe107 [Backport release-0.35] Fix missing cozy-lib.resources.flatten template (#1375)
# Description
Backport of #1372 to `release-0.35`.
2025-09-01 13:11:11 +02:00
Andrei Kvapil
1a7589574c Fix missing cozy-lib.resources.flatten template
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
(cherry picked from commit fe869b97fd)
2025-09-01 11:09:58 +00:00
Andrei Kvapil
fdb8293b36 Release v0.35.3 (#1369)
This PR prepares the release `v0.35.3`.
2025-09-01 09:05:21 +02:00
cozystack-bot
b8fd151124 Prepare release v0.35.3
Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com>
2025-08-29 17:16:51 +00:00
Andrei Kvapil
78198cd7ee fix seaweedfs s3 liveness probe scheme (#1368)
<!-- Thank you for making a contribution! Here are some tips for you:
- Start the PR title with the [label] of Cozystack component:
- For system components: [platform], [system], [linstor], [cilium],
[kube-ovn], [dashboard], [cluster-api], etc.
- For managed apps: [apps], [tenant], [kubernetes], [postgres],
[virtual-machine] etc.
- For development and maintenance: [tests], [ci], [docs], [maintenance].
- If it's a work in progress, consider creating this PR as a draft.
- Don't hesistate to ask for opinion and review in the community chats,
even if it's still a draft.
- Add the label `backport` if it's a bugfix that needs to be backported
to a previous version.
-->

## What this PR does


### Release note

<!--  Write a release note:
- Explain what has changed internally and for users.
- Start with the same [label] as in the PR title
- Follow the guidelines at
https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md.
-->

```release-note
fix seaweedfs s3 liveness probe scheme
```

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Added a liveness check for the SeaweedFS S3 endpoint (HTTPS). This
improves health monitoring and enables automatic recovery if the service
becomes unresponsive, enhancing stability and uptime while reducing
manual intervention. Readiness behavior remains unchanged. No user
action required.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-29 19:12:42 +02:00
167 changed files with 664 additions and 8284 deletions

View File

@@ -1,7 +1,7 @@
name: Pull Request
env:
REGISTRY: ${{ vars.OCIR_REPO }}
REGISTRY: ${{ secrets.OCIR_REPO }}
on:
pull_request:
types: [opened, synchronize, reopened]
@@ -32,14 +32,7 @@ jobs:
fetch-depth: 0
fetch-tags: true
- name: Set up Docker config
run: |
if [ -d ~/.docker ]; then
cp -r ~/.docker "${{ runner.temp }}/.docker"
fi
- name: Login to GitHub Container Registry
if: ${{ !github.event.pull_request.head.repo.fork }}
uses: docker/login-action@v3
with:
username: ${{ secrets.OCIR_USER}}

View File

@@ -18,7 +18,6 @@ build: build-deps
make -C packages/system/cilium image
make -C packages/system/kubeovn image
make -C packages/system/kubeovn-webhook image
make -C packages/system/kubeovn-plunger image
make -C packages/system/dashboard image
make -C packages/system/metallb image
make -C packages/system/kamaji image

View File

@@ -38,7 +38,6 @@ import (
cozystackiov1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
"github.com/cozystack/cozystack/internal/controller"
"github.com/cozystack/cozystack/internal/controller/lineagelabeler"
"github.com/cozystack/cozystack/internal/telemetry"
helmv2 "github.com/fluxcd/helm-controller/api/v2"
@@ -68,7 +67,6 @@ func main() {
var telemetryEndpoint string
var telemetryInterval string
var cozystackVersion string
var watchResources string
var tlsOpts []func(*tls.Config)
flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+
"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.")
@@ -88,9 +86,6 @@ func main() {
"Interval between telemetry data collection (e.g. 15m, 1h)")
flag.StringVar(&cozystackVersion, "cozystack-version", "unknown",
"Version of Cozystack")
flag.StringVar(&watchResources, "watch-resources",
"v1/Pod,v1/Service,v1/Secret,v1/PersistentVolumeClaim",
"Comma-separated list of resources to watch in the form 'group/version/Kind'.")
opts := zap.Options{
Development: false,
}
@@ -219,15 +214,6 @@ func main() {
os.Exit(1)
}
if err := (&lineagelabeler.LineageLabelerReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
WatchResourceCSV: watchResources,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "LineageLabeler")
os.Exit(1)
}
// +kubebuilder:scaffold:builder
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {

View File

@@ -1,176 +0,0 @@
/*
Copyright 2025.
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.
*/
package main
import (
"crypto/tls"
"flag"
"os"
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/metrics"
"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"github.com/cozystack/cozystack/internal/controller/kubeovnplunger"
// +kubebuilder:scaffold:imports
)
var (
scheme = runtime.NewScheme()
setupLog = ctrl.Log.WithName("setup")
)
func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
// +kubebuilder:scaffold:scheme
}
func main() {
var metricsAddr string
var enableLeaderElection bool
var probeAddr string
var kubeOVNNamespace string
var ovnCentralName string
var secureMetrics bool
var enableHTTP2 bool
var disableTelemetry bool
var tlsOpts []func(*tls.Config)
flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+
"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.")
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
flag.StringVar(&kubeOVNNamespace, "kube-ovn-namespace", "cozy-kubeovn", "Namespace where kube-OVN is deployed.")
flag.StringVar(&ovnCentralName, "ovn-central-name", "ovn-central", "Ovn-central deployment name.")
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
"Enable leader election for controller manager. "+
"Enabling this will ensure there is only one active controller manager.")
flag.BoolVar(&secureMetrics, "metrics-secure", true,
"If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.")
flag.BoolVar(&enableHTTP2, "enable-http2", false,
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
flag.BoolVar(&disableTelemetry, "disable-telemetry", false,
"Disable telemetry collection")
opts := zap.Options{
Development: false,
}
opts.BindFlags(flag.CommandLine)
flag.Parse()
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
// if the enable-http2 flag is false (the default), http/2 should be disabled
// due to its vulnerabilities. More specifically, disabling http/2 will
// prevent from being vulnerable to the HTTP/2 Stream Cancellation and
// Rapid Reset CVEs. For more information see:
// - https://github.com/advisories/GHSA-qppj-fm5r-hxr3
// - https://github.com/advisories/GHSA-4374-p667-p6c8
disableHTTP2 := func(c *tls.Config) {
setupLog.Info("disabling http/2")
c.NextProtos = []string{"http/1.1"}
}
if !enableHTTP2 {
tlsOpts = append(tlsOpts, disableHTTP2)
}
webhookServer := webhook.NewServer(webhook.Options{
TLSOpts: tlsOpts,
})
// Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server.
// More info:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.1/pkg/metrics/server
// - https://book.kubebuilder.io/reference/metrics.html
metricsServerOptions := metricsserver.Options{
BindAddress: metricsAddr,
SecureServing: secureMetrics,
TLSOpts: tlsOpts,
}
if secureMetrics {
// FilterProvider is used to protect the metrics endpoint with authn/authz.
// These configurations ensure that only authorized users and service accounts
// can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info:
// https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.1/pkg/metrics/filters#WithAuthenticationAndAuthorization
metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization
// TODO(user): If CertDir, CertName, and KeyName are not specified, controller-runtime will automatically
// generate self-signed certificates for the metrics server. While convenient for development and testing,
// this setup is not recommended for production.
}
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
Metrics: metricsServerOptions,
WebhookServer: webhookServer,
HealthProbeBindAddress: probeAddr,
LeaderElection: enableLeaderElection,
LeaderElectionID: "29a0338b.cozystack.io",
// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
// when the Manager ends. This requires the binary to immediately end when the
// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
// speeds up voluntary leader transitions as the new leader don't have to wait
// LeaseDuration time first.
//
// In the default scaffold provided, the program ends immediately after
// the manager stops, so would be fine to enable this option. However,
// if you are doing or is intended to do any operation such as perform cleanups
// after the manager stops then its usage might be unsafe.
// LeaderElectionReleaseOnCancel: true,
})
if err != nil {
setupLog.Error(err, "unable to create manager")
os.Exit(1)
}
if err = (&kubeovnplunger.KubeOVNPlunger{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Registry: metrics.Registry,
}).SetupWithManager(mgr, kubeOVNNamespace, ovnCentralName); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "KubeOVNPlunger")
os.Exit(1)
}
// +kubebuilder:scaffold:builder
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up health check")
os.Exit(1)
}
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up ready check")
os.Exit(1)
}
setupLog.Info("starting manager")
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
}
}

View File

@@ -1,3 +1,4 @@
Release description.
<!--
https://github.com/cozystack/cozystack/releases/tag/v0..
@@ -14,7 +15,3 @@ https://github.com/cozystack/cozystack/releases/tag/v0..
## Documentation
## Development, Testing, and CI/CD
---
**Full Changelog**: **Full Changelog**: https://github.com/cozystack/cozystack/compare/v0.34.0...v0.35.0

View File

@@ -1,138 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.35.0
-->
## Feature Highlights
### External Application Sources in Cozystack
Cozystack now supports adding external application packages to the platform's application catalog.
Platform administrators can include custom or third-party applications alongside built-in ones, using the Cozystack API.
Adding an application requires making an application package, similar to the ones included in Cozystack
under [`packages/apps`](https://github.com/cozystack/cozystack/tree/main/packages/apps).
Using external packages is enabled by a new CustomResourceDefinition (CRD) called `CozystackResourceDefinition` and
a corresponding controller (reconciler) that watches for these resources.
Add your own managed application using the [documentation](https://cozystack.io/docs/applications/external/)
and an example at [github.com/cozystack/external-apps-example](https://github.com/cozystack/external-apps-example).
<!--
* [platform] Enable using external application packages by adding a `CozystackResourceDefinition` reconciler. Read the documentation on [adding external applications to Cozystack](https://cozystack.io/docs/applications/external/) to learn more. (@klinch0 in https://github.com/cozystack/cozystack/pull/1313)
* [cozystack-api] Provide an API for administrators to define custom managed applications alongside existing managed apps. (@klinch in https://github.com/cozystack/cozystack/pull/1230)
-->
### Cozystack API Improvements
This release brings significant improvements to the OpenAPI specs for all managed applications in Cozystack,
including databases, tenant Kubernetes, virtual machines, monitoring, and others.
These changes include more precise type definitions for fields that were previously defined only as generic objects,
and many fields now have value constraints.
Now many possible misconfigurations are detected immediately upon API request, and not later, with a failed deployment.
The Cozystack API now also displays default values for the application resources.
Most other fields now have sane default values when such values are possible.
All these changes pave the road for the new Cozystack UI, which is currently under development.
### Hetzner RobotLB Support
MetalLB, the default load balancer included in Cozystack, is built for bare metal and self-hosted VMs,
but is not supported on most cloud providers.
For example, Hetzner provides its own RobotLB service, which Cozystack now supports as an optional component.
Read the updated guide on [deploying Cozystack on Hetzner.com](https://cozystack.io/docs/install/providers/hetzner/)
to learn more and deploy your own Cozystack cluster on Hetzner.
### S3 Service: Dedicated Clusters and Monitoring
You can now deploy dedicated Cozystack clusters to run the S3 service, powered by SeaweedFS.
Thanks to the support for [integration with remote filer endpoints](https://cozystack.io/docs/operations/stretched/seaweedfs-multidc/),
you can connect your primary Cozystack cluster to use S3 storage in a dedicated cluster.
For security, platform administrators can now configure the SeaweedFS application with
a list of IP addresses or CIDR ranges that are allowed to access the filer service.
SeaweedFS has also been integrated into the monitoring stack and now has its own Grafana dashboard.
Together, these enhancements help Cozystack users build a more reliable, scalable, and observable S3 service.
### ClickHouse Keeper
The ClickHouse application now includes a ClickHouse Keeper service to improve cluster reliability and availability.
This component is deployed by default with every ClickHouse cluster.
Learn more in the [ClickHouse configuration reference](https://cozystack.io/docs/applications/clickhouse/#clickhouse-keeper-parameters).
## Major Features and Improvements
* [platform] Enable using external application packages by adding a `CozystackResourceDefinition` reconciler. Read the documentation on [adding external applications to Cozystack](https://cozystack.io/docs/applications/external/) to learn more. (@klinch0 in https://github.com/cozystack/cozystack/pull/1313)
* [cozystack-api, apps] Add default values, clear type definitions, value constraints and other improvements to the OpenAPI specs and READMEs by migrating to [cozyvalue-gen](https://github.com/cozystack/cozyvalues-gen). (@kvaps and @NickVolynkin in https://github.com/cozystack/cozystack/pull/1216, https://github.com/cozystack/cozystack/pull/1314, https://github.com/cozystack/cozystack/pull/1316, https://github.com/cozystack/cozystack/pull/1321, and https://github.com/cozystack/cozystack/pull/1333)
* [cozystack-api] Show default values from the OpenAPI spec in the application resources. (@kvaps in https://github.com/cozystack/cozystack/pull/1241)
* [cozystack-api] Provide an API for administrators to define custom managed applications alongside existing managed apps. (@klinch in https://github.com/cozystack/cozystack/pull/1230)
* [robotlb] Introduce the Hetzner RobotLB balancer. (@IvanHunters and @gwynbleidd2106 in https://github.com/cozystack/cozystack/pull/1233)
* [platform, robotlb] Autodetect if node ports should be assigned to load balancer services. (@lllamnyp in https://github.com/cozystack/cozystack/pull/1271)
* [seaweedfs] Enable [integration with remote filer endpoints](https://cozystack.io/docs/operations/stretched/seaweedfs-multidc/) by adding new `Client` topology. (@kvaps in https://github.com/cozystack/cozystack/pull/1239)
* [seaweedfs] Add support for whitelisting and exporting via nginx-ingress. Update cosi-driver. (@kvaps in https://github.com/cozystack/cozystack/pull/1277)
* [monitoring, seaweedfs] Add monitoring and Grafana dashboard for SeaweedFS. (@IvanHunters in https://github.com/cozystack/cozystack/pull/1285)
* [clickhouse] Add the ClickHouse Keeper component. (@klinch0 in https://github.com/cozystack/cozystack/pull/1298 and https://github.com/cozystack/cozystack/pull/1320)
## Security
* [keycloak] Store administrative passwords in the management cluster's secrets. (@IvanHunters in https://github.com/cozystack/cozystack/pull/1286)
* [keycloak] Update Keycloak client redirect URI to use HTTPS instead of HTTP. Enable `cookie-secure`. (@klinch0 in https://github.com/cozystack/cozystack/pull/1287)
## Fixes
* [platform] Introduce a fixed 2-second delay at the start of reconciliation for system and tenant Helm operations. (@klinch0 in https://github.com/cozystack/cozystack/pull/1343)
* [kubernetes] Add dependency for snapshot CRD and migration to the latest version. (@kvaps in https://github.com/cozystack/cozystack/pull/1275)
* [kubernetes] Fix regression in `volumesnapshotclass` installation from https://github.com/cozystack/cozystack/pull/1203. (@kvaps in https://github.com/cozystack/cozystack/pull/1238)
* [kubernetes] Resolve problems with pod names exceeding allowed length by shortening the name of volume snapshot CRD from `*-volumesnapshot-crd-for-tenant-k8s` to `*-vsnap-crd`. To apply this change, update each affected tenant Kubernetes cluster after updating Cozystack. (@klinch0 in https://github.com/cozystack/cozystack/pull/1284)
* [kubernetes] Disable VPA for VPA in tenant Kubernetes clusters. Tenant clusters have no need for this feature, and it was not designed to work in a tenant cluster, but was enabled by mistake. (@lllamnyp in https://github.com/cozystack/cozystack/pull/1301 and https://github.com/cozystack/cozystack/pull/1318)
* [kamaji] Fix broken migration jobs originating from missing environment variables in the in-tree build. (@lllamnyp in https://github.com/cozystack/cozystack/pull/1338)
* [etcd] Fix the `topologySpreadConstraints` for etcd. (@klinch0 in https://github.com/cozystack/cozystack/pull/1331)
* [tenant] Fix tenant network policy to allow traffic to additional tenant-related services across namespace hierarchies. (@klinch0 in https://github.com/cozystack/cozystack/pull/1232)
* [tenant, monitoring] Improve the reliability of tenant monitoring by increasing the timeout and number of retries. (@IvanHunters in https://github.com/cozystack/cozystack/pull/1294)
* [kubevirt] Fix building KubeVirt CCM image. (@kvaps in https://github.com/cozystack/cozystack/commit/3c7e256906e1dbb0f957dc3a205fa77a147d419d)
* [virtual-machine] Fix a regression with `optional=true` field. (@kvaps in https://github.com/cozystack/cozystack/commit/01053f7c3180d1bd045d7c5fb949984c2bdaf19d)
* [virtual-machine] Enable using custom `instanceType` values in `virtual-machine` and `vm-instance` by disabling field validation. (@lllamnyp in https://github.com/cozystack/cozystack/pull/1300, backported in https://github.com/cozystack/cozystack/pull/1303)
* [cozystack-api] Show correct `kind` values of `ApplicationList`. (@kvaps in https://github.com/cozystack/cozystack/pull/1290)
* [cozystack-api] Add missing roles to allow cozystack-controller to read Kubernetes deployments. (@klinch0 in https://github.com/cozystack/cozystack/pull/1342)
* [linstor] Update LINSTOR monitoring configuration to use label `controller_node` instead of `node`. (@kvaps in https://github.com/cozystack/cozystack/pull/1326 and https://github.com/cozystack/cozystack/pull/1335)
* [seaweedfs] Fix SeaweedFS volume configuration. Increase the volume size limit from 100MB to 30,000MB. (@kvaps in https://github.com/cozystack/cozystack/pull/1328)
* [seaweedfs] Disable proxy buffering and proxy request buffering for ingress. (@kvaps in https://github.com/cozystack/cozystack/pull/1330)
## Dependencies
* Update flux-operator to 0.28.0. (@kingdonb in https://github.com/cozystack/cozystack/pull/1315 and https://github.com/cozystack/cozystack/pull/1344)
## Documentation
* [Reimplement Cozystack Roadmap as a GitHub project](https://github.com/orgs/cozystack/projects/1). (@cozystack team)
* [SeaweedFS Multi-DC Configuration](https://cozystack.io/docs/operations/stretched/seaweedfs-multidc/). (@kvaps and @NickVolynkin in https://github.com/cozystack/website/pull/272)
* [Troubleshooting Kube-OVN](https://cozystack.io/docs/operations/troubleshooting/#kube-ovn-crash). (@kvaps and @NickVolynkin in https://github.com/cozystack/website/pull/273)
* [Removing failed nodes from Cozystack cluster](https://cozystack.io/docs/operations/troubleshooting/#remove-a-failed-node-from-the-cluster). (@kvaps and @NickVolynkin in https://github.com/cozystack/website/pull/273)
* [Installing Talos with `kexec`](https://cozystack.io/docs/talos/install/kexec/). (@kvaps and @NickVolynkin in https://github.com/cozystack/website/pull/268)
* [Rewrite Cozystack tutorial](https://cozystack.io/docs/getting-started/). (@NickVolynkin in https://github.com/cozystack/website/pull/262 and https://github.com/cozystack/website/pull/268)
* [How to install Cozystack in Hetzner](https://cozystack.io/docs/install/providers/hetzner/). (@NickVolynkin and @IvanHunters in https://github.com/cozystack/website/pull/280)
* [Adding External Applications to Cozystack Catalog](https://cozystack.io/docs/applications/external/). (@klinch0 and @NickVolynkin in https://github.com/cozystack/website/pull/283)
* [Creating and Using Named VM Images (Golden Images)](https://cozystack.io/docs/virtualization/vm-image/) (@NickVolynkin and @kvaps in https://github.com/cozystack/website/pull/276)
* [Creating Encrypted Storage on LINSTOR](https://cozystack.io/docs/operations/storage/disk-encryption/). (@kvaps and @NickVolynkin in https://github.com/cozystack/website/pull/282)
* [Adding and removing components on Cozystack installation using `bundle-enable` and `bundle-disable`](https://cozystack.io/docs/operations/bundles/#how-to-enable-and-disable-bundle-components) (@NickVolynkin in https://github.com/cozystack/website/pull/281)
* Restructure Cozystack documentation. Bring [managed Kubernetes](https://cozystack.io/docs/kubernetes/), [managed applications](https://cozystack.io/docs/applications/), [virtualization](https://cozystack.io/docs/virtualization/), and [networking](https://cozystack.io/docs/networking/) guides to the top level. (@NickVolynkin in https://github.com/cozystack/website/pull/266)
## Development, Testing, and CI/CD
* [tests] Add tests for S3 buckets. (@IvanHunters in https://github.com/cozystack/cozystack/pull/1283)
* [tests, ci] Simplify test discovery logic; run two k8s tests as separate jobs; delete Clickhouse application after a successful test. (@lllamnyp in https://github.com/cozystack/cozystack/pull/1236)
* [dx] When running `make` commands with `BUILDER` value specified, `PLATFORM` is optional. (@kvaps in https://github.com/cozystack/cozystack/pull/1288)
* [tests] Fix resource specification in virtual machine tests. (@IvanHunters in https://github.com/cozystack/cozystack/pull/1308)
* [tests] Increase available space for e2e tests. (@kvaps in https://github.com/cozystack/cozystack/commit/168a24ffdf1202b3bf2e7d2b5ef54b72b7403baf)
* [tests, ci] Continue application tests after one of them fails. (@NickVolynkin in https://github.com/cozystack/cozystack/commit/634b77edad6c32c101f3e5daea6a5ffc0c83d904)
* [ci] Use a subdomain of aenix.org for Nexus service in CI. (@lllamnyp in https://github.com/cozystack/cozystack/pull/1322)
---
**Full Changelog**: https://github.com/cozystack/cozystack/compare/v0.34.0...v0.35.0

View File

@@ -1,10 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.35.1
-->
## Fixes
* [cozy-lib] Fix malformed retrieval of `cozyConfig` in the cozy-lib template. (@lllamnyp in https://github.com/cozystack/cozystack/pull/1348)
**Full Changelog**: https://github.com/cozystack/cozystack/compare/v0.35.0...v0.35.1

View File

@@ -1,22 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.35.2
-->
## Features and Improvements
* [talos] Add LLDPD (`ghcr.io/siderolabs/lldpd`) as a built-in system extension, enabling LLDP-based neighbor discovery out of the box. (@lllamnyp in https://github.com/cozystack/cozystack/pull/1351 and https://github.com/cozystack/cozystack/pull/1360)
## Fixes
* [cozystack-api] Sanitize the OpenAPI v2 schema. (@kvaps in https://github.com/cozystack/cozystack/pull/1353)
* [seaweedfs] Fix a problem where S3 gateway would be moved to an external pod, resulting in authentication failure. (@kvaps in https://github.com/cozystack/cozystack/pull/1361)
## Dependencies
* Update LINSTOR to v1.31.3. (@kvaps in https://github.com/cozystack/cozystack/pull/1358)
* Update SeaweedFS to v3.96. (@kvaps in https://github.com/cozystack/cozystack/pull/1361)
**Full Changelog**: https://github.com/cozystack/cozystack/compare/v0.35.1...v0.35.2

3
go.mod
View File

@@ -59,7 +59,6 @@ require (
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
github.com/imdario/mergo v0.3.6 // indirect
@@ -67,11 +66,9 @@ require (
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/moby/spdystream v0.4.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.19.1 // indirect

6
go.sum
View File

@@ -2,8 +2,6 @@ github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cq
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -117,8 +115,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8=
github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -126,8 +122,6 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=

View File

@@ -48,7 +48,7 @@ kubectl get ns --no-headers | awk '$2 != "Active"' |
echo "Collecting helmreleases..."
kubectl get hr -A > $REPORT_DIR/kubernetes/helmreleases.txt 2>&1
kubectl get hr -A --no-headers | awk '$4 != "True"' | \
kubectl get hr -A | awk '$4 != "True"' | \
while read NAMESPACE NAME _; do
DIR=$REPORT_DIR/kubernetes/helmreleases/$NAMESPACE/$NAME
mkdir -p $DIR
@@ -105,7 +105,7 @@ kubectl get svc -A --no-headers | awk '$4 == "<pending>"' |
echo "Collecting pvcs..."
kubectl get pvc -A > $REPORT_DIR/kubernetes/pvcs.txt 2>&1
kubectl get pvc -A --no-headers | awk '$3 != "Bound"' |
kubectl get pvc -A | awk '$3 != "Bound"' |
while read NAMESPACE NAME _; do
DIR=$REPORT_DIR/kubernetes/pvc/$NAMESPACE/$NAME
mkdir -p $DIR

View File

@@ -18,8 +18,8 @@ spec:
EOF
sleep 5
kubectl -n tenant-test wait hr vm-disk-$name --timeout=5s --for=condition=ready
kubectl -n tenant-test wait dv vm-disk-$name --timeout=250s --for=condition=ready
kubectl -n tenant-test wait pvc vm-disk-$name --timeout=200s --for=jsonpath='{.status.phase}'=Bound
kubectl -n tenant-test wait dv vm-disk-$name --timeout=150s --for=condition=ready
kubectl -n tenant-test wait pvc vm-disk-$name --timeout=100s --for=jsonpath='{.status.phase}'=Bound
}
@test "Create a VM Instance" {

View File

@@ -123,7 +123,6 @@ EOF
@test "Configure Tenant and wait for applications" {
# Patch root tenant and wait for its releases
kubectl patch tenants/root -n tenant-root --type merge -p '{"spec":{"host":"example.org","ingress":true,"monitoring":true,"etcd":true,"isolated":true, "seaweedfs": true}}'
timeout 60 sh -ec 'until kubectl get hr -n tenant-root etcd ingress monitoring seaweedfs tenant-root >/dev/null 2>&1; do sleep 1; done'
@@ -140,7 +139,6 @@ EOF
kubectl wait hr/seaweedfs-system -n tenant-root --timeout=2m --for=condition=ready
fi
# Expose Cozystack services through ingress
kubectl patch configmap/cozystack -n cozy-system --type merge -p '{"data":{"expose-services":"api,dashboard,cdi-uploadproxy,vm-exportproxy,keycloak"}}'
@@ -152,7 +150,7 @@ EOF
kubectl wait sts/etcd -n tenant-root --for=jsonpath='{.status.readyReplicas}'=3 --timeout=5m
# VictoriaMetrics components
kubectl wait vmalert/vmalert-shortterm vmalertmanager/alertmanager -n tenant-root --for=jsonpath='{.status.updateStatus}'=operational --timeout=15m
kubectl wait vmalert/vmalert-shortterm vmalertmanager/alertmanager -n tenant-root --for=jsonpath='{.status.updateStatus}'=operational --timeout=5m
kubectl wait vlogs/generic -n tenant-root --for=jsonpath='{.status.updateStatus}'=operational --timeout=5m
kubectl wait vmcluster/shortterm vmcluster/longterm -n tenant-root --for=jsonpath='{.status.clusterStatus}'=operational --timeout=5m
@@ -189,22 +187,9 @@ spec:
ingress: false
isolated: true
monitoring: false
resourceQuotas:
cpu: "60"
memory: "128Gi"
storage: "100Gi"
resourceQuotas: {}
seaweedfs: false
EOF
kubectl wait hr/tenant-test -n tenant-root --timeout=1m --for=condition=ready
kubectl wait namespace tenant-test --timeout=20s --for=jsonpath='{.status.phase}'=Active
# Wait for ResourceQuota to appear and assert values
timeout 60 sh -ec 'until [ "$(kubectl get quota -n tenant-test --no-headers 2>/dev/null | wc -l)" -ge 1 ]; do sleep 1; done'
kubectl get quota -n tenant-test \
-o jsonpath='{range .items[*]}{.spec.hard.requests\.memory}{" "}{.spec.hard.requests\.storage}{"\n"}{end}' \
| grep -qx '137438953472 100Gi'
# Assert LimitRange defaults for containers
kubectl get limitrange -n tenant-test \
-o jsonpath='{range .items[*].spec.limits[*]}{.default.cpu}{" "}{.default.memory}{" "}{.defaultRequest.cpu}{" "}{.defaultRequest.memory}{"\n"}{end}' \
| grep -qx '250m 128Mi 25m 128Mi'
}

View File

@@ -2,25 +2,14 @@ package controller
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"sort"
"sync"
"time"
"github.com/cozystack/cozystack/internal/shared/crdmem"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
"github.com/go-logr/logr"
appsv1 "k8s.io/api/apps/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/log"
@@ -31,55 +20,85 @@ type CozystackResourceDefinitionReconciler struct {
client.Client
Scheme *runtime.Scheme
// Configurable debounce duration
Debounce time.Duration
// Internal state for debouncing
mu sync.Mutex
lastEvent time.Time
lastHandled time.Time
mem *crdmem.Memory
lastEvent time.Time // Time of last CRUD event on CozystackResourceDefinition
lastHandled time.Time // Last time the Deployment was actually restarted
}
// Reconcile handles the logic to restart the target Deployment only once,
// even if multiple events occur close together
func (r *CozystackResourceDefinitionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
log := log.FromContext(ctx)
crd := &cozyv1alpha1.CozystackResourceDefinition{}
err := r.Get(ctx, types.NamespacedName{Name: req.Name}, crd)
if err == nil {
if r.mem != nil {
r.mem.Upsert(crd)
}
r.mu.Lock()
r.lastEvent = time.Now()
r.mu.Unlock()
// Only respond to our target deployment
if req.Namespace != "cozy-system" || req.Name != "cozystack-api" {
return ctrl.Result{}, nil
}
if err != nil && !apierrors.IsNotFound(err) {
r.mu.Lock()
le := r.lastEvent
lh := r.lastHandled
debounce := r.Debounce
r.mu.Unlock()
if debounce <= 0 {
debounce = 5 * time.Second
}
// No events received yet — nothing to do
if le.IsZero() {
return ctrl.Result{}, nil
}
// Wait until the debounce duration has passed since the last event
if d := time.Since(le); d < debounce {
return ctrl.Result{RequeueAfter: debounce - d}, nil
}
// Already handled this event — skip restart
if !lh.Before(le) {
return ctrl.Result{}, nil
}
// Perform the restart by patching the deployment annotation
deploy := &appsv1.Deployment{}
if err := r.Get(ctx, types.NamespacedName{Namespace: "cozy-system", Name: "cozystack-api"}, deploy); err != nil {
log.Error(err, "Failed to get Deployment cozy-system/cozystack-api")
return ctrl.Result{}, client.IgnoreNotFound(err)
}
patch := client.MergeFrom(deploy.DeepCopy())
if deploy.Spec.Template.Annotations == nil {
deploy.Spec.Template.Annotations = make(map[string]string)
}
deploy.Spec.Template.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)
if err := r.Patch(ctx, deploy, patch); err != nil {
log.Error(err, "Failed to patch Deployment annotation")
return ctrl.Result{}, err
}
if apierrors.IsNotFound(err) && r.mem != nil {
r.mem.Delete(req.Name)
}
if req.Namespace == "cozy-system" && req.Name == "cozystack-api" {
return r.debouncedRestart(ctx, logger)
}
// Mark this event as handled
r.mu.Lock()
r.lastHandled = le
r.mu.Unlock()
log.Info("Deployment cozy-system/cozystack-api successfully restarted")
return ctrl.Result{}, nil
}
// SetupWithManager configures how the controller listens to events
func (r *CozystackResourceDefinitionReconciler) SetupWithManager(mgr ctrl.Manager) error {
if r.Debounce == 0 {
r.Debounce = 5 * time.Second
}
if r.mem == nil {
r.mem = crdmem.Global()
}
if err := r.mem.EnsurePrimingWithManager(mgr); err != nil {
return err
}
return ctrl.NewControllerManagedBy(mgr).
Named("cozystackresource-controller").
For(&cozyv1alpha1.CozystackResourceDefinition{}, builder.WithPredicates()).
Named("cozystack-restart-controller").
Watches(
&cozyv1alpha1.CozystackResourceDefinition{},
handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
@@ -96,88 +115,3 @@ func (r *CozystackResourceDefinitionReconciler) SetupWithManager(mgr ctrl.Manage
).
Complete(r)
}
type crdHashView struct {
Name string `json:"name"`
Spec cozyv1alpha1.CozystackResourceDefinitionSpec `json:"spec"`
}
func (r *CozystackResourceDefinitionReconciler) computeConfigHash() (string, error) {
if r.mem == nil {
return "", nil
}
snapshot := r.mem.Snapshot()
sort.Slice(snapshot, func(i, j int) bool { return snapshot[i].Name < snapshot[j].Name })
views := make([]crdHashView, 0, len(snapshot))
for i := range snapshot {
views = append(views, crdHashView{
Name: snapshot[i].Name,
Spec: snapshot[i].Spec,
})
}
b, err := json.Marshal(views)
if err != nil {
return "", err
}
sum := sha256.Sum256(b)
return hex.EncodeToString(sum[:]), nil
}
func (r *CozystackResourceDefinitionReconciler) debouncedRestart(ctx context.Context, logger logr.Logger) (ctrl.Result, error) {
r.mu.Lock()
le := r.lastEvent
lh := r.lastHandled
debounce := r.Debounce
r.mu.Unlock()
if debounce <= 0 {
debounce = 5 * time.Second
}
if le.IsZero() {
return ctrl.Result{}, nil
}
if d := time.Since(le); d < debounce {
return ctrl.Result{RequeueAfter: debounce - d}, nil
}
if !lh.Before(le) {
return ctrl.Result{}, nil
}
newHash, err := r.computeConfigHash()
if err != nil {
return ctrl.Result{}, err
}
deploy := &appsv1.Deployment{}
if err := r.Get(ctx, types.NamespacedName{Namespace: "cozy-system", Name: "cozystack-api"}, deploy); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
if deploy.Spec.Template.Annotations == nil {
deploy.Spec.Template.Annotations = map[string]string{}
}
oldHash := deploy.Spec.Template.Annotations["cozystack.io/config-hash"]
if oldHash == newHash && oldHash != "" {
r.mu.Lock()
r.lastHandled = le
r.mu.Unlock()
logger.Info("No changes in CRD config; skipping restart", "hash", newHash)
return ctrl.Result{}, nil
}
patch := client.MergeFrom(deploy.DeepCopy())
deploy.Spec.Template.Annotations["cozystack.io/config-hash"] = newHash
if err := r.Patch(ctx, deploy, patch); err != nil {
return ctrl.Result{}, err
}
r.mu.Lock()
r.lastHandled = le
r.mu.Unlock()
logger.Info("Updated cozystack-api podTemplate config-hash; rollout triggered",
"old", oldHash, "new", newHash)
return ctrl.Result{}, nil
}

View File

@@ -1,280 +0,0 @@
package kubeovnplunger
import (
"bytes"
"context"
"fmt"
"io"
"strings"
"time"
"github.com/cozystack/cozystack/internal/sse"
"github.com/cozystack/cozystack/pkg/ovnstatus"
"github.com/prometheus/client_golang/prometheus"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/remotecommand"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
)
var (
srv *sse.Server
)
const (
rescanInterval = 1 * time.Minute
)
// KubeOVNPlunger watches the ovn-central cluster members
type KubeOVNPlunger struct {
client.Client
Scheme *runtime.Scheme
ClientSet kubernetes.Interface
REST *rest.Config
Registry prometheus.Registerer
metrics metrics
lastLeader map[string]string
seenCIDs map[string]map[string]struct{}
}
// Reconcile runs the checks on the ovn-central members to see if their views of the cluster are consistent
func (r *KubeOVNPlunger) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
l := log.FromContext(ctx)
deploy := &appsv1.Deployment{}
if err := r.Get(ctx, req.NamespacedName, deploy); err != nil {
return ctrl.Result{}, err
}
iphints := map[string]string{}
for _, env := range deploy.Spec.Template.Spec.Containers[0].Env {
if env.Name != "NODE_IPS" {
continue
}
for _, ip := range strings.Split(env.Value, ",") {
iphints[ip] = ""
}
break
}
if len(iphints) == 0 {
l.Info("WARNING: running without IP hints, some error conditions cannot be detected")
}
pods := &corev1.PodList{}
if err := r.List(ctx, pods, client.InNamespace(req.Namespace), client.MatchingLabels(map[string]string{"app": req.Name})); err != nil {
return ctrl.Result{}, fmt.Errorf("list ovn-central pods: %w", err)
}
nbmv := make([]ovnstatus.MemberView, 0, len(pods.Items))
sbmv := make([]ovnstatus.MemberView, 0, len(pods.Items))
nbSnaps := make([]ovnstatus.HealthSnapshot, 0, len(pods.Items))
sbSnaps := make([]ovnstatus.HealthSnapshot, 0, len(pods.Items))
// TODO: get real iphints
for i := range pods.Items {
o := ovnstatus.OVNClient{}
o.ApplyDefaults()
o.Runner = func(ctx context.Context, bin string, args ...string) (string, error) {
cmd := append([]string{bin}, args...)
eo := ExecOptions{
Namespace: req.Namespace,
Pod: pods.Items[i].Name,
Container: pods.Items[i].Spec.Containers[0].Name,
Command: cmd,
}
res, err := r.ExecPod(ctx, eo)
if err != nil {
return "", err
}
return res.Stdout, nil
}
nb, sb, err1, err2 := o.HealthBoth(ctx)
if err1 != nil || err2 != nil {
l.Error(fmt.Errorf("health check failed: nb=%w, sb=%w", err1, err2), "pod", pods.Items[i].Name)
continue
}
nbSnaps = append(nbSnaps, nb)
sbSnaps = append(sbSnaps, sb)
nbmv = append(nbmv, ovnstatus.BuildMemberView(nb))
sbmv = append(sbmv, ovnstatus.BuildMemberView(sb))
}
r.recordAndPruneCIDs("nb", cidFromSnaps(nbSnaps))
r.recordAndPruneCIDs("sb", cidFromSnaps(sbSnaps))
nbmv = ovnstatus.NormalizeViews(nbmv)
sbmv = ovnstatus.NormalizeViews(sbmv)
nbecv := ovnstatus.AnalyzeConsensusWithIPHints(nbmv, &ovnstatus.Hints{ExpectedIPs: iphints})
sbecv := ovnstatus.AnalyzeConsensusWithIPHints(sbmv, &ovnstatus.Hints{ExpectedIPs: iphints})
expected := len(iphints)
r.WriteClusterMetrics("nb", nbSnaps, nbecv, expected)
r.WriteClusterMetrics("sb", sbSnaps, sbecv, expected)
r.WriteMemberMetrics("nb", nbSnaps, nbmv, nbecv)
r.WriteMemberMetrics("sb", sbSnaps, sbmv, sbecv)
srv.Publish(nbecv.PrettyString() + sbecv.PrettyString())
return ctrl.Result{}, nil
}
// SetupWithManager attaches a generic ticker to trigger a reconcile every <interval> seconds
func (r *KubeOVNPlunger) SetupWithManager(mgr ctrl.Manager, kubeOVNNamespace, appName string) error {
r.REST = rest.CopyConfig(mgr.GetConfig())
cs, err := kubernetes.NewForConfig(r.REST)
if err != nil {
return fmt.Errorf("build clientset: %w", err)
}
r.ClientSet = cs
ch := make(chan event.GenericEvent, 10)
mapFunc := func(context.Context, client.Object) []reconcile.Request {
return []reconcile.Request{{
NamespacedName: types.NamespacedName{Namespace: kubeOVNNamespace, Name: appName},
}}
}
mapper := handler.EnqueueRequestsFromMapFunc(mapFunc)
srv = sse.New(sse.Options{
Addr: ":18080",
AllowCORS: true,
})
r.initMetrics()
r.lastLeader = make(map[string]string)
r.seenCIDs = map[string]map[string]struct{}{"nb": {}, "sb": {}}
if err := ctrl.NewControllerManagedBy(mgr).
Named("kubeovnplunger").
WatchesRawSource(source.Channel(ch, mapper)).
Complete(r); err != nil {
return err
}
_ = mgr.Add(manager.RunnableFunc(func(ctx context.Context) error {
go srv.ListenAndServe()
<-ctx.Done()
_ = srv.Shutdown(context.Background())
return nil
}))
return mgr.Add(manager.RunnableFunc(func(ctx context.Context) error {
ticker := time.NewTicker(rescanInterval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return nil
case <-ticker.C:
ch <- event.GenericEvent{
Object: &metav1.PartialObjectMetadata{
ObjectMeta: metav1.ObjectMeta{
Namespace: kubeOVNNamespace,
Name: appName,
},
},
}
}
}
}))
}
type ExecOptions struct {
Namespace string
Pod string
Container string
Command []string // e.g. []string{"sh", "-c", "echo hi"}
Stdin io.Reader // optional
TTY bool // if true, stderr is merged into stdout
Timeout time.Duration // optional overall timeout
}
type ExecResult struct {
Stdout string
Stderr string
ExitCode *int // nil if not determinable
}
// ExecPod runs a command in a pod and returns stdout/stderr/exit code.
func (r *KubeOVNPlunger) ExecPod(ctx context.Context, opts ExecOptions) (*ExecResult, error) {
if opts.Namespace == "" || opts.Pod == "" || opts.Container == "" {
return nil, fmt.Errorf("namespace, pod, and container are required")
}
req := r.ClientSet.CoreV1().RESTClient().
Post().
Resource("pods").
Namespace(opts.Namespace).
Name(opts.Pod).
SubResource("exec").
VersionedParams(&corev1.PodExecOptions{
Container: opts.Container,
Command: opts.Command,
Stdin: opts.Stdin != nil,
Stdout: true,
Stderr: !opts.TTY,
TTY: opts.TTY,
}, scheme.ParameterCodec)
exec, err := remotecommand.NewSPDYExecutor(r.REST, "POST", req.URL())
if err != nil {
return nil, fmt.Errorf("spdy executor: %w", err)
}
var stdout, stderr bytes.Buffer
streamCtx := ctx
if opts.Timeout > 0 {
var cancel context.CancelFunc
streamCtx, cancel = context.WithTimeout(ctx, opts.Timeout)
defer cancel()
}
streamErr := exec.StreamWithContext(streamCtx, remotecommand.StreamOptions{
Stdin: opts.Stdin,
Stdout: &stdout,
Stderr: &stderr,
Tty: opts.TTY,
})
res := &ExecResult{Stdout: stdout.String(), Stderr: stderr.String()}
if streamErr != nil {
// Try to surface exit code instead of treating all failures as transport errors
type exitCoder interface{ ExitStatus() int }
if ec, ok := streamErr.(exitCoder); ok {
code := ec.ExitStatus()
res.ExitCode = &code
return res, nil
}
return res, fmt.Errorf("exec stream: %w", streamErr)
}
zero := 0
res.ExitCode = &zero
return res, nil
}
func (r *KubeOVNPlunger) recordAndPruneCIDs(db, currentCID string) {
// Mark current as seen
if r.seenCIDs[db] == nil {
r.seenCIDs[db] = map[string]struct{}{}
}
if currentCID != "" {
r.seenCIDs[db][currentCID] = struct{}{}
}
// Build a set of "still active" CIDs this cycle (could be none if you failed to collect)
active := map[string]struct{}{}
if currentCID != "" {
active[currentCID] = struct{}{}
}
// Any seen CID that isn't active now is stale -> delete all its series
for cid := range r.seenCIDs[db] {
if _, ok := active[cid]; ok {
continue
}
r.deleteAllFor(db, cid)
delete(r.seenCIDs[db], cid)
}
}

View File

@@ -1,34 +0,0 @@
package kubeovnplunger
import (
"context"
"testing"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)
var testPlunger *KubeOVNPlunger
func init() {
scheme := runtime.NewScheme()
cfg := config.GetConfigOrDie()
c, _ := client.New(cfg, client.Options{})
cs, _ := kubernetes.NewForConfig(cfg)
testPlunger = &KubeOVNPlunger{
Client: c,
Scheme: scheme,
ClientSet: cs,
REST: cfg,
}
}
func TestPlungerGetsStatuses(t *testing.T) {
_, err := testPlunger.Reconcile(context.Background(), ctrl.Request{})
if err != nil {
t.Errorf("error should be nil but it's %s", err)
}
}

View File

@@ -1,423 +0,0 @@
package kubeovnplunger
import (
"time"
"github.com/cozystack/cozystack/pkg/ovnstatus"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
type metrics struct {
// --- Core cluster health (per DB/cid) ---
clusterQuorum *prometheus.GaugeVec // 1/0
allAgree *prometheus.GaugeVec // 1/0
membersExpected *prometheus.GaugeVec
membersObserved *prometheus.GaugeVec
ipsExpected *prometheus.GaugeVec
ipsObserved *prometheus.GaugeVec
excessMembers *prometheus.GaugeVec
missingMembers *prometheus.GaugeVec
unexpectedIPsCount *prometheus.GaugeVec
missingExpectedIPsCount *prometheus.GaugeVec
ipConflictsCount *prometheus.GaugeVec
sidAddrDisagreements *prometheus.GaugeVec
// --- Consensus summary (per DB/cid) ---
consensusMajoritySize *prometheus.GaugeVec
consensusMinoritySize *prometheus.GaugeVec
consensusDiffsTotal *prometheus.GaugeVec
// --- Detail exports (sparse, keyed by IP/SID) ---
unexpectedIPGauge *prometheus.GaugeVec // {db,cid,ip} -> 1
missingExpectedIPGauge *prometheus.GaugeVec // {db,cid,ip} -> 1
ipConflictGauge *prometheus.GaugeVec // {db,cid,ip} -> count(sids)
suspectStaleGauge *prometheus.GaugeVec // {db,cid,sid} -> 1
// --- Per-member liveness/freshness (per DB/cid/sid[/ip]) ---
memberConnected *prometheus.GaugeVec // {db,cid,sid,ip}
memberLeader *prometheus.GaugeVec // {db,cid,sid}
memberLastMsgMs *prometheus.GaugeVec // {db,cid,sid}
memberIndex *prometheus.GaugeVec // {db,cid,sid}
memberIndexGap *prometheus.GaugeVec // {db,cid,sid}
memberReporter *prometheus.GaugeVec // {db,cid,sid}
memberMissingReporter *prometheus.GaugeVec // {db,cid,sid}
// --- Ops/housekeeping ---
leaderTransitionsTotal *prometheus.CounterVec // {db,cid}
collectErrorsTotal *prometheus.CounterVec // {db,cid}
publishEventsTotal *prometheus.CounterVec // {db,cid}
snapshotTimestampSec *prometheus.GaugeVec // {db,cid}
}
func (r *KubeOVNPlunger) initMetrics() {
p := promauto.With(r.Registry)
ns := "ovn"
// --- Core cluster health ---
r.metrics.clusterQuorum = p.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Subsystem: "cluster", Name: "quorum",
Help: "1 if cluster has quorum, else 0",
}, []string{"db", "cid"})
r.metrics.allAgree = p.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Subsystem: "cluster", Name: "all_agree",
Help: "1 if all members report identical membership",
}, []string{"db", "cid"})
r.metrics.membersExpected = p.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Subsystem: "cluster", Name: "members_expected",
Help: "Expected cluster size (replicas)",
}, []string{"db", "cid"})
r.metrics.membersObserved = p.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Subsystem: "cluster", Name: "members_observed",
Help: "Observed members (distinct SIDs across views)",
}, []string{"db", "cid"})
r.metrics.ipsExpected = p.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Subsystem: "cluster", Name: "ips_expected",
Help: "Expected distinct member IPs (from k8s hints)",
}, []string{"db", "cid"})
r.metrics.ipsObserved = p.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Subsystem: "cluster", Name: "ips_observed",
Help: "Observed distinct member IPs (from OVN views)",
}, []string{"db", "cid"})
r.metrics.excessMembers = p.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Subsystem: "cluster", Name: "excess_members",
Help: "Members over expected (>=0)",
}, []string{"db", "cid"})
r.metrics.missingMembers = p.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Subsystem: "cluster", Name: "missing_members",
Help: "Members short of expected (>=0)",
}, []string{"db", "cid"})
r.metrics.unexpectedIPsCount = p.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Subsystem: "cluster", Name: "unexpected_ips",
Help: "Count of IPs in OVN not present in k8s expected set",
}, []string{"db", "cid"})
r.metrics.missingExpectedIPsCount = p.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Subsystem: "cluster", Name: "missing_expected_ips",
Help: "Count of expected IPs not found in OVN",
}, []string{"db", "cid"})
r.metrics.ipConflictsCount = p.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Subsystem: "cluster", Name: "ip_conflicts",
Help: "Number of IPs claimed by multiple SIDs",
}, []string{"db", "cid"})
r.metrics.sidAddrDisagreements = p.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Subsystem: "cluster", Name: "sid_address_disagreements",
Help: "Number of SIDs seen with >1 distinct addresses",
}, []string{"db", "cid"})
// --- Consensus summary ---
r.metrics.consensusMajoritySize = p.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Subsystem: "consensus", Name: "majority_size",
Help: "Majority group size (0 if none)",
}, []string{"db", "cid"})
r.metrics.consensusMinoritySize = p.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Subsystem: "consensus", Name: "minority_size",
Help: "Minority group size",
}, []string{"db", "cid"})
r.metrics.consensusDiffsTotal = p.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Subsystem: "consensus", Name: "diffs_total",
Help: "Total per-reporter differences vs truth (missing + extra + mismatches)",
}, []string{"db", "cid"})
// --- Detail exports (sparse) ---
r.metrics.unexpectedIPGauge = p.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Subsystem: "consensus", Name: "unexpected_ip",
Help: "Unexpected IP present in OVN; value fixed at 1",
}, []string{"db", "cid", "ip"})
r.metrics.missingExpectedIPGauge = p.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Subsystem: "consensus", Name: "missing_expected_ip",
Help: "Expected IP missing from OVN; value fixed at 1",
}, []string{"db", "cid", "ip"})
r.metrics.ipConflictGauge = p.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Subsystem: "consensus", Name: "ip_conflict",
Help: "Number of SIDs claiming the same IP for this key",
}, []string{"db", "cid", "ip"})
r.metrics.suspectStaleGauge = p.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Subsystem: "consensus", Name: "suspect_stale",
Help: "Suspected stale SID candidate for kick; value fixed at 1 (emit only when remediation is warranted)",
}, []string{"db", "cid", "sid"})
// --- Per-member liveness/freshness ---
r.metrics.memberConnected = p.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Subsystem: "member", Name: "connected",
Help: "1 if local server reports connected/quorum, else 0",
}, []string{"db", "cid", "sid", "ip"})
r.metrics.memberLeader = p.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Subsystem: "member", Name: "leader",
Help: "1 if this member is leader, else 0",
}, []string{"db", "cid", "sid"})
r.metrics.memberLastMsgMs = p.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Subsystem: "member", Name: "last_msg_ms",
Help: "Follower->leader 'last msg' age in ms (legacy heuristic). NaN/omit if unknown",
}, []string{"db", "cid", "sid"})
r.metrics.memberIndex = p.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Subsystem: "member", Name: "index",
Help: "Local Raft log index",
}, []string{"db", "cid", "sid"})
r.metrics.memberIndexGap = p.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Subsystem: "member", Name: "index_gap",
Help: "Leader index minus local index (>=0)",
}, []string{"db", "cid", "sid"})
r.metrics.memberReporter = p.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Subsystem: "member", Name: "reporter",
Help: "1 if a self-view from this SID was collected in the scrape cycle",
}, []string{"db", "cid", "sid"})
r.metrics.memberMissingReporter = p.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Subsystem: "member", Name: "missing_reporter",
Help: "1 if SID appears in union but produced no self-view",
}, []string{"db", "cid", "sid"})
// --- Ops/housekeeping ---
r.metrics.leaderTransitionsTotal = p.NewCounterVec(prometheus.CounterOpts{
Namespace: ns, Subsystem: "ops", Name: "leader_transitions_total",
Help: "Count of observed leader SID changes",
}, []string{"db", "cid"})
r.metrics.collectErrorsTotal = p.NewCounterVec(prometheus.CounterOpts{
Namespace: ns, Subsystem: "ops", Name: "collect_errors_total",
Help: "Count of errors during health collection/analysis",
}, []string{"db", "cid"})
r.metrics.publishEventsTotal = p.NewCounterVec(prometheus.CounterOpts{
Namespace: ns, Subsystem: "ops", Name: "publish_events_total",
Help: "Count of SSE publish events (optional)",
}, []string{"db", "cid"})
r.metrics.snapshotTimestampSec = p.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Subsystem: "ops", Name: "snapshot_timestamp_seconds",
Help: "Unix timestamp of the last successful consensus snapshot",
}, []string{"db", "cid"})
}
func (r *KubeOVNPlunger) WriteClusterMetrics(db string, snaps []ovnstatus.HealthSnapshot, ecv ovnstatus.ExtendedConsensusResult, expectedReplicas int) {
cid := cidFromSnaps(snaps)
// Core cluster health
r.metrics.clusterQuorum.WithLabelValues(db, cid).Set(b2f(ecv.HasMajority))
r.metrics.allAgree.WithLabelValues(db, cid).Set(b2f(ecv.AllAgree))
r.metrics.membersExpected.WithLabelValues(db, cid).Set(float64(expectedReplicas))
r.metrics.membersObserved.WithLabelValues(db, cid).Set(float64(ecv.MembersCount))
r.metrics.ipsExpected.WithLabelValues(db, cid).Set(float64(len(ecv.ConsensusResult.TruthView.Members))) // optional; or len(hints.ExpectedIPs)
r.metrics.ipsObserved.WithLabelValues(db, cid).Set(float64(ecv.DistinctIPCount))
r.metrics.excessMembers.WithLabelValues(db, cid).Set(float64(ecv.ExpectedExcess))
r.metrics.missingMembers.WithLabelValues(db, cid).Set(float64(ecv.ExpectedShortfall))
r.metrics.unexpectedIPsCount.WithLabelValues(db, cid).Set(float64(len(ecv.UnexpectedIPs)))
r.metrics.missingExpectedIPsCount.WithLabelValues(db, cid).Set(float64(len(ecv.MissingExpectedIPs)))
r.metrics.ipConflictsCount.WithLabelValues(db, cid).Set(float64(len(ecv.IPConflicts)))
// Count SIDs with >1 distinct addresses
disagree := 0
for _, n := range ecv.SIDAddressDisagreements {
if n > 1 {
disagree++
}
}
r.metrics.sidAddrDisagreements.WithLabelValues(db, cid).Set(float64(disagree))
// Consensus summary
r.metrics.consensusMajoritySize.WithLabelValues(db, cid).Set(float64(len(ecv.MajorityMembers)))
r.metrics.consensusMinoritySize.WithLabelValues(db, cid).Set(float64(len(ecv.MinorityMembers)))
// Sum diffs across reporters (missing + extra + mismatches)
totalDiffs := 0
for _, d := range ecv.Diffs {
totalDiffs += len(d.MissingSIDs) + len(d.ExtraSIDs) + len(d.AddressMismatches)
}
r.metrics.consensusDiffsTotal.WithLabelValues(db, cid).Set(float64(totalDiffs))
// Sparse per-key exports (reset then re-emit for this {db,cid})
r.metrics.unexpectedIPGauge.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
for _, ip := range ecv.UnexpectedIPs {
r.metrics.unexpectedIPGauge.WithLabelValues(db, cid, ip).Set(1)
}
r.metrics.missingExpectedIPGauge.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
for _, ip := range ecv.MissingExpectedIPs {
r.metrics.missingExpectedIPGauge.WithLabelValues(db, cid, ip).Set(1)
}
r.metrics.ipConflictGauge.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
for ip, sids := range ecv.IPConflicts {
r.metrics.ipConflictGauge.WithLabelValues(db, cid, ip).Set(float64(len(sids)))
}
// Only emit suspects when remediation is warranted (e.g., TooManyMembers / unexpected IPs / conflicts)
r.metrics.suspectStaleGauge.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
if ecv.TooManyMembers || len(ecv.UnexpectedIPs) > 0 || len(ecv.IPConflicts) > 0 {
for _, sid := range ecv.SuspectStaleSIDs {
r.metrics.suspectStaleGauge.WithLabelValues(db, cid, sid).Set(1)
}
}
// Snapshot timestamp
r.metrics.snapshotTimestampSec.WithLabelValues(db, cid).Set(float64(time.Now().Unix()))
}
func (r *KubeOVNPlunger) WriteMemberMetrics(db string, snaps []ovnstatus.HealthSnapshot, views []ovnstatus.MemberView, ecv ovnstatus.ExtendedConsensusResult) {
cid := cidFromSnaps(snaps)
// Figure out current leader SID (prefer local view from any leader snapshot)
curLeader := ""
for _, s := range snaps {
if s.Local.Leader {
curLeader = s.Local.SID
break
}
}
// Leader transitions
key := db + "|" + cid
if prev, ok := r.lastLeader[key]; ok && prev != "" && curLeader != "" && prev != curLeader {
r.metrics.leaderTransitionsTotal.WithLabelValues(db, cid).Inc()
}
if curLeader != "" {
r.lastLeader[key] = curLeader
}
// Build quick maps for reporter set & IP per SID (best-effort)
reporter := map[string]struct{}{}
for _, v := range views {
if v.FromSID != "" {
reporter[v.FromSID] = struct{}{}
}
}
sidToIP := map[string]string{}
for _, v := range views {
for sid, addr := range v.Members {
if sidToIP[sid] == "" && addr != "" {
sidToIP[sid] = ovnstatus.AddrToIP(addr) // expose addrToIP or wrap here
}
}
}
// Reset member vectors for this {db,cid} (avoid stale series)
r.metrics.memberConnected.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.memberLeader.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.memberLastMsgMs.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.memberIndex.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.memberIndexGap.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.memberReporter.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.memberMissingReporter.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
// Leader index (to compute gaps)
lIdx := leaderIndex(snaps, curLeader)
// Emit one series per snapshot (self view)
for _, s := range snaps {
sid := s.Local.SID
ip := sidToIP[sid]
if ip == "" {
ip = "unknown"
}
r.metrics.memberConnected.WithLabelValues(db, cid, sid, ip).Set(b2f(s.Local.Connected))
r.metrics.memberLeader.WithLabelValues(db, cid, sid).Set(b2f(s.Local.Leader))
r.metrics.memberIndex.WithLabelValues(db, cid, sid).Set(float64(s.Local.Index))
if lIdx != nil && s.Local.Index >= 0 {
gap := *lIdx - s.Local.Index
if gap < 0 {
gap = 0
}
r.metrics.memberIndexGap.WithLabelValues(db, cid, sid).Set(float64(gap))
}
// Reporter presence
_, isReporter := reporter[sid]
r.metrics.memberReporter.WithLabelValues(db, cid, sid).Set(b2f(isReporter))
}
// “Missing reporter” SIDs = union reporters (from ecv)
reporterSet := map[string]struct{}{}
for sid := range reporter {
reporterSet[sid] = struct{}{}
}
unionSet := map[string]struct{}{}
for _, sid := range ecv.UnionMembers {
unionSet[sid] = struct{}{}
}
for sid := range unionSet {
if _, ok := reporterSet[sid]; !ok {
r.metrics.memberMissingReporter.WithLabelValues(db, cid, sid).Set(1)
}
}
// Legacy follower freshness (if you kept LastMsgMs in servers parsing)
// We only know LastMsgMs from the Full.Servers in each snapshot; pick the freshest per SID.
lastMsg := map[string]int64{}
for _, s := range snaps {
for _, srv := range s.Full.Servers {
if srv.LastMsgMs != nil {
cur, ok := lastMsg[srv.SID]
if !ok || *srv.LastMsgMs < cur {
lastMsg[srv.SID] = *srv.LastMsgMs
}
}
}
}
for sid, ms := range lastMsg {
r.metrics.memberLastMsgMs.WithLabelValues(db, cid, sid).Set(float64(ms))
}
}
func (r *KubeOVNPlunger) deleteAllFor(db, cid string) {
// Cluster-level vecs (db,cid)
r.metrics.clusterQuorum.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.allAgree.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.membersExpected.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.membersObserved.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.ipsExpected.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.ipsObserved.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.excessMembers.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.missingMembers.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.unexpectedIPsCount.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.missingExpectedIPsCount.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.ipConflictsCount.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.sidAddrDisagreements.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.consensusMajoritySize.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.consensusMinoritySize.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.consensusDiffsTotal.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
// Sparse detail vecs (db,cid,*)
r.metrics.unexpectedIPGauge.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.missingExpectedIPGauge.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.ipConflictGauge.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.suspectStaleGauge.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
// Per-member vecs (db,cid,*)
r.metrics.memberConnected.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.memberLeader.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.memberLastMsgMs.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.memberIndex.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.memberIndexGap.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.memberReporter.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.memberMissingReporter.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
// Ops vecs (db,cid)
r.metrics.leaderTransitionsTotal.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.collectErrorsTotal.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.publishEventsTotal.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
r.metrics.snapshotTimestampSec.DeletePartialMatch(prometheus.Labels{"db": db, "cid": cid})
}

View File

@@ -1,31 +0,0 @@
package kubeovnplunger
import "github.com/cozystack/cozystack/pkg/ovnstatus"
func b2f(b bool) float64 {
if b {
return 1
}
return 0
}
// Pull a cluster UUID (cid) from any snapshots Local.CID (falls back to "")
func cidFromSnaps(snaps []ovnstatus.HealthSnapshot) string {
for _, s := range snaps {
if s.Local.CID != "" {
return s.Local.CID
}
}
return ""
}
// Map SID -> last local index to compute gaps (optional)
func leaderIndex(snaps []ovnstatus.HealthSnapshot, leaderSID string) (idx *int64) {
for _, s := range snaps {
if s.Local.SID == leaderSID && s.Local.Index > 0 {
v := s.Local.Index
return &v
}
}
return nil
}

View File

@@ -1,367 +0,0 @@
package lineagelabeler
import (
"context"
"errors"
"fmt"
"strings"
"sync"
"sync/atomic"
"time"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
"github.com/cozystack/cozystack/internal/shared/crdmem"
"github.com/cozystack/cozystack/pkg/lineage"
helmv2 "github.com/fluxcd/helm-controller/api/v2"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/client-go/discovery/cached/memory"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
"k8s.io/client-go/restmapper"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
var ErrNoAncestors = errors.New("no ancestors")
type LineageLabelerReconciler struct {
client.Client
Scheme *runtime.Scheme
WatchResourceCSV string
dynClient dynamic.Interface
mapper meta.RESTMapper
appMap atomic.Value
once sync.Once
mem *crdmem.Memory
}
type chartRef struct{ repo, chart string }
type appRef struct{ groupVersion, kind, prefix string }
func (r *LineageLabelerReconciler) initMapping() {
r.once.Do(func() {
r.appMap.Store(make(map[chartRef]appRef))
})
}
func (r *LineageLabelerReconciler) currentMap() map[chartRef]appRef {
val := r.appMap.Load()
if val == nil {
return map[chartRef]appRef{}
}
return val.(map[chartRef]appRef)
}
func (r *LineageLabelerReconciler) Map(hr *helmv2.HelmRelease) (string, string, string, error) {
cfg := r.currentMap()
s := hr.Spec.Chart.Spec
key := chartRef{s.SourceRef.Name, s.Chart}
if v, ok := cfg[key]; ok {
return v.groupVersion, v.kind, v.prefix, nil
}
return "", "", "", fmt.Errorf("cannot map helm release %s/%s to dynamic app", hr.Namespace, hr.Name)
}
func parseGVKList(csv string) ([]schema.GroupVersionKind, error) {
csv = strings.TrimSpace(csv)
if csv == "" {
return nil, fmt.Errorf("watch resource list is empty")
}
parts := strings.Split(csv, ",")
out := make([]schema.GroupVersionKind, 0, len(parts))
for _, p := range parts {
p = strings.TrimSpace(p)
s := strings.Split(p, "/")
if len(s) == 2 {
out = append(out, schema.GroupVersionKind{Group: "", Version: s[0], Kind: s[1]})
continue
}
if len(s) == 3 {
out = append(out, schema.GroupVersionKind{Group: s[0], Version: s[1], Kind: s[2]})
continue
}
return nil, fmt.Errorf("invalid resource token %q, expected 'group/version/Kind' or 'v1/Kind'", p)
}
return out, nil
}
func (r *LineageLabelerReconciler) SetupWithManager(mgr ctrl.Manager) error {
r.initMapping()
cfg := rest.CopyConfig(mgr.GetConfig())
dc, err := dynamic.NewForConfig(cfg)
if err != nil {
return err
}
disco, err := discovery.NewDiscoveryClientForConfig(cfg)
if err != nil {
return err
}
cached := memory.NewMemCacheClient(disco)
r.dynClient = dc
r.mapper = restmapper.NewDeferredDiscoveryRESTMapper(cached)
if r.mem == nil {
r.mem = crdmem.Global()
}
if err := r.mem.EnsurePrimingWithManager(mgr); err != nil {
return err
}
gvks, err := parseGVKList(r.WatchResourceCSV)
if err != nil {
return err
}
if len(gvks) == 0 {
return fmt.Errorf("no resources to watch")
}
b := ctrl.NewControllerManagedBy(mgr).Named("lineage-labeler")
nsPred := predicate.NewPredicateFuncs(func(obj client.Object) bool {
ns := obj.GetNamespace()
return ns != "" && strings.HasPrefix(ns, "tenant-")
})
primary := gvks[0]
primaryObj := &unstructured.Unstructured{}
primaryObj.SetGroupVersionKind(primary)
b = b.For(primaryObj,
builder.WithPredicates(
predicate.And(
nsPred,
predicate.Or(
predicate.GenerationChangedPredicate{},
predicate.ResourceVersionChangedPredicate{},
),
),
),
)
for _, gvk := range gvks[1:] {
u := &unstructured.Unstructured{}
u.SetGroupVersionKind(gvk)
b = b.Watches(u,
&handler.EnqueueRequestForObject{},
builder.WithPredicates(
predicate.And(
nsPred,
predicate.Or(
predicate.GenerationChangedPredicate{},
predicate.ResourceVersionChangedPredicate{},
),
),
),
)
}
b = b.Watches(
&cozyv1alpha1.CozystackResourceDefinition{},
handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
_ = r.refreshAppMap(ctx)
return nil
}),
)
_ = r.refreshAppMap(context.Background())
return b.Complete(r)
}
func (r *LineageLabelerReconciler) refreshAppMap(ctx context.Context) error {
var items []cozyv1alpha1.CozystackResourceDefinition
var err error
if r.mem != nil {
items, err = r.mem.ListFromCacheOrAPI(ctx, r.Client)
} else {
var list cozyv1alpha1.CozystackResourceDefinitionList
err = r.Client.List(ctx, &list)
items = list.Items
}
if err != nil {
return err
}
newMap := make(map[chartRef]appRef, len(items))
for _, crd := range items {
k := chartRef{
repo: crd.Spec.Release.Chart.SourceRef.Name,
chart: crd.Spec.Release.Chart.Name,
}
v := appRef{
groupVersion: "apps.cozystack.io/v1alpha1",
kind: crd.Spec.Application.Kind,
prefix: crd.Spec.Release.Prefix,
}
if _, exists := newMap[k]; exists {
continue
}
newMap[k] = v
}
r.appMap.Store(newMap)
return nil
}
func (r *LineageLabelerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
l := log.FromContext(ctx)
if req.Namespace == "" || !strings.HasPrefix(req.Namespace, "tenant-") {
return ctrl.Result{}, nil
}
if len(r.currentMap()) == 0 {
_ = r.refreshAppMap(ctx)
if len(r.currentMap()) == 0 {
return ctrl.Result{RequeueAfter: 2 * time.Second}, nil
}
}
gvks, err := parseGVKList(r.WatchResourceCSV)
if err != nil {
return ctrl.Result{}, err
}
var obj *unstructured.Unstructured
found := false
for _, gvk := range gvks {
mapping, mErr := r.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
if mErr != nil {
continue
}
ns := req.Namespace
if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
ns = ""
}
res, gErr := r.dynClient.Resource(mapping.Resource).Namespace(ns).Get(ctx, req.Name, metav1.GetOptions{})
if gErr != nil {
if apierrors.IsNotFound(gErr) {
continue
}
continue
}
obj = res
found = true
break
}
if !found || obj == nil {
return ctrl.Result{}, nil
}
existing := obj.GetLabels()
if existing == nil {
existing = map[string]string{}
}
keys := []string{
"apps.cozystack.io/application.group",
"apps.cozystack.io/application.kind",
"apps.cozystack.io/application.name",
}
allPresent := true
for _, k := range keys {
if _, ok := existing[k]; !ok {
allPresent = false
break
}
}
if allPresent {
return ctrl.Result{}, nil
}
labels, warn, err := r.computeLabels(ctx, obj)
if err != nil {
if errors.Is(err, ErrNoAncestors) {
return ctrl.Result{}, nil
}
return ctrl.Result{}, client.IgnoreNotFound(err)
}
if warn != "" {
l.V(1).Info("lineage ambiguous; using first ancestor", "name", req.NamespacedName)
}
for k, v := range labels {
existing[k] = v
}
obj.SetLabels(existing)
// Server-Side Apply: claim ownership of our label keys
gvk := obj.GroupVersionKind()
patch := &unstructured.Unstructured{}
patch.SetGroupVersionKind(gvk)
patch.SetNamespace(obj.GetNamespace())
patch.SetName(obj.GetName())
patch.SetLabels(map[string]string{
"apps.cozystack.io/application.group": existing["apps.cozystack.io/application.group"],
"apps.cozystack.io/application.kind": existing["apps.cozystack.io/application.kind"],
"apps.cozystack.io/application.name": existing["apps.cozystack.io/application.name"],
})
// Use controller-runtime client with Apply patch type and field owner
if err := r.Patch(ctx, patch,
client.Apply,
client.FieldOwner("cozystack/lineage"),
client.ForceOwnership(false),
); err != nil {
if apierrors.IsConflict(err) {
return ctrl.Result{RequeueAfter: 500 * time.Millisecond}, nil
}
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
func (r *LineageLabelerReconciler) computeLabels(ctx context.Context, o *unstructured.Unstructured) (map[string]string, string, error) {
owners := lineage.WalkOwnershipGraph(ctx, r.dynClient, r.mapper, r, o)
if len(owners) == 0 {
return nil, "", ErrNoAncestors
}
obj, err := owners[0].GetUnstructured(ctx, r.dynClient, r.mapper)
if err != nil {
return nil, "", err
}
gv, err := schema.ParseGroupVersion(obj.GetAPIVersion())
if err != nil {
return nil, "", fmt.Errorf("invalid APIVersion %s: %w", obj.GetAPIVersion(), err)
}
var warn string
if len(owners) > 1 {
warn = "ambiguous"
}
group := gv.Group
if len(group) > 63 {
group = trimDNSLabel(group[:63])
}
return map[string]string{
"apps.cozystack.io/application.group": group,
"apps.cozystack.io/application.kind": obj.GetKind(),
"apps.cozystack.io/application.name": obj.GetName(),
}, warn, nil
}
func trimDNSLabel(s string) string {
for len(s) > 0 {
b := s[len(s)-1]
if (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9') {
return s
}
s = s[:len(s)-1]
}
return s
}

View File

@@ -1,99 +0,0 @@
package crdmem
import (
"context"
"sync"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)
type Memory struct {
mu sync.RWMutex
data map[string]cozyv1alpha1.CozystackResourceDefinition
primed bool
primeOnce sync.Once
}
func New() *Memory {
return &Memory{data: make(map[string]cozyv1alpha1.CozystackResourceDefinition)}
}
var (
global *Memory
globalOnce sync.Once
)
func Global() *Memory {
globalOnce.Do(func() { global = New() })
return global
}
func (m *Memory) Upsert(obj *cozyv1alpha1.CozystackResourceDefinition) {
if obj == nil {
return
}
m.mu.Lock()
m.data[obj.Name] = *obj.DeepCopy()
m.mu.Unlock()
}
func (m *Memory) Delete(name string) {
m.mu.Lock()
delete(m.data, name)
m.mu.Unlock()
}
func (m *Memory) Snapshot() []cozyv1alpha1.CozystackResourceDefinition {
m.mu.RLock()
defer m.mu.RUnlock()
out := make([]cozyv1alpha1.CozystackResourceDefinition, 0, len(m.data))
for _, v := range m.data {
out = append(out, v)
}
return out
}
func (m *Memory) IsPrimed() bool {
m.mu.RLock()
defer m.mu.RUnlock()
return m.primed
}
type runnable func(context.Context) error
func (r runnable) Start(ctx context.Context) error { return r(ctx) }
func (m *Memory) EnsurePrimingWithManager(mgr ctrl.Manager) error {
var errOut error
m.primeOnce.Do(func() {
errOut = mgr.Add(runnable(func(ctx context.Context) error {
if ok := mgr.GetCache().WaitForCacheSync(ctx); !ok {
return nil
}
var list cozyv1alpha1.CozystackResourceDefinitionList
if err := mgr.GetClient().List(ctx, &list); err == nil {
for i := range list.Items {
m.Upsert(&list.Items[i])
}
m.mu.Lock()
m.primed = true
m.mu.Unlock()
}
return nil
}))
})
return errOut
}
func (m *Memory) ListFromCacheOrAPI(ctx context.Context, c client.Client) ([]cozyv1alpha1.CozystackResourceDefinition, error) {
if m.IsPrimed() {
return m.Snapshot(), nil
}
var list cozyv1alpha1.CozystackResourceDefinitionList
if err := c.List(ctx, &list); err != nil {
return nil, err
}
return list.Items, nil
}

View File

@@ -1,293 +0,0 @@
// Package sse provides a tiny Server-Sent Events server with pluggable routes.
// No external deps; safe for quick demos and small dashboards.
package sse
import (
"context"
"fmt"
"html/template"
"log"
"net/http"
"strings"
"sync"
"time"
)
// Options configures the SSE server.
type Options struct {
// Addr is the listening address, e.g. ":8080" or "127.0.0.1:0".
Addr string
// IndexPath is the path serving a minimal live HTML page ("" to disable).
// e.g. "/" or "/status"
IndexPath string
// StreamPath is the SSE endpoint path, e.g. "/stream".
StreamPath string
// Title for the index page (cosmetic).
Title string
// AllowCORS, if true, sets Access-Control-Allow-Origin: * for /stream.
AllowCORS bool
// ClientBuf is the per-client buffered message queue size.
// If 0, defaults to 16. When full, new messages are dropped for that client.
ClientBuf int
// Heartbeat sends a comment line every interval to keep connections alive.
// If 0, defaults to 25s.
Heartbeat time.Duration
// Logger (optional). If nil, log.Printf is used.
Logger *log.Logger
}
// Server is a simple SSE broadcaster.
type Server struct {
opts Options
mux *http.ServeMux
http *http.Server
clientsMu sync.RWMutex
clients map[*client]struct{}
// latest holds the most recent payload (sent to new clients on connect).
latestMu sync.RWMutex
latest string
}
type client struct {
ch chan string
closeCh chan struct{}
flusher http.Flusher
w http.ResponseWriter
req *http.Request
logf func(string, ...any)
heartbeat time.Duration
}
func New(opts Options) *Server {
if opts.ClientBuf <= 0 {
opts.ClientBuf = 16
}
if opts.Heartbeat <= 0 {
opts.Heartbeat = 25 * time.Second
}
if opts.Addr == "" {
opts.Addr = ":8080"
}
if opts.StreamPath == "" {
opts.StreamPath = "/stream"
}
if opts.IndexPath == "" {
opts.IndexPath = "/"
}
s := &Server{
opts: opts,
mux: http.NewServeMux(),
clients: make(map[*client]struct{}),
}
s.routes()
s.http = &http.Server{
Addr: opts.Addr,
Handler: s.mux,
ReadHeaderTimeout: 10 * time.Second,
}
return s
}
func (s *Server) routes() {
if s.opts.IndexPath != "" {
s.mux.HandleFunc(s.opts.IndexPath, s.handleIndex)
}
s.mux.HandleFunc(s.opts.StreamPath, s.handleStream)
}
func (s *Server) logf(format string, args ...any) {
if s.opts.Logger != nil {
s.opts.Logger.Printf(format, args...)
} else {
log.Printf(format, args...)
}
}
// ListenAndServe starts the HTTP server (blocking).
func (s *Server) ListenAndServe() error {
s.logf("sse: listening on http://%s (index=%s, stream=%s)", s.http.Addr, s.opts.IndexPath, s.opts.StreamPath)
return s.http.ListenAndServe()
}
// Shutdown gracefully stops the server.
func (s *Server) Shutdown(ctx context.Context) error {
s.clientsMu.Lock()
for c := range s.clients {
close(c.closeCh)
}
s.clientsMu.Unlock()
return s.http.Shutdown(ctx)
}
// Publish broadcasts a new payload to all clients and stores it as latest.
func (s *Server) Publish(payload string) {
// Store latest
s.latestMu.Lock()
s.latest = payload
s.latestMu.Unlock()
// Broadcast
s.clientsMu.RLock()
defer s.clientsMu.RUnlock()
for c := range s.clients {
select {
case c.ch <- payload:
default:
// Drop if client is slow (buffer full)
if s.opts.Logger != nil {
s.opts.Logger.Printf("sse: dropping message to slow client %p", c)
}
}
}
}
func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
page := indexTemplate(s.opts.Title, s.opts.StreamPath)
_, _ = w.Write([]byte(page))
}
func (s *Server) handleStream(w http.ResponseWriter, r *http.Request) {
// Required SSE headers
if s.opts.AllowCORS {
w.Header().Set("Access-Control-Allow-Origin", "*")
}
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "streaming unsupported", http.StatusInternalServerError)
return
}
c := &client{
ch: make(chan string, s.opts.ClientBuf),
closeCh: make(chan struct{}),
flusher: flusher,
w: w,
req: r,
logf: s.logf,
heartbeat: s.opts.Heartbeat,
}
// Register client
s.clientsMu.Lock()
s.clients[c] = struct{}{}
s.clientsMu.Unlock()
// Initial comment to open the stream for some proxies
fmt.Fprintf(w, ": connected %s\n\n", time.Now().Format(time.RFC3339))
flusher.Flush()
// Send latest if any
s.latestMu.RLock()
latest := s.latest
s.latestMu.RUnlock()
if latest != "" {
writeSSE(w, latest)
flusher.Flush()
}
// Start pump
go c.pump()
// Block until client disconnects
<-r.Context().Done()
// Unregister client
close(c.closeCh)
s.clientsMu.Lock()
delete(s.clients, c)
s.clientsMu.Unlock()
}
func (c *client) pump() {
t := time.NewTicker(c.heartbeat)
defer t.Stop()
for {
select {
case <-c.closeCh:
return
case msg := <-c.ch:
writeSSE(c.w, msg)
c.flusher.Flush()
case <-t.C:
// heartbeat comment (keeps connections alive through proxies)
fmt.Fprint(c.w, ": hb\n\n")
c.flusher.Flush()
}
}
}
func writeSSE(w http.ResponseWriter, msg string) {
// Split on lines; each needs its own "data:" field per the SSE spec
lines := strings.Split(strings.TrimRight(msg, "\n"), "\n")
for _, ln := range lines {
fmt.Fprintf(w, "data: %s\n", ln)
}
fmt.Fprint(w, "\n")
}
// Minimal index page with live updates
func indexTemplate(title, streamPath string) string {
if title == "" {
title = "SSE Stream"
}
if streamPath == "" {
streamPath = "/stream"
}
const tpl = `<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>{{.Title}}</title>
<style>
body { font-family: system-ui, sans-serif; margin: 2rem; }
pre { background:#111; color:#eee; padding:1rem; border-radius:12px; white-space:pre-wrap;}
.status { margin-bottom: 1rem; }
</style>
</head>
<body>
<h1>{{.Title}}</h1>
<div class="status">Connecting…</div>
<pre id="out"></pre>
<script>
const statusEl = document.querySelector('.status');
const out = document.getElementById('out');
const es = new EventSource('{{.Stream}}');
es.onmessage = (e) => {
// Replace content with the latest full snapshot
if (e.data === "") return;
// We accumulate until a blank 'data:' terminator; simpler approach: reset on first line.
// For this demo, server always sends full content in one event, so just overwrite.
out.textContent = (out._acc ?? "") + e.data + "\n";
};
es.addEventListener('open', () => { statusEl.textContent = "Connected"; out._acc = ""; });
es.addEventListener('error', () => { statusEl.textContent = "Disconnected (browser will retry)…"; out._acc = ""; });
// Optional: keep the latest only per message
es.onmessage = (e) => {
out.textContent = e.data + "\n";
statusEl.textContent = "Connected";
};
</script>
</body>
</html>`
page, _ := template.New("idx").Parse(tpl)
var b strings.Builder
_ = page.Execute(&b, map[string]any{
"Title": title,
"Stream": streamPath,
})
return b.String()
}

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/nginx-cache:0.7.0@sha256:e0a07082bb6fc6aeaae2315f335386f1705a646c72f9e0af512aebbca5cb2b15
ghcr.io/cozystack/cozystack/nginx-cache:0.7.0@sha256:50ac1581e3100bd6c477a71161cb455a341ffaf9e5e2f6086802e4e25271e8af

View File

@@ -16,7 +16,7 @@ type: application
# 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.29.0
version: 0.27.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

View File

@@ -93,7 +93,7 @@ See the reference for components utilized in this service:
| Name | Description | Type | Value |
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------- | ------- |
| `version` | Kubernetes version given as vMAJOR.MINOR. Available are versions from 1.28 to 1.33. | `string` | `v1.33` |
| `version` | Kubernetes version given as vMAJOR.MINOR. Available are versions from 1.28 to 1.33. | `string` | `v1.32` |
| `host` | Hostname used to access the Kubernetes cluster externally. Defaults to `<cluster-name>.<tenant-host>` when empty. | `string` | `""` |
| `nodeGroups` | Worker nodes configuration | `map[string]object` | `{...}` |
| `nodeGroups[name].minReplicas` | Minimum amount of replicas | `int` | `0` |
@@ -139,8 +139,6 @@ See the reference for components utilized in this service:
| `addons.velero` | Velero | `object` | `{}` |
| `addons.velero.enabled` | Enable Velero for backup and recovery of a tenant Kubernetes cluster. | `bool` | `false` |
| `addons.velero.valuesOverride` | Custom values to override | `object` | `{}` |
| `addons.coredns` | Coredns | `object` | `{}` |
| `addons.coredns.valuesOverride` | Custom values to override | `object` | `{}` |
### Kubernetes Control Plane Configuration

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/cluster-autoscaler:0.29.0@sha256:2d39989846c3579dd020b9f6c77e6e314cc81aa344eaac0f6d633e723c17196d
ghcr.io/cozystack/cozystack/cluster-autoscaler:0.27.0@sha256:e4fbb7d2043f25b90cc8840468d0880e9d3d72ae8b1c8801bf8c35f944cc485d

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/kubevirt-cloud-provider:0.29.0@sha256:5335c044313b69ee13b30ca4941687e509005e55f4ae25723861edbf2fbd6dd2
ghcr.io/cozystack/cozystack/kubevirt-cloud-provider:0.27.0@sha256:5335c044313b69ee13b30ca4941687e509005e55f4ae25723861edbf2fbd6dd2

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.29.0@sha256:3a3bc912f70ccba1e9f92a0754179dbdc4c01f24073467b6d1406c77da794863
ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.27.0@sha256:8eb9803aa1b38e1b2db98237bf0d1046f0ba90be0157c22da1efc3811bb25ecf

View File

@@ -127,6 +127,9 @@ spec:
resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.controlPlane.scheduler.resourcesPreset .Values.controlPlane.scheduler.resources $) | nindent 6 }}
dataStoreName: "{{ $etcd }}"
addons:
coreDNS:
dnsServiceIPs:
- 10.95.0.10
konnectivity:
server:
port: 8132

View File

@@ -1,47 +0,0 @@
{{- define "cozystack.defaultCoreDNSValues" -}}
coredns:
service:
clusterIP: "10.95.0.10"
{{- end }}
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: {{ .Release.Name }}-coredns
labels:
cozystack.io/repository: system
cozystack.io/target-cluster-name: {{ .Release.Name }}
spec:
interval: 5m
releaseName: coredns
chart:
spec:
chart: cozy-coredns
reconcileStrategy: Revision
sourceRef:
kind: HelmRepository
name: cozystack-system
namespace: cozy-system
version: '>= 0.0.0-0'
kubeConfig:
secretRef:
name: {{ .Release.Name }}-admin-kubeconfig
key: super-admin.svc
targetNamespace: kube-system
storageNamespace: kube-system
install:
createNamespace: true
remediation:
retries: -1
upgrade:
remediation:
retries: -1
values:
{{- toYaml (deepCopy .Values.addons.coredns.valuesOverride | mergeOverwrite (fromYaml (include "cozystack.defaultCoreDNSValues" .))) | nindent 4 }}
dependsOn:
{{- if lookup "helm.toolkit.fluxcd.io/v2" "HelmRelease" .Release.Namespace .Release.Name }}
- name: {{ .Release.Name }}
namespace: {{ .Release.Namespace }}
{{- end }}
- name: {{ .Release.Name }}-cilium
namespace: {{ .Release.Namespace }}

View File

@@ -41,7 +41,6 @@ spec:
{{ .Release.Name }}-fluxcd
{{ .Release.Name }}-gpu-operator
{{ .Release.Name }}-velero
{{ .Release.Name }}-coredns
-p '{"spec": {"suspend": true}}'
--type=merge --field-manager=flux-client-side-apply || true
---
@@ -82,7 +81,6 @@ rules:
- {{ .Release.Name }}-fluxcd
- {{ .Release.Name }}-gpu-operator
- {{ .Release.Name }}-velero
- {{ .Release.Name }}-coredns
---
apiVersion: rbac.authorization.k8s.io/v1

View File

@@ -13,9 +13,6 @@
"cilium": {
"valuesOverride": {}
},
"coredns": {
"valuesOverride": {}
},
"fluxcd": {
"enabled": false,
"valuesOverride": {}
@@ -48,7 +45,6 @@
"required": [
"certManager",
"cilium",
"coredns",
"fluxcd",
"gatewayAPI",
"gpuOperator",
@@ -101,24 +97,6 @@
}
}
},
"coredns": {
"description": "Coredns",
"type": "object",
"default": {
"valuesOverride": {}
},
"required": [
"valuesOverride"
],
"properties": {
"valuesOverride": {
"description": "Custom values to override",
"type": "object",
"default": {},
"x-kubernetes-preserve-unknown-fields": true
}
}
},
"fluxcd": {
"description": "Flux CD",
"type": "object",
@@ -720,7 +698,7 @@
"version": {
"description": "Kubernetes version given as vMAJOR.MINOR. Available are versions from 1.28 to 1.33.",
"type": "string",
"default": "v1.33",
"default": "v1.32",
"enum": [
"v1.28",
"v1.29",

View File

@@ -5,7 +5,7 @@ storageClass: replicated
## @section Application-specific parameters
## @param version {string} Kubernetes version given as vMAJOR.MINOR. Available are versions from 1.28 to 1.33.
version: "v1.33"
version: "v1.32"
## @param host {string} Hostname used to access the Kubernetes cluster externally. Defaults to `<cluster-name>.<tenant-host>` when empty.
host: ""
@@ -122,13 +122,6 @@ addons:
enabled: false
valuesOverride: {}
## @field addons.coredns {coredns} Coredns
##
coredns:
## @field coredns.valuesOverride {object} Custom values to override
##
valuesOverride: {}
## @section Kubernetes Control Plane Configuration
##
## @param controlPlane {controlPlane} Control Plane Configuration

View File

@@ -4,4 +4,4 @@ description: Separated tenant namespace
icon: /logos/tenant.svg
type: application
version: 1.14.0
version: 1.12.0

View File

@@ -20,7 +20,5 @@ spec:
version: "*"
interval: 1m0s
timeout: 5m0s
upgrade:
force: true
values: {}
{{- end }}

View File

@@ -19,10 +19,10 @@ spec:
namespace: cozy-public
install:
remediation:
retries: -1
retries: 10
upgrade:
remediation:
retries: -1
retries: 10
interval: 1m0s
timeout: 10m0s
{{- end }}

View File

@@ -20,11 +20,7 @@ metadata:
name: allow-external-communication
namespace: {{ include "tenant.name" . }}
spec:
endpointSelector:
matchExpressions:
- key: policy.cozystack.io/allow-external-communication
operator: NotIn
values: ["false"]
endpointSelector: {}
ingress:
- fromEntities:
- world

View File

@@ -7,21 +7,4 @@ metadata:
spec:
hard:
{{- include "cozy-lib.resources.flatten" (list .Values.resourceQuotas $) | nindent 6 }}
---
apiVersion: v1
kind: LimitRange
metadata:
name: tenant-range-limits
namespace: {{ include "tenant.name" . }}
spec:
limits:
- default:
cpu: "250m"
memory: "128Mi"
ephemeral-storage: "2Gi"
defaultRequest:
cpu: "25m"
memory: "128Mi"
ephemeral-storage: "50Mi"
type: Container
{{- end }}

View File

@@ -17,12 +17,6 @@ spec:
kind: HelmRepository
name: cozystack-extra
namespace: cozy-public
install:
remediation:
retries: -1
upgrade:
remediation:
retries: -1
interval: 1m0s
timeout: 10m0s
timeout: 5m0s
{{- end }}

View File

@@ -69,9 +69,7 @@ kubernetes 0.26.0 9584e5f5
kubernetes 0.26.1 0e47e1e8
kubernetes 0.26.2 8ddbe32e
kubernetes 0.26.3 c02a3818
kubernetes 0.27.0 6cd5e746
kubernetes 0.28.0 7f477eec
kubernetes 0.29.0 HEAD
kubernetes 0.27.0 HEAD
mysql 0.1.0 263e47be
mysql 0.2.0 c24a103f
mysql 0.3.0 53f2365e
@@ -165,16 +163,55 @@ tcp-balancer 0.4.2 4369b031
tcp-balancer 0.5.0 08cb7c0f
tcp-balancer 0.5.1 c02a3818
tcp-balancer 0.6.0 HEAD
tenant 1.13.0 8f1975d1
tenant 1.14.0 HEAD
virtual-machine 0.14.0 HEAD
tenant 1.10.0 4369b031
tenant 1.11.0 08cb7c0f
tenant 1.11.1 28c9fcd6
tenant 1.11.2 c02a3818
tenant 1.12.0 HEAD
virtual-machine 0.1.4 f2015d65
virtual-machine 0.1.5 263e47be
virtual-machine 0.2.0 c0685f43
virtual-machine 0.3.0 6c5cf5bf
virtual-machine 0.4.0 b8e33d19
virtual-machine 0.5.0 1ec10165
virtual-machine 0.6.0 4e68e65c
virtual-machine 0.7.0 e23286a3
virtual-machine 0.7.1 0ab39f20
virtual-machine 0.8.0 3fa4dd3a
virtual-machine 0.8.1 93c46161
virtual-machine 0.8.2 de19450f
virtual-machine 0.9.0 721c12a7
virtual-machine 0.9.1 93bdf411
virtual-machine 0.10.0 6130f43d
virtual-machine 0.10.2 632224a3
virtual-machine 0.11.0 4369b031
virtual-machine 0.12.0 acd4663a
virtual-machine 0.12.1 909208ba
virtual-machine 0.12.2 8ddbe32e
virtual-machine 0.12.3 c02a3818
virtual-machine 0.13.0 HEAD
vm-disk 0.1.0 d971f2ff
vm-disk 0.1.1 6130f43d
vm-disk 0.1.2 632224a3
vm-disk 0.2.0 4369b031
vm-disk 0.3.0 c02a3818
vm-disk 0.4.0 HEAD
vm-instance 0.12.0 HEAD
vm-instance 0.1.0 1ec10165
vm-instance 0.2.0 84f3ccc0
vm-instance 0.3.0 4e68e65c
vm-instance 0.4.0 e23286a3
vm-instance 0.4.1 0ab39f20
vm-instance 0.5.0 3fa4dd3a
vm-instance 0.5.1 de19450f
vm-instance 0.6.0 721c12a7
vm-instance 0.7.0 6130f43d
vm-instance 0.7.2 632224a3
vm-instance 0.8.0 4369b031
vm-instance 0.9.0 acd4663a
vm-instance 0.10.0 909208ba
vm-instance 0.10.1 8ddbe32e
vm-instance 0.10.2 c02a3818
vm-instance 0.11.0 HEAD
vpn 0.1.0 263e47be
vpn 0.2.0 53f2365e
vpn 0.3.0 6c5cf5bf

View File

@@ -17,10 +17,10 @@ type: application
# 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.14.0
version: 0.13.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: 0.14.0
appVersion: 0.13.0

View File

@@ -6,12 +6,14 @@ metadata:
name: {{ include "virtual-machine.fullname" . }}
labels:
{{- include "virtual-machine.labels" . | nindent 4 }}
{{- if eq .Values.externalMethod "WholeIP" }}
annotations:
networking.cozystack.io/wholeIP: "true"
{{- end }}
spec:
type: {{ ternary "LoadBalancer" "ClusterIP" .Values.external }}
externalTrafficPolicy: Local
{{- if ((include "cozy-lib.network.disableLoadBalancerNodePorts" $) | fromYaml) }}
{{- if (include "cozy-lib.network.disableLoadBalancerNodePorts" $ | fromYaml) }}
allocateLoadBalancerNodePorts: false
{{- end }}
selector:
@@ -27,27 +29,3 @@ spec:
{{- end }}
{{- end }}
{{- end }}
---
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: {{ include "virtual-machine.fullname" . }}
spec:
endpointSelector:
matchLabels:
{{- include "virtual-machine.selectorLabels" . | nindent 6 }}
ingress:
- fromEntities:
- cluster
- fromEntities:
- world
{{- if eq .Values.externalMethod "PortList" }}
toPorts:
- ports:
{{- range .Values.externalPorts }}
- port: {{ quote . }}
{{- end }}
{{- end }}
egress:
- toEntities:
- world

View File

@@ -51,7 +51,7 @@ spec:
restartPolicy: Never
containers:
- name: update-resources
image: docker.io/alpine/k8s:1.33.4
image: bitnami/kubectl:latest
resources:
requests:
memory: "16Mi"

View File

@@ -62,7 +62,6 @@ spec:
template:
metadata:
annotations:
policy.cozystack.io/allow-external-communication: "false"
kubevirt.io/allow-pod-bridge-network-live-migration: "true"
labels:
{{- include "virtual-machine.labels" . | nindent 8 }}

View File

@@ -19,7 +19,7 @@ spec:
backoffLimit: 1
containers:
- name: resize
image: docker.io/alpine/k8s:1.33.4
image: bitnami/kubectl
command: ["sh", "-xec"]
args:
- |

View File

@@ -17,10 +17,10 @@ type: application
# 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.12.0
version: 0.11.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: 0.12.0
appVersion: 0.11.0

View File

@@ -6,12 +6,14 @@ metadata:
name: {{ include "virtual-machine.fullname" . }}
labels:
{{- include "virtual-machine.labels" . | nindent 4 }}
{{- if eq .Values.externalMethod "WholeIP" }}
annotations:
networking.cozystack.io/wholeIP: "true"
{{- end }}
spec:
type: {{ ternary "LoadBalancer" "ClusterIP" .Values.external }}
externalTrafficPolicy: Local
{{- if ((include "cozy-lib.network.disableLoadBalancerNodePorts" $) | fromYaml) }}
{{- if (include "cozy-lib.network.disableLoadBalancerNodePorts" $ | fromYaml) }}
allocateLoadBalancerNodePorts: false
{{- end }}
selector:
@@ -27,27 +29,3 @@ spec:
{{- end }}
{{- end }}
{{- end }}
---
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: {{ include "virtual-machine.fullname" . }}
spec:
endpointSelector:
matchLabels:
{{- include "virtual-machine.selectorLabels" . | nindent 6 }}
ingress:
- fromEntities:
- cluster
- fromEntities:
- world
{{- if eq .Values.externalMethod "PortList" }}
toPorts:
- ports:
{{- range .Values.externalPorts }}
- port: {{ quote . }}
{{- end }}
{{- end }}
egress:
- toEntities:
- world

View File

@@ -41,7 +41,7 @@ spec:
restartPolicy: Never
containers:
- name: update-resources
image: docker.io/alpine/k8s:1.33.4
image: bitnami/kubectl:latest
resources:
requests:
memory: "16Mi"

View File

@@ -26,7 +26,6 @@ spec:
template:
metadata:
annotations:
policy.cozystack.io/allow-external-communication: "false"
kubevirt.io/allow-pod-bridge-network-live-migration: "true"
labels:
{{- include "virtual-machine.labels" . | nindent 8 }}

View File

@@ -34,7 +34,7 @@ FROM alpine:3.22
RUN wget -O- https://github.com/cozystack/cozypkg/raw/refs/heads/main/hack/install.sh | sh -s -- -v 1.1.0
RUN apk add --no-cache make kubectl coreutils git jq
RUN apk add --no-cache make kubectl coreutils
COPY --from=builder /src/scripts /cozystack/scripts
COPY --from=builder /src/packages/core /cozystack/packages/core

View File

@@ -1,2 +1,2 @@
cozystack:
image: ghcr.io/cozystack/cozystack/installer:v0.36.0-beta.4@sha256:51cb9828af3bdceac289de3b1161625065db22535a961958530e9c751880ee96
image: ghcr.io/cozystack/cozystack/installer:v0.35.4@sha256:c60b2026347aab1d13c12d1f6432aba0cc89025786502f82b5bc34eac746efdb

View File

@@ -70,12 +70,6 @@ releases:
privileged: true
dependsOn: [cilium,kubeovn,cert-manager]
- name: kubeovn-plunger
releaseName: kubeovn-plunger
chart: cozy-kubeovn-plunger
namespace: cozy-kubeovn
dependsOn: [cilium,kubeovn]
- name: cozy-proxy
releaseName: cozystack
chart: cozy-cozy-proxy

View File

@@ -1,2 +1,2 @@
e2e:
image: ghcr.io/cozystack/cozystack/e2e-sandbox:v0.36.0-beta.4@sha256:c70ad3321fc14ca831c84bf6e7e6e5409bfe8130703173c277ca51db740c6cb3
image: ghcr.io/cozystack/cozystack/e2e-sandbox:v0.35.4@sha256:3906424ea24eec9043ac6781201bd912f833a9cfb646e48c0d451446e371025d

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/matchbox:v0.36.0-beta.4@sha256:764c547a352c9c1d0442e43cdfd0ef50b216bd7f6e5514c777b1d90c4d95da92
ghcr.io/cozystack/cozystack/matchbox:v0.35.4@sha256:d1ce10bb0be21d6b5a6d5aa734ec88b04b2979715f73fc083becd12791c1ea37

View File

@@ -3,4 +3,4 @@ name: etcd
description: Storage for Kubernetes clusters
icon: /logos/etcd.svg
type: application
version: 2.10.1
version: 2.10.0

View File

@@ -56,9 +56,6 @@ spec:
{{- end }}
{{- if $rawConstraints }}
{{- $rawConstraints | fromYaml | toYaml | nindent 6 }}
labelSelector:
matchLabels:
app.kubernetes.io/instance: etcd
{{- else }}
topologySpreadConstraints:
- maxSkew: 1

View File

@@ -25,7 +25,7 @@ spec:
serviceAccountName: etcd-hook
containers:
- name: kubectl
image: docker.io/alpine/k8s:1.33.4
image: bitnami/kubectl:latest
command:
- sh
args:

View File

@@ -3,4 +3,4 @@ name: ingress
description: NGINX Ingress Controller
icon: /logos/ingress-nginx.svg
type: application
version: 1.9.0
version: 1.8.0

View File

@@ -4,13 +4,9 @@
### Common parameters
| Name | Description | Type | Value |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ | ----------- | ------- |
| `replicas` | Number of ingress-nginx replicas | `int` | `2` |
| `whitelist` | List of client networks | `[]*string` | `[]` |
| `cloudflareProxy` | Restoring original visitor IPs when Cloudflare proxied is enabled | `bool` | `false` |
| `resources` | Explicit CPU and memory configuration for each ingress-nginx replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `{}` |
| `resources.cpu` | CPU available to each replica | `*quantity` | `null` |
| `resources.memory` | Memory (RAM) available to each replica | `*quantity` | `null` |
| `resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `micro` |
| Name | Description | Type | Value |
| ---------------- | ----------------------------------------------------------------- | ----------- | ------- |
| `replicas` | Number of ingress-nginx replicas | `int` | `2` |
| `whitelist` | List of client networks | `[]*string` | `[]` |
| `clouflareProxy` | Restoring original visitor IPs when Cloudflare proxied is enabled | `bool` | `false` |

View File

@@ -15,21 +15,14 @@ spec:
name: cozystack-system
namespace: cozy-system
version: '>= 0.0.0-0'
install:
remediation:
retries: -1
upgrade:
remediation:
retries: -1
interval: 1m0s
timeout: 10m0s
timeout: 5m0s
values:
ingress-nginx:
fullnameOverride: {{ trimPrefix "tenant-" .Release.Namespace }}-ingress
controller:
replicaCount: {{ .Values.replicas }}
ingressClass: {{ .Release.Namespace }}
resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.resourcesPreset .Values.resources $) | nindent 10 }}
ingressClassResource:
name: {{ .Release.Namespace }}
controllerValue: k8s.io/ingress-nginx-{{ .Release.Namespace }}
@@ -50,12 +43,12 @@ spec:
type: LoadBalancer
externalTrafficPolicy: Local
{{- end }}
{{- if or .Values.whitelist .Values.cloudflareProxy }}
{{- if or .Values.whitelist .Values.clouflareProxy }}
config:
{{- with .Values.whitelist }}
whitelist-source-range: "{{ join "," . }}"
{{- end }}
{{- if .Values.cloudflareProxy }}
{{- if .Values.clouflareProxy }}
set_real_ip_from: "{{ include "ingress.cloudflare-ips" . }}"
use-forwarded-headers: "true"
server-snippet: "real_ip_header CF-Connecting-IP;"

View File

@@ -2,7 +2,7 @@
"title": "Chart Values",
"type": "object",
"properties": {
"cloudflareProxy": {
"clouflareProxy": {
"description": "Restoring original visitor IPs when Cloudflare proxied is enabled",
"type": "boolean",
"default": false
@@ -12,53 +12,6 @@
"type": "integer",
"default": 2
},
"resources": {
"description": "Explicit CPU and memory configuration for each ingress-nginx replica. When left empty, the preset defined in `resourcesPreset` is applied.",
"type": "object",
"default": {},
"properties": {
"cpu": {
"description": "CPU available to each replica",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"x-kubernetes-int-or-string": true
},
"memory": {
"description": "Memory (RAM) available to each replica",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"x-kubernetes-int-or-string": true
}
}
},
"resourcesPreset": {
"description": "Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`.",
"type": "string",
"default": "micro",
"enum": [
"nano",
"micro",
"small",
"medium",
"large",
"xlarge",
"2xlarge"
]
},
"whitelist": {
"description": "List of client networks",
"type": "array",

View File

@@ -11,16 +11,5 @@ replicas: 2
## - "10.100.0.0/16"
whitelist: []
## @param cloudflareProxy {bool} Restoring original visitor IPs when Cloudflare proxied is enabled
cloudflareProxy: false
## @param resources {*resources} Explicit CPU and memory configuration for each ingress-nginx replica. When left empty, the preset defined in `resourcesPreset` is applied.
## @field resources.cpu {*quantity} CPU available to each replica
## @field resources.memory {*quantity} Memory (RAM) available to each replica
## Example:
## resources:
## cpu: 4000m
## memory: 4Gi
resources: {}
## @param resourcesPreset {string enum:"nano,micro,small,medium,large,xlarge,2xlarge"} Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`.
resourcesPreset: "micro"
## @param clouflareProxy {bool} Restoring original visitor IPs when Cloudflare proxied is enabled
clouflareProxy: false

View File

@@ -16,7 +16,7 @@ type: application
# 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.7.0
version: 0.6.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

View File

@@ -4,55 +4,19 @@
### Common parameters
| Name | Description | Type | Value |
| ------------------- | ------------------------------------------------------------------------------------------------------ | --------- | -------- |
| `host` | The hostname used to access the SeaweedFS externally (defaults to 's3' subdomain for the tenant host). | `*string` | `""` |
| `topology` | The topology of the SeaweedFS cluster. (allowed values: Simple, MultiZone, Client) | `string` | `Simple` |
| `replicationFactor` | Replication factor: number of replicas for each volume in the SeaweedFS cluster. | `int` | `2` |
### SeaweedFS Components Configuration
| Name | Description | Type | Value |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | ------- |
| `db` | Database Configuration | `object` | `{}` |
| `db.replicas` | Number of database replicas | `*int` | `2` |
| `db.size` | Persistent Volume size | `*quantity` | `10Gi` |
| `db.storageClass` | StorageClass used to store the data | `*string` | `""` |
| `db.resources` | Explicit CPU and memory configuration for the database. When left empty, the preset defined in `resourcesPreset` is applied. | `object` | `{}` |
| `db.resources.cpu` | The number of CPU cores allocated | `*quantity` | `null` |
| `db.resources.memory` | The amount of memory allocated | `*quantity` | `null` |
| `db.resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `small` |
| `master` | Master service configuration | `*object` | `{}` |
| `master.replicas` | Number of master replicas | `*int` | `3` |
| `master.resources` | Explicit CPU and memory configuration for the master. When left empty, the preset defined in `resourcesPreset` is applied. | `object` | `{}` |
| `master.resources.cpu` | The number of CPU cores allocated | `*quantity` | `null` |
| `master.resources.memory` | The amount of memory allocated | `*quantity` | `null` |
| `master.resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `small` |
| `filer` | Filer service configuration | `*object` | `{}` |
| `filer.replicas` | Number of filer replicas | `*int` | `2` |
| `filer.resources` | Explicit CPU and memory configuration for the filer. When left empty, the preset defined in `resourcesPreset` is applied. | `object` | `{}` |
| `filer.resources.cpu` | The number of CPU cores allocated | `*quantity` | `null` |
| `filer.resources.memory` | The amount of memory allocated | `*quantity` | `null` |
| `filer.resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `small` |
| `filer.grpcHost` | The hostname used to expose or access the filer service externally. | `*string` | `""` |
| `filer.grpcPort` | The port used to access the filer service externally. | `*int` | `443` |
| `filer.whitelist` | A list of IP addresses or CIDR ranges that are allowed to access the filer service. | `[]*string` | `[]` |
| `volume` | Volume service configuration | `*object` | `{}` |
| `volume.replicas` | Number of volume replicas | `*int` | `2` |
| `volume.size` | Persistent Volume size | `*quantity` | `10Gi` |
| `volume.storageClass` | StorageClass used to store the data | `*string` | `""` |
| `volume.resources` | Explicit CPU and memory configuration for the volume. When left empty, the preset defined in `resourcesPreset` is applied. | `object` | `{}` |
| `volume.resources.cpu` | The number of CPU cores allocated | `*quantity` | `null` |
| `volume.resources.memory` | The amount of memory allocated | `*quantity` | `null` |
| `volume.resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `small` |
| `volume.zones` | A map of zones for MultiZone topology. Each zone can have its own number of replicas and size. | `map[string]object` | `{}` |
| `volume.zones.replicas` | Number of replicas in the zone | `*int` | `null` |
| `volume.zones.size` | Zone storage size | `*quantity` | `null` |
| `s3` | S3 service configuration | `*object` | `{}` |
| `s3.replicas` | Number of s3 replicas | `*int` | `2` |
| `s3.resources` | Explicit CPU and memory configuration for the s3. When left empty, the preset defined in `resourcesPreset` is applied. | `object` | `{}` |
| `s3.resources.cpu` | The number of CPU cores allocated | `*quantity` | `null` |
| `s3.resources.memory` | The amount of memory allocated | `*quantity` | `null` |
| `s3.resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `small` |
| Name | Description | Type | Value |
| ---------------------- | ------------------------------------------------------------------------------------------------------ | ------------------- | -------- |
| `host` | The hostname used to access the SeaweedFS externally (defaults to 's3' subdomain for the tenant host). | `*string` | `""` |
| `topology` | The topology of the SeaweedFS cluster. (allowed values: Simple, MultiZone, Client) | `string` | `Simple` |
| `replicationFactor` | Replication factor: number of replicas for each volume in the SeaweedFS cluster. | `int` | `2` |
| `replicas` | Number of replicas | `int` | `2` |
| `size` | Persistent Volume size | `quantity` | `10Gi` |
| `storageClass` | StorageClass used to store the data | `*string` | `""` |
| `zones` | A map of zones for MultiZone topology. Each zone can have its own number of replicas and size. | `map[string]object` | `{...}` |
| `zones[name].replicas` | Number of replicas in the zone | `int` | `0` |
| `zones[name].size` | Zone storage size | `quantity` | `""` |
| `filer` | Filer service configuration | `*object` | `{}` |
| `filer.grpcHost` | The hostname used to expose or access the filer service externally. | `*string` | `""` |
| `filer.grpcPort` | The port used to access the filer service externally. | `*int` | `443` |
| `filer.whitelist` | A list of IP addresses or CIDR ranges that are allowed to access the filer service. | `[]*string` | `[]` |

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.36.0-beta.4@sha256:24451989b15b6801b33ad355a5507307d0333bf9afd240f1db0aca9c92f6b2ad
ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.35.4@sha256:f59d7755c1e1a4e3ab24affc1a76f095c03e57a86986dc4059ba7c154f842a4c

View File

@@ -9,11 +9,11 @@
{{- fail "Invalid value for .Values.replicationFactor. Must be at least 1." }}
{{- end }}
{{- if eq .Values.topology "MultiZone" }}
{{- if (eq (len .Values.volume.zones) 0) }}
{{- if (eq (len .Values.zones) 0) }}
{{- fail "Zones must be defined for MultiZone topology." }}
{{- end }}
{{- if and (hasKey .Values.volume "zones") (gt (int .Values.replicationFactor) (len .Values.volume.zones)) }}
{{- fail "replicationFactor must be less than or equal to the number of zones defined in .Values.volume.zones." }}
{{- if and (hasKey .Values "zones") (gt (int .Values.replicationFactor) (len .Values.zones)) }}
{{- fail "replicationFactor must be less than or equal to the number of zones defined in .Values.zones." }}
{{- end }}
{{- end }}
@@ -51,24 +51,11 @@ spec:
name: cozystack-system
namespace: cozy-system
version: '>= 0.0.0-0'
install:
remediation:
retries: -1
upgrade:
remediation:
retries: -1
interval: 1m0s
timeout: 10m0s
timeout: 5m0s
values:
global:
serviceAccountName: "{{ .Release.Namespace }}-seaweedfs"
db:
replicas: {{ .Values.db.replicas }}
resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.db.resourcesPreset .Values.db.resources $) | nindent 8 }}
size: {{ .Values.db.size }}
{{- with .Values.db.storageClass }}
storageClass: {{ . }}
{{- end }}
seaweedfs:
master:
{{ if eq .Values.topology "Simple" }}
@@ -76,25 +63,36 @@ spec:
{{- else if eq .Values.topology "MultiZone" }}
defaultReplicaPlacement: "{{ sub .Values.replicationFactor 1 }}00"
{{- end }}
replicas: {{ .Values.master.replicas }}
resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.master.resourcesPreset .Values.master.resources $) | nindent 10 }}
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
volume:
{{ if eq .Values.topology "MultiZone" }}
enabled: false
{{- end }}
replicas: {{ .Values.volume.replicas }}
resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.volume.resourcesPreset .Values.volume.resources $) | nindent 10 }}
replicas: {{ .Values.replicas }}
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
dataDirs:
- name: data1
type: "persistentVolumeClaim"
size: "{{ .Values.volume.size }}"
{{- with .Values.volume.storageClass }}
size: "{{ .Values.size }}"
{{- with .Values.storageClass }}
storageClass: {{ . }}
{{- end }}
maxVolumes: 0
{{ if eq .Values.topology "MultiZone" }}
volumes:
{{- range $zoneName, $zone := .Values.volume.zones }}
{{- range $zoneName, $zone := .Values.zones }}
{{ $zoneName }}:
{{ with $zone.replicas }}
replicas: {{ . }}
@@ -109,8 +107,8 @@ spec:
{{- end }}
{{- if $zone.storageClass }}
storageClass: {{ $zone.storageClass }}
{{- else if $.Values.volume.storageClass }}
storageClass: {{ $.Values.volume.storageClass }}
{{- else if $.Values.storageClass }}
storageClass: {{ $.Values.storageClass }}
{{- end }}
maxVolumes: 0
nodeSelector: |
@@ -126,7 +124,13 @@ spec:
{{- end }}
s3:
domainName: {{ .Values.host | default (printf "s3.%s" $host) }}
resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.filer.resourcesPreset .Values.filer.resources $) | nindent 10 }}
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
s3:
ingress:
className: {{ $ingress }}
@@ -140,7 +144,6 @@ spec:
- hosts:
- {{ .Values.host | default (printf "s3.%s" $host) }}
secretName: {{ .Release.Name }}-s3-ingress-tls
resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.s3.resourcesPreset .Values.s3.resources $) | nindent 10 }}
cosi:
driverName: "{{ .Release.Namespace }}.seaweedfs.objectstorage.k8s.io"
bucketClassName: "{{ .Release.Namespace }}"
@@ -157,8 +160,8 @@ kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}-master
spec:
replicas: {{ .Values.master.replicas }}
minReplicas: {{ div .Values.master.replicas 2 | add1 }}
replicas: 3
minReplicas: 2
kind: seaweedfs
type: master
selector:
@@ -171,7 +174,7 @@ kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}-filer
spec:
replicas: {{ .Values.filer.replicas }}
replicas: 2
minReplicas: 1
kind: seaweedfs
type: filer
@@ -179,47 +182,27 @@ spec:
app.kubernetes.io/component: filer
app.kubernetes.io/name: seaweedfs
version: {{ $.Chart.Version }}
{{ if eq .Values.topology "Simple" }}
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}-volume
spec:
replicas: {{ .Values.volume.replicas }}
minReplicas: 1
replicas: {{ .Values.replicas }}
minReplicas: {{ div .Values.replicas 2 | add1 }}
kind: seaweedfs
type: volume
selector:
app.kubernetes.io/component: volume
app.kubernetes.io/name: seaweedfs
version: {{ $.Chart.Version }}
{{- else if eq .Values.topology "MultiZone" }}
{{- range $zoneName, $zoneSpec := .Values.volume.zones }}
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}-volume-{{ $zoneName }}
spec:
replicas: {{ default $.Values.volume.replicas $zoneSpec.replicas }}
minReplicas: 1
kind: seaweedfs
type: volume
selector:
app.kubernetes.io/component: volume-{{ $zoneName }}
app.kubernetes.io/name: seaweedfs
version: {{ $.Chart.Version }}
{{- end }}
{{- end }}
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}-db
spec:
replicas: {{ .Values.db.replicas }}
replicas: 2
minReplicas: 1
kind: seaweedfs
type: postgres
@@ -227,18 +210,4 @@ spec:
cnpg.io/cluster: seaweedfs-db
cnpg.io/podRole: instance
version: {{ $.Chart.Version }}
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}-s3
spec:
replicas: {{ .Values.s3.replicas }}
minReplicas: 1
kind: seaweedfs
type: s3
selector:
app.kubernetes.io/component: s3
app.kubernetes.io/name: seaweedfs
version: {{ $.Chart.Version }}
{{- end }}

View File

@@ -0,0 +1,68 @@
{{- if not (eq .Values.topology "Client") }}
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: {{ .Release.Name }}-filer
spec:
targetRef:
apiVersion: apps/v1
kind: StatefulSet
name: {{ .Release.Name }}-filer
updatePolicy:
updateMode: Auto
resourcePolicy:
containerPolicies:
- containerName: seaweedfs
minAllowed:
cpu: 25m
memory: 64Mi
maxAllowed:
cpu: "1"
memory: 2048Mi
---
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: {{ .Release.Name }}-master
spec:
targetRef:
apiVersion: apps/v1
kind: StatefulSet
name: {{ .Release.Name }}-master
updatePolicy:
updateMode: Auto
resourcePolicy:
containerPolicies:
- containerName: seaweedfs
minAllowed:
cpu: 25m
memory: 64Mi
maxAllowed:
cpu: "1"
memory: 2048Mi
---
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: {{ .Release.Name }}-volume
spec:
targetRef:
apiVersion: apps/v1
kind: StatefulSet
name: {{ .Release.Name }}-volume
updatePolicy:
updateMode: Auto
resourcePolicy:
containerPolicies:
- containerName: seaweedfs
minAllowed:
cpu: 25m
memory: 64Mi
maxAllowed:
cpu: "1"
memory: 2048Mi
{{- end }}

View File

@@ -2,108 +2,14 @@
"title": "Chart Values",
"type": "object",
"properties": {
"db": {
"description": "Database Configuration",
"type": "object",
"default": {
"replicas": 2,
"resources": {},
"resourcesPreset": "small",
"size": "10Gi",
"storageClass": ""
},
"required": [
"resources",
"resourcesPreset"
],
"properties": {
"replicas": {
"description": "Number of database replicas",
"type": "integer",
"default": 2
},
"resources": {
"description": "Explicit CPU and memory configuration for the database. When left empty, the preset defined in `resourcesPreset` is applied.",
"type": "object",
"default": {},
"properties": {
"cpu": {
"description": "The number of CPU cores allocated",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"x-kubernetes-int-or-string": true
},
"memory": {
"description": "The amount of memory allocated",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"x-kubernetes-int-or-string": true
}
}
},
"resourcesPreset": {
"description": "Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`.",
"type": "string",
"default": "small",
"enum": [
"nano",
"micro",
"small",
"medium",
"large",
"xlarge",
"2xlarge"
]
},
"size": {
"description": "Persistent Volume size",
"default": "10Gi",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"x-kubernetes-int-or-string": true
},
"storageClass": {
"description": "StorageClass used to store the data",
"type": "string"
}
}
},
"filer": {
"description": "Filer service configuration",
"type": "object",
"default": {
"grpcHost": "",
"grpcPort": 443,
"replicas": 2,
"resources": {},
"resourcesPreset": "small",
"whitelist": {}
},
"required": [
"resources",
"resourcesPreset"
],
"properties": {
"grpcHost": {
"description": "The hostname used to expose or access the filer service externally.",
@@ -114,58 +20,6 @@
"type": "integer",
"default": 443
},
"replicas": {
"description": "Number of filer replicas",
"type": "integer",
"default": 2
},
"resources": {
"description": "Explicit CPU and memory configuration for the filer. When left empty, the preset defined in `resourcesPreset` is applied.",
"type": "object",
"default": {},
"properties": {
"cpu": {
"description": "The number of CPU cores allocated",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"x-kubernetes-int-or-string": true
},
"memory": {
"description": "The amount of memory allocated",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"x-kubernetes-int-or-string": true
}
}
},
"resourcesPreset": {
"description": "Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`.",
"type": "string",
"default": "small",
"enum": [
"nano",
"micro",
"small",
"medium",
"large",
"xlarge",
"2xlarge"
]
},
"whitelist": {
"description": "A list of IP addresses or CIDR ranges that are allowed to access the filer service.",
"type": "array",
@@ -180,144 +34,33 @@
"description": "The hostname used to access the SeaweedFS externally (defaults to 's3' subdomain for the tenant host).",
"type": "string"
},
"master": {
"description": "Master service configuration",
"type": "object",
"default": {
"replicas": 3,
"resources": {},
"resourcesPreset": "small"
},
"required": [
"resources",
"resourcesPreset"
],
"properties": {
"replicas": {
"description": "Number of master replicas",
"type": "integer",
"default": 3
},
"resources": {
"description": "Explicit CPU and memory configuration for the master. When left empty, the preset defined in `resourcesPreset` is applied.",
"type": "object",
"default": {},
"properties": {
"cpu": {
"description": "The number of CPU cores allocated",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"x-kubernetes-int-or-string": true
},
"memory": {
"description": "The amount of memory allocated",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"x-kubernetes-int-or-string": true
}
}
},
"resourcesPreset": {
"description": "Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`.",
"type": "string",
"default": "small",
"enum": [
"nano",
"micro",
"small",
"medium",
"large",
"xlarge",
"2xlarge"
]
}
}
"replicas": {
"description": "Number of replicas",
"type": "integer",
"default": 2
},
"replicationFactor": {
"description": "Replication factor: number of replicas for each volume in the SeaweedFS cluster.",
"type": "integer",
"default": 2
},
"s3": {
"description": "S3 service configuration",
"type": "object",
"default": {
"replicas": 2,
"resources": {},
"resourcesPreset": "small"
},
"required": [
"resources",
"resourcesPreset"
],
"properties": {
"replicas": {
"description": "Number of s3 replicas",
"type": "integer",
"default": 2
"size": {
"description": "Persistent Volume size",
"default": "10Gi",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
"type": "integer"
},
"resources": {
"description": "Explicit CPU and memory configuration for the s3. When left empty, the preset defined in `resourcesPreset` is applied.",
"type": "object",
"default": {},
"properties": {
"cpu": {
"description": "The number of CPU cores allocated",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"x-kubernetes-int-or-string": true
},
"memory": {
"description": "The amount of memory allocated",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"x-kubernetes-int-or-string": true
}
}
},
"resourcesPreset": {
"description": "Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`.",
"type": "string",
"default": "small",
"enum": [
"nano",
"micro",
"small",
"medium",
"large",
"xlarge",
"2xlarge"
]
{
"type": "string"
}
}
],
"x-kubernetes-int-or-string": true
},
"storageClass": {
"description": "StorageClass used to store the data",
"type": "string"
},
"topology": {
"description": "The topology of the SeaweedFS cluster. (allowed values: Simple, MultiZone, Client)",
@@ -329,117 +72,33 @@
"Client"
]
},
"volume": {
"description": "Volume service configuration",
"zones": {
"description": "A map of zones for MultiZone topology. Each zone can have its own number of replicas and size.",
"type": "object",
"default": {
"replicas": 2,
"resources": {},
"resourcesPreset": "small",
"size": "10Gi",
"storageClass": "",
"zones": {}
},
"required": [
"resources",
"resourcesPreset"
],
"properties": {
"replicas": {
"description": "Number of volume replicas",
"type": "integer",
"default": 2
},
"resources": {
"description": "Explicit CPU and memory configuration for the volume. When left empty, the preset defined in `resourcesPreset` is applied.",
"type": "object",
"default": {},
"properties": {
"cpu": {
"description": "The number of CPU cores allocated",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"x-kubernetes-int-or-string": true
},
"memory": {
"description": "The amount of memory allocated",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"x-kubernetes-int-or-string": true
}
}
},
"resourcesPreset": {
"description": "Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`.",
"type": "string",
"default": "small",
"enum": [
"nano",
"micro",
"small",
"medium",
"large",
"xlarge",
"2xlarge"
]
},
"size": {
"description": "Persistent Volume size",
"default": "10Gi",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"x-kubernetes-int-or-string": true
},
"storageClass": {
"description": "StorageClass used to store the data",
"type": "string"
},
"zones": {
"description": "A map of zones for MultiZone topology. Each zone can have its own number of replicas and size.",
"type": "object",
"default": {},
"additionalProperties": {
"type": "object",
"properties": {
"replicas": {
"description": "Number of replicas in the zone",
"default": {},
"additionalProperties": {
"type": "object",
"required": [
"replicas",
"size"
],
"properties": {
"replicas": {
"description": "Number of replicas in the zone",
"type": "integer"
},
"size": {
"description": "Zone storage size",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
"type": "integer"
},
"size": {
"description": "Zone storage size",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"x-kubernetes-int-or-string": true
{
"type": "string"
}
}
],
"x-kubernetes-int-or-string": true
}
}
}

View File

@@ -4,93 +4,42 @@
host: ""
## @param topology {string enum:"Simple,MultiZone,Client"} The topology of the SeaweedFS cluster. (allowed values: Simple, MultiZone, Client)
##
topology: Simple
## @param replicationFactor {int} Replication factor: number of replicas for each volume in the SeaweedFS cluster.
replicationFactor: 2
## @section SeaweedFS Components Configuration
## @param replicas {int} Number of replicas
##
## @param db {db} Database Configuration
db:
## @field db.replicas {*int} Number of database replicas
## @field db.size {*quantity} Persistent Volume size
## @field db.storageClass {*string} StorageClass used to store the data
replicas: 2
size: "10Gi"
storageClass: ""
## @field db.resources {resources} Explicit CPU and memory configuration for the database. When left empty, the preset defined in `resourcesPreset` is applied.
## @field resources {*resources} Resource configuration for etcd
## @field resources.cpu {*quantity} The number of CPU cores allocated
## @field resources.memory {*quantity} The amount of memory allocated
## e.g:
## resources:
## cpu: 4000m
## memory: 4Gi
##
## @field db.resourcesPreset {string enum:"nano,micro,small,medium,large,xlarge,2xlarge"} Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`.
resources: {}
resourcesPreset: "small"
replicas: 2
## @param size {quantity} Persistent Volume size
size: 10Gi
## @param storageClass {*string} StorageClass used to store the data
storageClass: ""
## @param master {*master} Master service configuration
master:
## @field master.replicas {*int} Number of master replicas
## @field master.resources {resources} Explicit CPU and memory configuration for the master. When left empty, the preset defined in `resourcesPreset` is applied.
## @field master.resourcesPreset {string enum:"nano,micro,small,medium,large,xlarge,2xlarge"} Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`.
replicas: 3
resources: {}
resourcesPreset: "small"
## @param zones {map[string]zone} A map of zones for MultiZone topology. Each zone can have its own number of replicas and size.
## @field zone.replicas {int} Number of replicas in the zone
## @field zone.size {quantity} Zone storage size
## Example:
## zones:
## dc1:
## replicas: 2
## size: 10Gi
## dc2:
## replicas: 2
## size: 10Gi
## dc3:
## replicas: 2
## size: 10Gi
zones: {}
## @param filer {*filer} Filer service configuration
## @field filer.grpcHost {*string} The hostname used to expose or access the filer service externally.
## @field filer.grpcPort {*int} The port used to access the filer service externally.
## TODO: select a more appropriate type after resolving https://github.com/cozystack/cozyvalues-gen/issues/4
## @field filer.whitelist {[]*string} A list of IP addresses or CIDR ranges that are allowed to access the filer service.
filer:
## @field filer.replicas {*int} Number of filer replicas
## @field filer.resources {resources} Explicit CPU and memory configuration for the filer. When left empty, the preset defined in `resourcesPreset` is applied.
## @field filer.resourcesPreset {string enum:"nano,micro,small,medium,large,xlarge,2xlarge"} Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`.
replicas: 2
resources: {}
resourcesPreset: "small"
## @field filer.grpcHost {*string} The hostname used to expose or access the filer service externally.
## @field filer.grpcPort {*int} The port used to access the filer service externally.
## @field filer.whitelist {[]*string} A list of IP addresses or CIDR ranges that are allowed to access the filer service.
grpcHost: ""
grpcPort: 443
whitelist: []
## @param volume {*volume} Volume service configuration
volume:
## @field volume.replicas {*int} Number of volume replicas
## @field volume.size {*quantity} Persistent Volume size
## @field volume.storageClass {*string} StorageClass used to store the data
## @field volume.resources {resources} Explicit CPU and memory configuration for the volume. When left empty, the preset defined in `resourcesPreset` is applied.
## @field volume.resourcesPreset {string enum:"nano,micro,small,medium,large,xlarge,2xlarge"} Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`.
replicas: 2
size: 10Gi
storageClass: ""
resources: {}
resourcesPreset: "small"
## @field volume.zones {map[string]zone} A map of zones for MultiZone topology. Each zone can have its own number of replicas and size.
## @field zone.replicas {*int} Number of replicas in the zone
## @field zone.size {*quantity} Zone storage size
## Example:
## zones:
## dc1:
## replicas: 2
## size: 10Gi
## dc2:
## replicas: 2
## size: 10Gi
## dc3:
## replicas: 2
## size: 10Gi
zones: {}
## @param s3 {*s3} S3 service configuration
s3:
## @field s3.replicas {*int} Number of s3 replicas
## @field s3.resources {resources} Explicit CPU and memory configuration for the s3. When left empty, the preset defined in `resourcesPreset` is applied.
## @field s3.resourcesPreset {string enum:"nano,micro,small,medium,large,xlarge,2xlarge"} Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`.
replicas: 2
resources: {}
resourcesPreset: "small"

View File

@@ -16,8 +16,7 @@ etcd 2.7.0 632224a3
etcd 2.8.0 4369b031
etcd 2.9.0 8ddbe32e
etcd 2.9.1 c02a3818
etcd 2.10.0 7f477eec
etcd 2.10.1 HEAD
etcd 2.10.0 HEAD
info 1.0.0 93bdf411
info 1.0.1 632224a3
info 1.1.0 c02a3818
@@ -30,8 +29,7 @@ ingress 1.4.0 fd240701
ingress 1.5.0 93bdf411
ingress 1.6.0 632224a3
ingress 1.7.0 c02a3818
ingress 1.8.0 8f1975d1
ingress 1.9.0 HEAD
ingress 1.8.0 HEAD
monitoring 1.0.0 d7cfa53c
monitoring 1.1.0 25221fdc
monitoring 1.2.0 f81be075
@@ -64,5 +62,4 @@ seaweedfs 0.3.0 45a7416c
seaweedfs 0.4.0 632224a3
seaweedfs 0.4.1 8c86905b
seaweedfs 0.5.0 9584e5f5
seaweedfs 0.6.0 8f1975d1
seaweedfs 0.7.0 HEAD
seaweedfs 0.6.0 HEAD

View File

@@ -179,10 +179,8 @@
{{- range $section, $values := $res }}
{{- range $k, $v := $values }}
{{- $key := printf "%s.%s" $section $k }}
{{- if ne $key "limits.storage" }}
{{- $_ := set $out $key $v }}
{{- end }}
{{- $_ := set $out $key $v }}
{{- end }}
{{- end }}
{{- $out | toYaml }}
{{- dict "resourceQuotas" $out | toYaml }}
{{- end }}

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:b9a401defb90a822087e50e7ab6afd9b4db7e71728030f92c7d320ac46889053
ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:f4aa0e84ad934b1261f11c4e7f30ff20b6cbd9a0478430f768c21fa945bf9f8e

View File

@@ -1,3 +0,0 @@
apiVersion: v2
name: cozy-coredns
version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process

View File

@@ -1,10 +0,0 @@
export NAME=coredns
export NAMESPACE=kube-system
include ../../../scripts/package.mk
update:
rm -rf charts
helm repo add cozy-coredns https://coredns.github.io/helm
helm repo update cozy-coredns
helm pull cozy-coredns/coredns --untar --untardir charts

View File

@@ -1,23 +0,0 @@
# 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
*~
# Various IDEs
.project
.idea/
*.tmproj
OWNERS
*.tests

View File

@@ -1,24 +0,0 @@
annotations:
artifacthub.io/changes: |
- kind: changed
description: Bump to CoreDNS 1.12.3
apiVersion: v2
appVersion: 1.12.3
description: CoreDNS is a DNS server that chains plugins and provides Kubernetes DNS
Services
home: https://coredns.io
icon: https://coredns.io/images/CoreDNS_Colour_Horizontal.png
keywords:
- coredns
- dns
- kubedns
maintainers:
- name: mrueg
- name: haad
- name: hagaibarel
- name: shubham-cmyk
name: coredns
sources:
- https://github.com/coredns/coredns
type: application
version: 1.43.2

View File

@@ -1,316 +0,0 @@
# CoreDNS
[CoreDNS](https://coredns.io/) is a DNS server that chains plugins and provides DNS Services
# TL;DR;
```console
$ helm repo add coredns https://coredns.github.io/helm
$ helm --namespace=kube-system install coredns coredns/coredns
```
## Introduction
This chart bootstraps a [CoreDNS](https://github.com/coredns/coredns) deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. This chart will provide DNS Services and can be deployed in multiple configuration to support various scenarios listed below:
- CoreDNS as a cluster dns service and a drop-in replacement for Kube/SkyDNS. This is the default mode and CoreDNS is deployed as cluster-service in kube-system namespace. This mode is chosen by setting `isClusterService` to true.
- CoreDNS as an external dns service. In this mode CoreDNS is deployed as any kubernetes app in user specified namespace. The CoreDNS service can be exposed outside the cluster by using using either the NodePort or LoadBalancer type of service. This mode is chosen by setting `isClusterService` to false.
- CoreDNS as an external dns provider for kubernetes federation. This is a sub case of 'external dns service' which uses etcd plugin for CoreDNS backend. This deployment mode as a dependency on `etcd-operator` chart, which needs to be pre-installed.
## Prerequisites
- Kubernetes 1.10 or later
## Installing the Chart
The chart can be installed as follows:
```console
$ helm repo add coredns https://coredns.github.io/helm
$ helm --namespace=kube-system install coredns coredns/coredns
```
The command deploys CoreDNS on the Kubernetes cluster in the default configuration. The [configuration](#configuration) section lists various ways to override default configuration during deployment.
> **Tip**: List all releases using `helm list --all-namespaces`
## OCI installing
The chart can also be installed using the following:
```console
$ helm --namespace=kube-system install coredns oci://ghcr.io/coredns/charts/coredns --version 1.38.0
```
The command deploys the `1.38.0` version of CoreDNS on the Kubernetes cluster in the default configuration.
## Helm Unit Testing & Debugging Guide
This document explains how to write, run, and debug Helm unit tests for this chart using [helm-unittest](https://github.com/helm-unittest/helm-unittest).
---
### Prerequisites
Install the Helm unittest plugin:
```bash
helm plugin install https://github.com/helm-unittest/helm-unittest
```
### Running Unit Tests
Run all unit tests in the chart folder (e.g., `./coredns`):
```bash
helm unittest ./coredns
```
To output results in **JUnit XML** format:
```bash
mkdir -p test-results
helm unittest --strict \
--output-type JUnit \
--output-file test-results/helm-unittest-report.xml \
./coredns
```
---
### Debugging Helm Charts
Render the chart templates with real values to debug:
```bash
helm template ./coredns --debug
```
## YAML Intellisense in VS Code
Add this line at the top of your unit test YAML files for schema validation and autocompletion in VS Code:
```yaml
# yaml-language-server: $schema=https://raw.githubusercontent.com/helm-unittest/helm-unittest/main/schema/helm-testsuite.json
```
This improves YAML editing and error highlighting for test definitions.
## Uninstalling the Chart
To uninstall/delete the `coredns` deployment:
```console
$ helm uninstall coredns
```
The command removes all the Kubernetes components associated with the chart and deletes the release.
## Configuration
| Parameter | Description | Default |
| :--------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------- |
| `image.repository` | The image repository to pull from | coredns/coredns |
| `image.tag` | The image tag to pull from (derived from Chart.yaml) | `` |
| `image.pullPolicy` | Image pull policy | IfNotPresent |
| `image.pullSecrets` | Specify container image pull secrets | `[]` |
| `replicaCount` | Number of replicas | 1 |
| `resources.limits.cpu` | Container maximum CPU | `100m` |
| `resources.limits.memory` | Container maximum memory | `128Mi` |
| `resources.requests.cpu` | Container requested CPU | `100m` |
| `resources.requests.memory` | Container requested memory | `128Mi` |
| `serviceType` | Kubernetes Service type | `ClusterIP` |
| `prometheus.service.enabled` | Set this to `true` to create Service for Prometheus metrics | `false` |
| `prometheus.service.annotations` | Annotations to add to the metrics Service | `{prometheus.io/scrape: "true", prometheus.io/port: "9153"}` |
| `prometheus.service.selector` | Pod selector | `{}` |
| `prometheus.monitor.enabled` | Set this to `true` to create ServiceMonitor for Prometheus operator | `false` |
| `prometheus.monitor.additionalLabels` | Additional labels that can be used so ServiceMonitor will be discovered by Prometheus | {} |
| `prometheus.monitor.namespace` | Selector to select which namespaces the Endpoints objects are discovered from. | `""` |
| `prometheus.monitor.interval` | Scrape interval for polling the metrics endpoint. (E.g. "30s") | `""` |
| `prometheus.monitor.selector` | Service selector | `{}` |
| `service.clusterIP` | IP address to assign to service | `""` |
| `service.clusterIPs` | IP addresses to assign to service | `[]` |
| `service.loadBalancerIP` | IP address to assign to load balancer (if supported) | `""` |
| `service.externalIPs` | External IP addresses | [] |
| `service.externalTrafficPolicy` | Enable client source IP preservation | [] |
| `service.ipFamilyPolicy` | Service dual-stack policy | `""` |
| `service.annotations` | Annotations to add to service | {} |
| `service.selector` | Pod selector | `{}` |
| `service.trafficDistribution` | Service traffic routing strategy | |
| `serviceAccount.create` | If true, create & use serviceAccount | false |
| `serviceAccount.name` | If not set & create is true, use template fullname | |
| `rbac.create` | If true, create & use RBAC resources | true |
| `rbac.pspEnable` | Specifies whether a PodSecurityPolicy should be created. | `false` |
| `isClusterService` | Specifies whether chart should be deployed as cluster-service or normal k8s app. | true |
| `priorityClassName` | Name of Priority Class to assign pods | `""` |
| `securityContext` | securityContext definition for pods | capabilities.add.NET_BIND_SERVICE |
| `servers` | Configuration for CoreDNS and plugins | See values.yml |
| `livenessProbe.enabled` | Enable/disable the Liveness probe | `true` |
| `livenessProbe.initialDelaySeconds` | Delay before liveness probe is initiated | `60` |
| `livenessProbe.periodSeconds` | How often to perform the probe | `10` |
| `livenessProbe.timeoutSeconds` | When the probe times out | `5` |
| `livenessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | `5` |
| `livenessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed. | `1` |
| `readinessProbe.enabled` | Enable/disable the Readiness probe | `true` |
| `readinessProbe.initialDelaySeconds` | Delay before readiness probe is initiated | `30` |
| `readinessProbe.periodSeconds` | How often to perform the probe | `10` |
| `readinessProbe.timeoutSeconds` | When the probe times out | `5` |
| `readinessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | `5` |
| `readinessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed. | `1` |
| `affinity` | Affinity settings for pod assignment | {} |
| `nodeSelector` | Node labels for pod assignment | {} |
| `tolerations` | Tolerations for pod assignment | [] |
| `zoneFiles` | Configure custom Zone files | [] |
| `extraContainers` | Optional array of sidecar containers | [] |
| `extraVolumes` | Optional array of volumes to create | [] |
| `extraVolumeMounts` | Optional array of volumes to mount inside the CoreDNS container | [] |
| `extraSecrets` | Optional array of secrets to mount inside the CoreDNS container | [] |
| `env` | Optional array of environment variables for CoreDNS container | [] |
| `customLabels` | Optional labels for Deployment(s), Pod, Service, ServiceMonitor objects | {} |
| `customAnnotations` | Optional annotations for Deployment(s), Pod, Service, ServiceMonitor objects |
| `rollingUpdate.maxUnavailable` | Maximum number of unavailable replicas during rolling update | `1` |
| `rollingUpdate.maxSurge` | Maximum number of pods created above desired number of pods | `25%` |
| `podDisruptionBudget` | Optional PodDisruptionBudget | {} |
| `podAnnotations` | Optional Pod only Annotations | {} |
| `terminationGracePeriodSeconds` | Optional duration in seconds the pod needs to terminate gracefully. | 30 |
| `hpa.enabled` | Enable Hpa autoscaler instead of proportional one | `false` |
| `hpa.minReplicas` | Hpa minimum number of CoreDNS replicas | `1` |
| `hpa.maxReplicas` | Hpa maximum number of CoreDNS replicas | `2` |
| `hpa.metrics` | Metrics definitions used by Hpa to scale up and down | {} |
| `autoscaler.enabled` | Optionally enabled a cluster-proportional-autoscaler for CoreDNS | `false` |
| `autoscaler.coresPerReplica` | Number of cores in the cluster per CoreDNS replica | `256` |
| `autoscaler.nodesPerReplica` | Number of nodes in the cluster per CoreDNS replica | `16` |
| `autoscaler.min` | Min size of replicaCount | 0 |
| `autoscaler.max` | Max size of replicaCount | 0 (aka no max) |
| `autoscaler.includeUnschedulableNodes` | Should the replicas scale based on the total number or only schedulable nodes | `false` |
| `autoscaler.preventSinglePointFailure` | If true does not allow single points of failure to form | `true` |
| `autoscaler.customFlags` | A list of custom flags to pass into cluster-proportional-autoscaler | (no args) |
| `autoscaler.image.repository` | The image repository to pull autoscaler from | registry.k8s.io/cpa/cluster-proportional-autoscaler |
| `autoscaler.image.tag` | The image tag to pull autoscaler from | `1.8.5` |
| `autoscaler.image.pullPolicy` | Image pull policy for the autoscaler | IfNotPresent |
| `autoscaler.image.pullSecrets` | Specify container image pull secrets | `[]` |
| `autoscaler.priorityClassName` | Optional priority class for the autoscaler pod. `priorityClassName` used if not set. | `""` |
| `autoscaler.affinity` | Affinity settings for pod assignment for autoscaler | {} |
| `autoscaler.nodeSelector` | Node labels for pod assignment for autoscaler | {} |
| `autoscaler.tolerations` | Tolerations for pod assignment for autoscaler | [] |
| `autoscaler.resources.limits.cpu` | Container maximum CPU for cluster-proportional-autoscaler | `20m` |
| `autoscaler.resources.limits.memory` | Container maximum memory for cluster-proportional-autoscaler | `10Mi` |
| `autoscaler.resources.requests.cpu` | Container requested CPU for cluster-proportional-autoscaler | `20m` |
| `autoscaler.resources.requests.memory` | Container requested memory for cluster-proportional-autoscaler | `10Mi` |
| `autoscaler.configmap.annotations` | Annotations to add to autoscaler config map. For example to stop CI renaming them | {} |
| `autoscaler.livenessProbe.enabled` | Enable/disable the Liveness probe | `true` |
| `autoscaler.livenessProbe.initialDelaySeconds` | Delay before liveness probe is initiated | `10` |
| `autoscaler.livenessProbe.periodSeconds` | How often to perform the probe | `5` |
| `autoscaler.livenessProbe.timeoutSeconds` | When the probe times out | `5` |
| `autoscaler.livenessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | `3` |
| `autoscaler.livenessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed. | `1` |
| `autoscaler.extraContainers` | Optional array of sidecar containers | [] |
| `deployment.enabled` | Optionally disable the main deployment and its respective resources. | `true` |
| `deployment.name` | Name of the deployment if `deployment.enabled` is true. Otherwise the name of an existing deployment for the autoscaler or HPA to target. | `""` |
| `deployment.annotations` | Annotations to add to the main deployment | `{}` |
| `deployment.selector` | Pod selector | `{}` |
| `clusterRole.nameOverride` | ClusterRole name override | |
See `values.yaml` for configuration notes. Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example,
```console
$ helm install coredns \
coredns/coredns \
--set rbac.create=false
```
The above command disables automatic creation of RBAC rules.
Alternatively, a YAML file that specifies the values for the above parameters can be provided while installing the chart. For example,
```console
$ helm install coredns coredns/coredns -f values.yaml
```
> **Tip**: You can use the default [values.yaml](/charts/coredns/values.yaml)
## Caveats
The chart will automatically determine which protocols to listen on based on
the protocols you define in your zones. This means that you could potentially
use both "TCP" and "UDP" on a single port.
Some cloud environments like "GCE" or "Azure container service" cannot
create external loadbalancers with both "TCP" and "UDP" protocols. So
When deploying CoreDNS with `serviceType="LoadBalancer"` on such cloud
environments, make sure you do not attempt to use both protocols at the same
time.
## Autoscaling
By setting `autoscaler.enabled = true` a
[cluster-proportional-autoscaler](https://github.com/kubernetes-incubator/cluster-proportional-autoscaler)
will be deployed. This will default to a coredns replica for every 256 cores, or
16 nodes in the cluster. These can be changed with `autoscaler.coresPerReplica`
and `autoscaler.nodesPerReplica`. When cluster is using large nodes (with more
cores), `coresPerReplica` should dominate. If using small nodes,
`nodesPerReplica` should dominate.
This also creates a ServiceAccount, ClusterRole, and ClusterRoleBinding for
the autoscaler deployment.
`replicaCount` is ignored if this is enabled.
By setting `hpa.enabled = true` a [Horizontal Pod Autoscaler](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/)
is enabled for Coredns deployment. This can scale number of replicas based on meitrics
like CpuUtilization, MemoryUtilization or Custom ones.
## Adopting existing CoreDNS resources
If you do not want to delete the existing CoreDNS resources in your cluster, you can adopt the resources into a release as of Helm 3.2.0.
You will also need to annotate and label your existing resources to allow Helm to assume control of them. See: https://github.com/helm/helm/pull/7649
```
annotations:
meta.helm.sh/release-name: your-release-name
meta.helm.sh/release-namespace: your-release-namespace
labels:
app.kubernetes.io/managed-by: Helm
```
Once you have annotated and labeled all the resources this chart specifies, you may need to locally template the chart and compare against existing manifest to ensure there are no changes/diffs.s If
you have been careful this should not diff and leave all the resources unmodified and now under management of helm.
Some values to investigate to help adopt your existing manifests to the Helm release are:
- k8sAppLabelOverride
- service.name
- customLabels
In some cases, you will need to orphan delete your existing deployment since selector labels are immutable.
```
kubectl delete deployment coredns --cascade=orphan
```
This will delete the deployment and leave the replicaset to ensure no downtime in the cluster. You will need to manually delete the replicaset AFTER Helm has released a new deployment.
Here is an example script to modify the annotations and labels of existing resources:
WARNING: Substitute YOUR_HELM_RELEASE_NAME_HERE with the name of your helm release.
```
#!/usr/bin/env bash
set -euo pipefail
for kind in config service serviceAccount; do
echo "setting annotations and labels on $kind/coredns"
kubectl -n kube-system annotate --overwrite $kind coredns meta.helm.sh/release-name=YOUR_HELM_RELEASE_NAME_HERE
kubectl -n kube-system annotate --overwrite $kind coredns meta.helm.sh/release-namespace=kube-system
kubectl -n kube-system label --overwrite $kind coredns app.kubernetes.io/managed-by=Helm
done
```
NOTE: Sometimes, previous deployments of kube-dns that have been migrated to CoreDNS still use kube-dns for the service name as well.
```
echo "setting annotations and labels on service/kube-dns"
kubectl -n kube-system annotate --overwrite service kube-dns meta.helm.sh/release-name=YOUR_HELM_RELEASE_NAME_HERE
kubectl -n kube-system annotate --overwrite service kube-dns meta.helm.sh/release-namespace=kube-system
kubectl -n kube-system label --overwrite service kube-dns app.kubernetes.io/managed-by=Helm
```

View File

@@ -1,30 +0,0 @@
{{- if .Values.isClusterService }}
CoreDNS is now running in the cluster as a cluster-service.
{{- else }}
CoreDNS is now running in the cluster.
It can be accessed using the below endpoint
{{- if contains "NodePort" .Values.serviceType }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "coredns.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo "$NODE_IP:$NODE_PORT"
{{- else if contains "LoadBalancer" .Values.serviceType }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status by running 'kubectl get svc -w {{ template "coredns.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "coredns.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo $SERVICE_IP
{{- else if contains "ClusterIP" .Values.serviceType }}
"{{ template "coredns.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local"
from within the cluster
{{- end }}
{{- end }}
It can be tested with the following:
1. Launch a Pod with DNS tools:
kubectl run -it --rm --restart=Never --image=infoblox/dnstools:latest dnstools
2. Query the DNS server:
/ # host kubernetes

View File

@@ -1,236 +0,0 @@
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "coredns.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).
*/}}
{{- define "coredns.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 -}}
{{/*
Common labels
*/}}
{{- define "coredns.labels" -}}
app.kubernetes.io/managed-by: {{ .Release.Service | quote }}
app.kubernetes.io/instance: {{ .Release.Name | quote }}
helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
{{- if .Values.isClusterService }}
k8s-app: {{ template "coredns.k8sapplabel" . }}
kubernetes.io/cluster-service: "true"
kubernetes.io/name: "CoreDNS"
{{- end }}
app.kubernetes.io/name: {{ template "coredns.name" . }}
{{- end -}}
{{/*
Common labels with autoscaler
*/}}
{{- define "coredns.labels.autoscaler" -}}
app.kubernetes.io/managed-by: {{ .Release.Service | quote }}
app.kubernetes.io/instance: {{ .Release.Name | quote }}
helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
{{- if .Values.isClusterService }}
k8s-app: {{ template "coredns.k8sapplabel" . }}-autoscaler
kubernetes.io/cluster-service: "true"
kubernetes.io/name: "CoreDNS"
{{- end }}
app.kubernetes.io/name: {{ template "coredns.name" . }}-autoscaler
{{- end -}}
{{/*
Allow k8s-app label to be overridden
*/}}
{{- define "coredns.k8sapplabel" -}}
{{- default .Chart.Name .Values.k8sAppLabelOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Generate the list of ports automatically from the server definitions
*/}}
{{- define "coredns.servicePorts" -}}
{{/* Set ports to be an empty dict */}}
{{- $ports := dict -}}
{{/* Iterate through each of the server blocks */}}
{{- range .Values.servers -}}
{{/* Capture port to avoid scoping awkwardness */}}
{{- $port := toString .port -}}
{{- $serviceport := default .port .servicePort -}}
{{/* If none of the server blocks has mentioned this port yet take note of it */}}
{{- if not (hasKey $ports $port) -}}
{{- $ports := set $ports $port (dict "istcp" false "isudp" false "serviceport" $serviceport) -}}
{{- end -}}
{{/* Retrieve the inner dict that holds the protocols for a given port */}}
{{- $innerdict := index $ports $port -}}
{{/*
Look at each of the zones and check which protocol they serve
At the moment the following are supported by CoreDNS:
UDP: dns://
TCP: tls://, grpc://, https://
*/}}
{{- range .zones -}}
{{- if has (default "" .scheme) (list "dns://" "") -}}
{{/* Optionally enable tcp for this service as well */}}
{{- if eq (default false .use_tcp) true }}
{{- $innerdict := set $innerdict "istcp" true -}}
{{- end }}
{{- $innerdict := set $innerdict "isudp" true -}}
{{- end -}}
{{- if has (default "" .scheme) (list "tls://" "grpc://" "https://") -}}
{{- $innerdict := set $innerdict "istcp" true -}}
{{- end -}}
{{- end -}}
{{/* If none of the zones specify scheme, default to dns:// udp */}}
{{- if and (not (index $innerdict "istcp")) (not (index $innerdict "isudp")) -}}
{{- $innerdict := set $innerdict "isudp" true -}}
{{- end -}}
{{- if .nodePort -}}
{{- $innerdict := set $innerdict "nodePort" .nodePort -}}
{{- end -}}
{{/* Write the dict back into the outer dict */}}
{{- $ports := set $ports $port $innerdict -}}
{{- end -}}
{{/* Write out the ports according to the info collected above */}}
{{- range $port, $innerdict := $ports -}}
{{- $portList := list -}}
{{- if index $innerdict "isudp" -}}
{{- $portList = append $portList (dict "port" (get $innerdict "serviceport") "protocol" "UDP" "name" (printf "udp-%s" $port) "targetPort" ($port | int)) -}}
{{- end -}}
{{- if index $innerdict "istcp" -}}
{{- $portList = append $portList (dict "port" (get $innerdict "serviceport") "protocol" "TCP" "name" (printf "tcp-%s" $port) "targetPort" ($port | int)) -}}
{{- end -}}
{{- range $portDict := $portList -}}
{{- if index $innerdict "nodePort" -}}
{{- $portDict := set $portDict "nodePort" (get $innerdict "nodePort" | int) -}}
{{- end -}}
{{- printf "- %s\n" (toJson $portDict) -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/*
Generate the list of ports automatically from the server definitions
*/}}
{{- define "coredns.containerPorts" -}}
{{/* Set ports to be an empty dict */}}
{{- $ports := dict -}}
{{/* Iterate through each of the server blocks */}}
{{- range .Values.servers -}}
{{/* Capture port to avoid scoping awkwardness */}}
{{- $port := toString .port -}}
{{/* If none of the server blocks has mentioned this port yet take note of it */}}
{{- if not (hasKey $ports $port) -}}
{{- $ports := set $ports $port (dict "istcp" false "isudp" false) -}}
{{- end -}}
{{/* Retrieve the inner dict that holds the protocols for a given port */}}
{{- $innerdict := index $ports $port -}}
{{/*
Look at each of the zones and check which protocol they serve
At the moment the following are supported by CoreDNS:
UDP: dns://
TCP: tls://, grpc://, https://
*/}}
{{- range .zones -}}
{{- if has (default "" .scheme) (list "dns://" "") -}}
{{/* Optionally enable tcp for this service as well */}}
{{- if eq (default false .use_tcp) true }}
{{- $innerdict := set $innerdict "istcp" true -}}
{{- end }}
{{- $innerdict := set $innerdict "isudp" true -}}
{{- end -}}
{{- if has (default "" .scheme) (list "tls://" "grpc://" "https://") -}}
{{- $innerdict := set $innerdict "istcp" true -}}
{{- end -}}
{{- end -}}
{{/* If none of the zones specify scheme, default to dns:// udp */}}
{{- if and (not (index $innerdict "istcp")) (not (index $innerdict "isudp")) -}}
{{- $innerdict := set $innerdict "isudp" true -}}
{{- end -}}
{{- if .hostPort -}}
{{- $innerdict := set $innerdict "hostPort" .hostPort -}}
{{- end -}}
{{/* Write the dict back into the outer dict */}}
{{- $ports := set $ports $port $innerdict -}}
{{/* Fetch port from the configuration if the prometheus section exists */}}
{{- range .plugins -}}
{{- if eq .name "prometheus" -}}
{{- $prometheus_addr := toString .parameters -}}
{{- $prometheus_addr_list := regexSplit ":" $prometheus_addr -1 -}}
{{- $prometheus_port := index $prometheus_addr_list 1 -}}
{{- $ports := set $ports $prometheus_port (dict "istcp" true "isudp" false) -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/* Write out the ports according to the info collected above */}}
{{- range $port, $innerdict := $ports -}}
{{- $portList := list -}}
{{- if index $innerdict "isudp" -}}
{{- $portList = append $portList (dict "containerPort" ($port | int) "protocol" "UDP" "name" (printf "udp-%s" $port)) -}}
{{- end -}}
{{- if index $innerdict "istcp" -}}
{{- $portList = append $portList (dict "containerPort" ($port | int) "protocol" "TCP" "name" (printf "tcp-%s" $port)) -}}
{{- end -}}
{{- range $portDict := $portList -}}
{{- if index $innerdict "hostPort" -}}
{{- $portDict := set $portDict "hostPort" (get $innerdict "hostPort" | int) -}}
{{- end -}}
{{- printf "- %s\n" (toJson $portDict) -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/*
Create the name of the service account to use
*/}}
{{- define "coredns.serviceAccountName" -}}
{{- if .Values.serviceAccount.create -}}
{{ default (include "coredns.fullname" .) .Values.serviceAccount.name }}
{{- else -}}
{{ default "default" .Values.serviceAccount.name }}
{{- end -}}
{{- end -}}
{{/*
Create the name of the service account to use
*/}}
{{- define "coredns.clusterRoleName" -}}
{{- if and .Values.clusterRole .Values.clusterRole.nameOverride -}}
{{ .Values.clusterRole.nameOverride }}
{{- else -}}
{{ template "coredns.fullname" . }}
{{- end -}}
{{- end -}}

View File

@@ -1,30 +0,0 @@
{{- if and .Values.autoscaler.enabled .Values.rbac.create }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ template "coredns.fullname" . }}-autoscaler
labels: {{- include "coredns.labels.autoscaler" . | nindent 4 }}
{{- if .Values.customLabels }}
{{ toYaml .Values.customLabels | indent 4 }}
{{- end }}
{{- with .Values.customAnnotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list","watch"]
- apiGroups: [""]
resources: ["replicationcontrollers/scale"]
verbs: ["get", "update"]
- apiGroups: ["extensions", "apps"]
resources: ["deployments/scale", "replicasets/scale"]
verbs: ["get", "update"]
# Remove the configmaps rule once below issue is fixed:
# kubernetes-incubator/cluster-proportional-autoscaler#16
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "create"]
{{- end }}

View File

@@ -1,36 +0,0 @@
{{- if and .Values.deployment.enabled .Values.rbac.create }}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ template "coredns.clusterRoleName" . }}
labels: {{- include "coredns.labels" . | nindent 4 }}
rules:
- apiGroups:
- ""
resources:
- endpoints
- services
- pods
- namespaces
verbs:
- list
- watch
- apiGroups:
- discovery.k8s.io
resources:
- endpointslices
verbs:
- list
- watch
{{- if .Values.rbac.pspEnable }}
- apiGroups:
- policy
- extensions
resources:
- podsecuritypolicies
verbs:
- use
resourceNames:
- {{ template "coredns.fullname" . }}
{{- end }}
{{- end }}

View File

@@ -1,23 +0,0 @@
{{- if and .Values.autoscaler.enabled .Values.rbac.create }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ template "coredns.fullname" . }}-autoscaler
labels: {{- include "coredns.labels.autoscaler" . | nindent 4 }}
{{- if .Values.customLabels }}
{{ toYaml .Values.customLabels | indent 4 }}
{{- end }}
{{- with .Values.customAnnotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ template "coredns.fullname" . }}-autoscaler
subjects:
- kind: ServiceAccount
name: {{ template "coredns.fullname" . }}-autoscaler
namespace: {{ .Release.Namespace }}
{{- end }}

View File

@@ -1,15 +0,0 @@
{{- if and .Values.deployment.enabled .Values.rbac.create }}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ template "coredns.clusterRoleName" . }}
labels: {{- include "coredns.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ template "coredns.clusterRoleName" . }}
subjects:
- kind: ServiceAccount
name: {{ template "coredns.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- end }}

View File

@@ -1,33 +0,0 @@
{{- if .Values.autoscaler.enabled }}
---
kind: ConfigMap
apiVersion: v1
metadata:
name: {{ template "coredns.fullname" . }}-autoscaler
namespace: {{ .Release.Namespace }}
labels: {{- include "coredns.labels.autoscaler" . | nindent 4 }}
{{- if .Values.customLabels }}
{{- toYaml .Values.customLabels | nindent 4 }}
{{- end }}
{{- if or .Values.autoscaler.configmap.annotations .Values.customAnnotations }}
annotations:
{{- if .Values.customAnnotations }}
{{- toYaml .Values.customAnnotations | nindent 4 }}
{{- end }}
{{- if .Values.autoscaler.configmap.annotations -}}
{{ toYaml .Values.autoscaler.configmap.annotations | nindent 4 }}
{{- end }}
{{- end }}
data:
# When cluster is using large nodes(with more cores), "coresPerReplica" should dominate.
# If using small nodes, "nodesPerReplica" should dominate.
linear: |-
{
"coresPerReplica": {{ .Values.autoscaler.coresPerReplica | float64 }},
"nodesPerReplica": {{ .Values.autoscaler.nodesPerReplica | float64 }},
"preventSinglePointFailure": {{ .Values.autoscaler.preventSinglePointFailure }},
"min": {{ .Values.autoscaler.min | int }},
"max": {{ .Values.autoscaler.max | int }},
"includeUnschedulableNodes": {{ .Values.autoscaler.includeUnschedulableNodes }}
}
{{- end }}

View File

@@ -1,37 +0,0 @@
{{- if .Values.deployment.enabled }}
{{- if not .Values.deployment.skipConfig }}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ template "coredns.fullname" . }}
namespace: {{ .Release.Namespace }}
labels: {{- include "coredns.labels" . | nindent 4 }}
{{- if .Values.customLabels }}
{{ toYaml .Values.customLabels | indent 4 }}
{{- end }}
{{- with .Values.customAnnotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
data:
Corefile: |-
{{- range $name, $conf := .Values.extraConfig }}
{{ $name }}{{ if $conf.parameters }} {{ $conf.parameters }}{{ end }}
{{- end }}
{{ range .Values.servers }}
{{- range $idx, $zone := .zones }}{{ if $idx }} {{ else }}{{ end }}{{ default "" $zone.scheme }}{{ default "." $zone.zone }}{{ else }}.{{ end -}}
{{- if .port }}:{{ .port }} {{ end -}}
{
{{- range .plugins }}
{{ .name }}{{ if .parameters }} {{ .parameters }}{{ end }}{{ if .configBlock }} {
{{ .configBlock | indent 12 }}
}{{ end }}
{{- end }}
}
{{ end }}
{{- range .Values.zoneFiles }}
{{ .filename }}: {{ toYaml .contents | indent 4 }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -1,98 +0,0 @@
{{- if and (.Values.autoscaler.enabled) (not .Values.hpa.enabled) }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ template "coredns.fullname" . }}-autoscaler
namespace: {{ .Release.Namespace }}
labels: {{- include "coredns.labels.autoscaler" . | nindent 4 }}
{{- if .Values.customLabels }}
{{ toYaml .Values.customLabels | indent 4 }}
{{- end }}
{{- with .Values.customAnnotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
selector:
matchLabels:
app.kubernetes.io/instance: {{ .Release.Name | quote }}
{{- if .Values.isClusterService }}
k8s-app: {{ template "coredns.k8sapplabel" . }}-autoscaler
{{- end }}
app.kubernetes.io/name: {{ template "coredns.name" . }}-autoscaler
template:
metadata:
labels:
{{- if .Values.isClusterService }}
{{- if not (hasKey .Values.customLabels "k8s-app")}}
k8s-app: {{ template "coredns.k8sapplabel" . }}-autoscaler
{{- end }}
{{- end }}
app.kubernetes.io/name: {{ template "coredns.name" . }}-autoscaler
app.kubernetes.io/instance: {{ .Release.Name | quote }}
{{- if .Values.customLabels }}
{{ toYaml .Values.customLabels | nindent 8 }}
{{- end }}
annotations:
checksum/configmap: {{ include (print $.Template.BasePath "/configmap-autoscaler.yaml") . | sha256sum }}
{{- if .Values.isClusterService }}
scheduler.alpha.kubernetes.io/tolerations: '[{"key":"CriticalAddonsOnly", "operator":"Exists"}]'
{{- end }}
{{- with .Values.autoscaler.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
serviceAccountName: {{ template "coredns.fullname" . }}-autoscaler
{{- $priorityClassName := default .Values.priorityClassName .Values.autoscaler.priorityClassName }}
{{- if $priorityClassName }}
priorityClassName: {{ $priorityClassName | quote }}
{{- end }}
{{- if .Values.autoscaler.affinity }}
affinity:
{{ toYaml .Values.autoscaler.affinity | indent 8 }}
{{- end }}
{{- if .Values.autoscaler.tolerations }}
tolerations:
{{ toYaml .Values.autoscaler.tolerations | indent 8 }}
{{- end }}
{{- if .Values.autoscaler.nodeSelector }}
nodeSelector:
{{ toYaml .Values.autoscaler.nodeSelector | indent 8 }}
{{- end }}
{{- if .Values.autoscaler.image.pullSecrets }}
imagePullSecrets:
{{ toYaml .Values.autoscaler.image.pullSecrets | indent 8 }}
{{- end }}
containers:
- name: autoscaler
image: "{{ .Values.autoscaler.image.repository }}:{{ .Values.autoscaler.image.tag }}"
imagePullPolicy: {{ .Values.autoscaler.image.pullPolicy }}
resources:
{{ toYaml .Values.autoscaler.resources | indent 10 }}
{{- if .Values.autoscaler.livenessProbe.enabled }}
livenessProbe:
httpGet:
path: /healthz
port: 8080
scheme: HTTP
initialDelaySeconds: {{ .Values.autoscaler.livenessProbe.initialDelaySeconds }}
periodSeconds: {{ .Values.autoscaler.livenessProbe.periodSeconds }}
timeoutSeconds: {{ .Values.autoscaler.livenessProbe.timeoutSeconds }}
successThreshold: {{ .Values.autoscaler.livenessProbe.successThreshold }}
failureThreshold: {{ .Values.autoscaler.livenessProbe.failureThreshold }}
{{- end }}
command:
- /cluster-proportional-autoscaler
- --namespace={{ .Release.Namespace }}
- --configmap={{ template "coredns.fullname" . }}-autoscaler
- --target=Deployment/{{ default (include "coredns.fullname" .) .Values.deployment.name }}
- --logtostderr=true
- --v=2
{{- if .Values.autoscaler.customFlags }}
{{ toYaml .Values.autoscaler.customFlags | indent 10 }}
{{- end }}
{{- if .Values.autoscaler.extraContainers }}
{{ toYaml .Values.autoscaler.extraContainers | indent 6 }}
{{- end }}
{{- end }}

View File

@@ -1,174 +0,0 @@
{{- if .Values.deployment.enabled }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ default (include "coredns.fullname" .) .Values.deployment.name }}
namespace: {{ .Release.Namespace }}
labels: {{- include "coredns.labels" . | nindent 4 }}
app.kubernetes.io/version: {{ .Values.image.tag | default .Chart.AppVersion | replace ":" "-" | replace "@" "_" | trunc 63 | trimSuffix "-" | quote }}
{{- if .Values.customLabels }}
{{ toYaml .Values.customLabels | indent 4 }}
{{- end }}
{{- if or .Values.deployment.annotations .Values.customAnnotations }}
annotations:
{{- if .Values.customAnnotations }}
{{- toYaml .Values.customAnnotations | nindent 4 }}
{{- end }}
{{- if .Values.deployment.annotations }}
{{- toYaml .Values.deployment.annotations | nindent 4 }}
{{- end }}
{{- end }}
spec:
{{- if and (not .Values.autoscaler.enabled) (not .Values.hpa.enabled) }}
replicas: {{ .Values.replicaCount }}
{{- end }}
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: {{ .Values.rollingUpdate.maxUnavailable }}
maxSurge: {{ .Values.rollingUpdate.maxSurge }}
selector:
{{- if .Values.deployment.selector }}
{{- toYaml .Values.deployment.selector | nindent 4 }}
{{- else }}
matchLabels:
app.kubernetes.io/instance: {{ .Release.Name | quote }}
{{- if .Values.isClusterService }}
k8s-app: {{ template "coredns.k8sapplabel" . }}
{{- end }}
app.kubernetes.io/name: {{ template "coredns.name" . }}
{{- end }}
template:
metadata:
labels:
{{- if .Values.isClusterService }}
k8s-app: {{ template "coredns.k8sapplabel" . }}
{{- end }}
app.kubernetes.io/name: {{ template "coredns.name" . }}
app.kubernetes.io/instance: {{ .Release.Name | quote }}
{{- if .Values.customLabels }}
{{ toYaml .Values.customLabels | indent 8 }}
{{- end }}
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
{{- if .Values.isClusterService }}
scheduler.alpha.kubernetes.io/tolerations: '[{"key":"CriticalAddonsOnly", "operator":"Exists"}]'
{{- end }}
{{- if .Values.podAnnotations }}
{{ toYaml .Values.podAnnotations | indent 8 }}
{{- end }}
spec:
{{- if .Values.podSecurityContext }}
securityContext: {{ toYaml .Values.podSecurityContext | nindent 8 }}
{{- end }}
{{- if .Values.terminationGracePeriodSeconds }}
terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }}
{{- end }}
serviceAccountName: {{ template "coredns.serviceAccountName" . }}
{{- if .Values.priorityClassName }}
priorityClassName: {{ .Values.priorityClassName | quote }}
{{- end }}
{{- if .Values.isClusterService }}
dnsPolicy: Default
{{- end }}
{{- if .Values.affinity }}
affinity:
{{ toYaml .Values.affinity | indent 8 }}
{{- end }}
{{- if .Values.topologySpreadConstraints }}
topologySpreadConstraints:
{{ tpl (toYaml .Values.topologySpreadConstraints) $ | indent 8 }}
{{- end }}
{{- if .Values.tolerations }}
tolerations:
{{ toYaml .Values.tolerations | indent 8 }}
{{- end }}
{{- if .Values.nodeSelector }}
nodeSelector:
{{ toYaml .Values.nodeSelector | indent 8 }}
{{- end }}
{{- if .Values.image.pullSecrets }}
imagePullSecrets:
{{ toYaml .Values.image.pullSecrets | indent 8 }}
{{- end }}
{{- if .Values.initContainers }}
initContainers:
{{ toYaml .Values.initContainers | indent 8 }}
{{- end }}
containers:
- name: "coredns"
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
args: [ "-conf", "/etc/coredns/Corefile" ]
volumeMounts:
- name: config-volume
mountPath: /etc/coredns
{{- range .Values.extraSecrets }}
- name: {{ .name }}
mountPath: {{ .mountPath }}
readOnly: true
{{- end }}
{{- if .Values.extraVolumeMounts }}
{{- toYaml .Values.extraVolumeMounts | nindent 8}}
{{- end }}
{{- if .Values.env }}
env:
{{- toYaml .Values.env | nindent 10}}
{{- end }}
resources:
{{ toYaml .Values.resources | indent 10 }}
ports:
{{ include "coredns.containerPorts" . | indent 8 }}
{{- if .Values.livenessProbe.enabled }}
livenessProbe:
httpGet:
path: /health
port: 8080
scheme: HTTP
initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}
periodSeconds: {{ .Values.livenessProbe.periodSeconds }}
timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }}
successThreshold: {{ .Values.livenessProbe.successThreshold }}
failureThreshold: {{ .Values.livenessProbe.failureThreshold }}
{{- end }}
{{- if .Values.readinessProbe.enabled }}
readinessProbe:
httpGet:
path: /ready
port: 8181
scheme: HTTP
initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}
periodSeconds: {{ .Values.readinessProbe.periodSeconds }}
timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }}
successThreshold: {{ .Values.readinessProbe.successThreshold }}
failureThreshold: {{ .Values.readinessProbe.failureThreshold }}
{{- end }}
{{- if .Values.securityContext }}
securityContext:
{{- toYaml .Values.securityContext | nindent 10 }}
{{- end }}
{{- if .Values.extraContainers }}
{{ toYaml .Values.extraContainers | indent 6 }}
{{- end }}
volumes:
- name: config-volume
configMap:
name: {{ template "coredns.fullname" . }}
items:
- key: Corefile
path: Corefile
{{ range .Values.zoneFiles }}
- key: {{ .filename }}
path: {{ .filename }}
{{ end }}
{{- range .Values.extraSecrets }}
- name: {{ .name }}
secret:
secretName: {{ .name }}
defaultMode: {{ default 400 .defaultMode }}
{{- end }}
{{- if .Values.extraVolumes }}
{{ toYaml .Values.extraVolumes | indent 8 }}
{{- end }}
{{- end }}

View File

@@ -1,33 +0,0 @@
{{- if and (.Values.hpa.enabled) (not .Values.autoscaler.enabled) }}
---
{{- if .Capabilities.APIVersions.Has "autoscaling/v2" }}
apiVersion: autoscaling/v2
{{- else }}
apiVersion: autoscaling/v2beta2
{{- end }}
kind: HorizontalPodAutoscaler
metadata:
name: {{ template "coredns.fullname" . }}
namespace: {{ .Release.Namespace }}
labels: {{- include "coredns.labels" . | nindent 4 }}
{{- if .Values.customLabels }}
{{ toYaml .Values.customLabels | indent 4 }}
{{- end }}
{{- with .Values.customAnnotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ default (include "coredns.fullname" .) .Values.deployment.name }}
minReplicas: {{ .Values.hpa.minReplicas }}
maxReplicas: {{ .Values.hpa.maxReplicas }}
metrics:
{{ toYaml .Values.hpa.metrics | indent 4 }}
{{- if .Values.hpa.behavior }}
behavior:
{{ toYaml .Values.hpa.behavior | indent 4 }}
{{- end }}
{{- end }}

View File

@@ -1,26 +0,0 @@
{{- if and .Values.deployment.enabled .Values.podDisruptionBudget -}}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ template "coredns.fullname" . }}
namespace: {{ .Release.Namespace }}
labels: {{- include "coredns.labels" . | nindent 4 }}
{{- if .Values.customLabels }}
{{ toYaml .Values.customLabels | indent 4 }}
{{- end }}
{{- with .Values.customAnnotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if not .Values.podDisruptionBudget.selector }}
selector:
matchLabels:
app.kubernetes.io/instance: {{ .Release.Name | quote }}
{{- if .Values.isClusterService }}
k8s-app: {{ template "coredns.k8sapplabel" . }}
{{- end }}
app.kubernetes.io/name: {{ template "coredns.name" . }}
{{- end }}
{{ toYaml .Values.podDisruptionBudget | indent 2 }}
{{- end }}

View File

@@ -1,39 +0,0 @@
{{- if and .Values.deployment.enabled .Values.prometheus.service.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ template "coredns.fullname" . }}-metrics
namespace: {{ .Release.Namespace }}
labels: {{- include "coredns.labels" . | nindent 4 }}
app.kubernetes.io/component: metrics
{{- if .Values.customLabels }}
{{ toYaml .Values.customLabels | indent 4 }}
{{- end }}
{{- if or .Values.prometheus.service.annotations .Values.service.annotations .Values.customAnnotations }}
annotations:
{{- if .Values.prometheus.service.annotations }}
{{- toYaml .Values.prometheus.service.annotations | nindent 4 }}
{{- end }}
{{- if .Values.service.annotations }}
{{- toYaml .Values.service.annotations | nindent 4 }}
{{- end }}
{{- if .Values.customAnnotations }}
{{- toYaml .Values.customAnnotations | nindent 4 }}
{{- end }}
{{- end }}
spec:
selector:
{{- if .Values.prometheus.service.selector }}
{{- toYaml .Values.prometheus.service.selector | nindent 4 }}
{{- else }}
app.kubernetes.io/instance: {{ .Release.Name | quote }}
{{- if .Values.isClusterService }}
k8s-app: {{ template "coredns.k8sapplabel" . }}
{{- end }}
app.kubernetes.io/name: {{ template "coredns.name" . }}
{{- end }}
ports:
- name: metrics
port: 9153
targetPort: 9153
{{- end }}

View File

@@ -1,61 +0,0 @@
{{- if .Values.deployment.enabled }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ default (include "coredns.fullname" .) .Values.service.name }}
namespace: {{ .Release.Namespace }}
labels: {{- include "coredns.labels" . | nindent 4 }}
{{- if .Values.customLabels }}
{{ toYaml .Values.customLabels | indent 4 }}
{{- end }}
{{- if or .Values.service.annotations .Values.customAnnotations }}
annotations:
{{- if .Values.service.annotations }}
{{- toYaml .Values.service.annotations | nindent 4 }}
{{- end }}
{{- if .Values.customAnnotations }}
{{- toYaml .Values.customAnnotations | nindent 4 }}
{{- end }}
{{- end }}
spec:
selector:
{{- if .Values.service.selector }}
{{- toYaml .Values.service.selector | nindent 4 }}
{{- else }}
app.kubernetes.io/instance: {{ .Release.Name | quote }}
{{- if .Values.isClusterService }}
k8s-app: {{ template "coredns.k8sapplabel" . }}
{{- end }}
app.kubernetes.io/name: {{ template "coredns.name" . }}
{{- end }}
{{- if .Values.service.clusterIP }}
clusterIP: {{ .Values.service.clusterIP }}
{{- end }}
{{- if .Values.service.clusterIPs }}
clusterIPs:
{{ toYaml .Values.service.clusterIPs | nindent 4 }}
{{- end }}
{{- if .Values.service.externalIPs }}
externalIPs:
{{- toYaml .Values.service.externalIPs | nindent 4 }}
{{- end }}
{{- if .Values.service.externalTrafficPolicy }}
externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy }}
{{- end }}
{{- if .Values.service.loadBalancerIP }}
loadBalancerIP: {{ .Values.service.loadBalancerIP }}
{{- end }}
{{- if .Values.service.loadBalancerClass }}
loadBalancerClass: {{ .Values.service.loadBalancerClass }}
{{- end }}
ports:
{{ include "coredns.servicePorts" . | indent 2 -}}
type: {{ default "ClusterIP" .Values.serviceType }}
{{- if .Values.service.ipFamilyPolicy }}
ipFamilyPolicy: {{ .Values.service.ipFamilyPolicy }}
{{- end }}
{{- end }}
{{- if .Values.service.trafficDistribution }}
trafficDistribution: {{ .Values.service.trafficDistribution }}
{{- end }}

View File

@@ -1,20 +0,0 @@
{{- if and .Values.autoscaler.enabled .Values.rbac.create }}
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ template "coredns.fullname" . }}-autoscaler
namespace: {{ .Release.Namespace }}
labels: {{- include "coredns.labels.autoscaler" . | nindent 4 }}
{{- if .Values.customLabels }}
{{ toYaml .Values.customLabels | indent 4 }}
{{- end }}
{{- with .Values.customAnnotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- if .Values.autoscaler.image.pullSecrets }}
imagePullSecrets:
{{ toYaml .Values.autoscaler.image.pullSecrets | indent 2 }}
{{- end }}
{{- end }}

View File

@@ -1,24 +0,0 @@
{{- if and .Values.deployment.enabled .Values.serviceAccount.create }}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ template "coredns.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
labels: {{- include "coredns.labels" . | nindent 4 }}
{{- if .Values.customLabels }}
{{ toYaml .Values.customLabels | indent 4 }}
{{- end }}
{{- if or .Values.serviceAccount.annotations .Values.customAnnotations }}
annotations:
{{- if .Values.customAnnotations }}
{{- toYaml .Values.customAnnotations | nindent 4 }}
{{- end }}
{{- if .Values.serviceAccount.annotations }}
{{- toYaml .Values.serviceAccount.annotations | nindent 4 }}
{{- end }}
{{- end }}
{{- if .Values.image.pullSecrets }}
imagePullSecrets:
{{ toYaml .Values.image.pullSecrets | indent 2 }}
{{- end }}
{{- end }}

View File

@@ -1,40 +0,0 @@
{{- if and .Values.deployment.enabled .Values.prometheus.monitor.enabled }}
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: {{ template "coredns.fullname" . }}
{{- if .Values.prometheus.monitor.namespace }}
namespace: {{ .Values.prometheus.monitor.namespace }}
{{- end }}
labels: {{- include "coredns.labels" . | nindent 4 }}
{{- if .Values.prometheus.monitor.additionalLabels }}
{{ toYaml .Values.prometheus.monitor.additionalLabels | indent 4 }}
{{- end }}
{{- with .Values.customAnnotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if ne .Values.prometheus.monitor.namespace .Release.Namespace }}
namespaceSelector:
matchNames:
- {{ .Release.Namespace }}
{{- end }}
selector:
{{- if .Values.prometheus.monitor.selector }}
{{- toYaml .Values.prometheus.monitor.selector | nindent 4 }}
{{- else }}
matchLabels:
app.kubernetes.io/instance: {{ .Release.Name | quote }}
{{- if .Values.isClusterService }}
k8s-app: {{ template "coredns.k8sapplabel" . }}
{{- end }}
app.kubernetes.io/name: {{ template "coredns.name" . }}
app.kubernetes.io/component: metrics
{{- end }}
endpoints:
- port: metrics
{{- if .Values.prometheus.monitor.interval }}
interval: {{ .Values.prometheus.monitor.interval }}
{{- end }}
{{- end }}

View File

@@ -1,62 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/helm-unittest/helm-unittest/main/schema/helm-testsuite.json
suite: test ClusterRole for autoscaler
templates:
- templates/clusterrole-autoscaler.yaml
tests:
- it: should render ClusterRole when autoscaler and rbac are enabled
set:
autoscaler:
enabled: true
rbac:
create: true
customLabels:
team: devops
customAnnotations:
note: autoscaler
asserts:
- hasDocuments:
count: 1
- isKind:
of: ClusterRole
- matchRegex:
path: metadata.name
pattern: ^.*-autoscaler$
- equal:
path: rules[0].resources
value: ["nodes"]
- equal:
path: metadata.labels.team
value: devops
- equal:
path: metadata.annotations.note
value: autoscaler
- it: should not render ClusterRole if autoscaler is disabled
set:
autoscaler:
enabled: false
rbac:
create: true
asserts:
- hasDocuments:
count: 0
- it: should not render ClusterRole if rbac is disabled
set:
autoscaler:
enabled: true
rbac:
create: false
asserts:
- hasDocuments:
count: 0

View File

@@ -1,53 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/helm-unittest/helm-unittest/main/schema/helm-testsuite.json
suite: ClusterRole RBAC Test
templates:
- templates/clusterrole.yaml
set:
deployment:
enabled: true
rbac:
create: true
pspEnable: true
tests:
- it: should render ClusterRole with correct name and rules
asserts:
- isKind:
of: ClusterRole
- equal:
path: metadata.name
value: RELEASE-NAME-coredns
- contains:
path: rules[0].resources
content: endpoints
- contains:
path: rules[0].resources
content: services
- contains:
path: rules[0].resources
content: pods
- contains:
path: rules[0].resources
content: namespaces
- equal:
path: rules[1].apiGroups[0]
value: discovery.k8s.io
- contains:
path: rules[1].resources
content: endpointslices
- equal:
path: rules[2].apiGroups[0]
value: policy
- equal:
path: rules[2].apiGroups[1]
value: extensions
- contains:
path: rules[2].resources
content: podsecuritypolicies
- contains:
path: rules[2].verbs
content: use

View File

@@ -1,60 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/helm-unittest/helm-unittest/main/schema/helm-testsuite.json
suite: ClusterRoleBinding Autoscaler Test
templates:
- templates/clusterrolebinding-autoscaler.yaml
set:
autoscaler:
enabled: true
rbac:
create: true
customLabels:
my-custom-label: "enabled"
customAnnotations:
custom-annotation: "test-value"
release:
namespace: test-namespace
tests:
- it: should render ClusterRoleBinding with correct metadata and subject
asserts:
- isKind:
of: ClusterRoleBinding
- equal:
path: metadata.name
value: RELEASE-NAME-coredns-autoscaler
- equal:
path: metadata.labels.my-custom-label
value: enabled
- equal:
path: metadata.annotations.custom-annotation
value: test-value
- equal:
path: roleRef.apiGroup
value: rbac.authorization.k8s.io
- equal:
path: roleRef.kind
value: ClusterRole
- equal:
path: roleRef.name
value: RELEASE-NAME-coredns-autoscaler
- equal:
path: subjects[0].kind
value: ServiceAccount
- equal:
path: subjects[0].name
value: RELEASE-NAME-coredns-autoscaler
- equal:
path: subjects[0].namespace
value: test-namespace

View File

@@ -1,52 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/helm-unittest/helm-unittest/main/schema/helm-testsuite.json
suite: ClusterRoleBinding Test
templates:
- templates/clusterrolebinding.yaml
set:
deployment:
enabled: true
rbac:
create: true
nameOverride: coredns-test
serviceAccount:
name: coredns-test
release:
namespace: test-namespace
tests:
- it: should render ClusterRoleBinding with correct metadata and subjects
asserts:
- isKind:
of: ClusterRoleBinding
- equal:
path: metadata.name
value: RELEASE-NAME-coredns-test
- equal:
path: roleRef.apiGroup
value: rbac.authorization.k8s.io
- equal:
path: roleRef.kind
value: ClusterRole
- equal:
path: roleRef.name
value: RELEASE-NAME-coredns-test
- equal:
path: subjects[0].kind
value: ServiceAccount
- equal:
path: subjects[0].name
value: coredns-test
- equal:
path: subjects[0].namespace
value: test-namespace

View File

@@ -1,75 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/helm-unittest/helm-unittest/main/schema/helm-testsuite.json
suite: ConfigMap Autoscaler Test
templates:
- templates/configmap-autoscaler.yaml
set:
autoscaler:
enabled: true
coresPerReplica: 256
nodesPerReplica: 32
preventSinglePointFailure: true
min: 1
max: 10
includeUnschedulableNodes: false
configmap:
annotations:
autoscaler-note: "used by cluster-autoscaler"
customLabels:
my-custom-label: "enabled"
customAnnotations:
custom-annotation: "test-value"
release:
namespace: test-namespace
tests:
- it: should render ConfigMap with correct metadata and autoscaler values
asserts:
- isKind:
of: ConfigMap
- equal:
path: metadata.name
value: RELEASE-NAME-coredns-autoscaler
- equal:
path: metadata.namespace
value: test-namespace
- equal:
path: metadata.labels.my-custom-label
value: enabled
- equal:
path: metadata.annotations.custom-annotation
value: test-value
- equal:
path: metadata.annotations.autoscaler-note
value: used by cluster-autoscaler
- matchRegex:
path: data.linear
pattern: '"coresPerReplica": 256'
- matchRegex:
path: data.linear
pattern: '"nodesPerReplica": 32'
- matchRegex:
path: data.linear
pattern: '"preventSinglePointFailure": true'
- matchRegex:
path: data.linear
pattern: '"min": 1'
- matchRegex:
path: data.linear
pattern: '"max": 10'
- matchRegex:
path: data.linear
pattern: '"includeUnschedulableNodes": false'

View File

@@ -1,41 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/helm-unittest/helm-unittest/main/schema/helm-testsuite.json
suite: "Test coredns configmap generation"
templates:
- templates/configmap.yaml
tests:
- it: "should create a configmap with custom Corefile and zone files"
set:
deployment:
enabled: true
skipConfig: false
customAnnotations:
custom-annotation: "test-value"
servers:
- zones:
- zone: "example.com"
scheme: "."
port: 5353
plugins:
- name: forward
parameters: ". 8.8.8.8"
zoneFiles:
- filename: db.example.com
contents: |
example.com. IN A 192.0.2.1
asserts:
- isKind:
of: ConfigMap
- matchRegex:
path: data.Corefile
pattern: "example\\.com:5353 \\{"
- matchRegex:
path: data.Corefile
pattern: "forward \\. 8\\.8\\.8\\.8"
- equal:
path: metadata.annotations.custom-annotation
value: "test-value"
- matchRegex:
path: 'data["db.example.com"]'
pattern: "example\\.com\\.\\s+IN\\s+A\\s+192\\.0\\.2\\.1"

View File

@@ -1,77 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/helm-unittest/helm-unittest/main/schema/helm-testsuite.json
suite: test autoscaler deployment
templates:
- templates/configmap-autoscaler.yaml
- templates/deployment-autoscaler.yaml
tests:
- it: should render autoscaler deployment with correct image
set:
autoscaler:
enabled: true
image:
repository: autoscaler
tag: v1.0.0
pullPolicy: IfNotPresent
hpa:
enabled: false
template: templates/deployment-autoscaler.yaml
asserts:
- isKind:
of: Deployment
- equal:
path: metadata.name
value: RELEASE-NAME-coredns-autoscaler
- equal:
path: spec.template.spec.containers[0].image
value: autoscaler:v1.0.0
- equal:
path: spec.template.spec.containers[0].imagePullPolicy
value: IfNotPresent
- it: should set correct priorityClassName and tolerations
set:
autoscaler:
enabled: true
priorityClassName: system-cluster-critical
tolerations:
- key: "CriticalAddonsOnly"
operator: "Exists"
image:
repository: autoscaler
tag: v1.0.0
hpa:
enabled: false
template: templates/deployment-autoscaler.yaml
asserts:
- equal:
path: spec.template.spec.priorityClassName
value: system-cluster-critical
- equal:
path: spec.template.spec.tolerations[0].key
value: CriticalAddonsOnly
- equal:
path: spec.template.spec.tolerations[0].operator
value: Exists
- it: should not render deployment if autoscaler disabled
set:
autoscaler:
enabled: false
hpa:
enabled: false
template: templates/deployment-autoscaler.yaml
asserts:
- hasDocuments:
count: 0
- it: should not render deployment if hpa enabled
set:
autoscaler:
enabled: true
hpa:
enabled: true
template: templates/deployment-autoscaler.yaml
asserts:
- hasDocuments:
count: 0

Some files were not shown because too many files have changed in this diff Show More