Use Solr's /admin/info/health for pod readiness checks (#629)
Now that the operator expects Solr versions >=8.11, we can use Solr's
/admin/info/health endpoint as our default readiness probe.
(/admin/info/system remains in use for liveness and startup probes.)
diff --git a/controllers/solrcloud_controller_basic_auth_test.go b/controllers/solrcloud_controller_basic_auth_test.go
index 7a4cee8..a91846f 100644
--- a/controllers/solrcloud_controller_basic_auth_test.go
+++ b/controllers/solrcloud_controller_basic_auth_test.go
@@ -208,13 +208,17 @@
func expectStatefulSetBasicAuthConfig(ctx context.Context, sc *solrv1beta1.SolrCloud, expectBootstrapSecret bool) *appsv1.StatefulSet {
Expect(sc.Spec.SolrSecurity).To(Not(BeNil()), "solrSecurity is not configured for this SolrCloud instance!")
- expProbePath := "/solr/admin/info/system"
+ expLivenessProbePath := "/solr/admin/info/system"
+ expReadinessProbePath := "/solr/admin/info/health"
if sc.Spec.CustomSolrKubeOptions.PodOptions != nil && sc.Spec.CustomSolrKubeOptions.PodOptions.LivenessProbe != nil {
- expProbePath = sc.Spec.CustomSolrKubeOptions.PodOptions.LivenessProbe.HTTPGet.Path
+ expLivenessProbePath = sc.Spec.CustomSolrKubeOptions.PodOptions.LivenessProbe.HTTPGet.Path
+ }
+ if sc.Spec.CustomSolrKubeOptions.PodOptions != nil && sc.Spec.CustomSolrKubeOptions.PodOptions.ReadinessProbe != nil {
+ expReadinessProbePath = sc.Spec.CustomSolrKubeOptions.PodOptions.ReadinessProbe.HTTPGet.Path
}
stateful := expectStatefulSetWithChecks(ctx, sc, sc.StatefulSetName(), func(g Gomega, found *appsv1.StatefulSet) {
- expectBasicAuthConfigOnPodTemplateWithGomega(g, sc, &found.Spec.Template, expectBootstrapSecret, expProbePath)
+ expectBasicAuthConfigOnPodTemplateWithGomega(g, sc, &found.Spec.Template, expectBootstrapSecret, expLivenessProbePath, expReadinessProbePath)
})
expectSecretWithChecks(ctx, sc, sc.BasicAuthSecretName(), func(innerG Gomega, found *corev1.Secret) {
@@ -237,7 +241,9 @@
probePaths := util.GetCustomProbePaths(sc)
if len(probePaths) > 0 {
securityJson := string(bootstrapSecret.Data["security.json"])
- Expect(securityJson).To(ContainSubstring(util.DefaultProbePath), "bootstrapped security.json should have an authz rule for probe path: %s", util.DefaultProbePath)
+ Expect(securityJson).To(ContainSubstring(util.DefaultLivenessProbePath), "bootstrapped security.json should have an authz rule for liveness probe path: %s", util.DefaultLivenessProbePath)
+ Expect(securityJson).To(ContainSubstring(util.DefaultReadinessProbePath), "bootstrapped security.json should have an authz rule for readiness probe path: %s", util.DefaultReadinessProbePath)
+
for _, p := range probePaths {
p = p[len("/solr"):] // drop the /solr part on the path
Expect(securityJson).To(ContainSubstring(p), "bootstrapped security.json should have an authz rule for probe path: %s", p)
@@ -250,7 +256,7 @@
}
// Ensures config is setup for basic-auth enabled Solr pods
-func expectBasicAuthConfigOnPodTemplateWithGomega(g Gomega, solrCloud *solrv1beta1.SolrCloud, podTemplate *corev1.PodTemplateSpec, expectBootstrapSecret bool, expProbePath string) *corev1.Container {
+func expectBasicAuthConfigOnPodTemplateWithGomega(g Gomega, solrCloud *solrv1beta1.SolrCloud, podTemplate *corev1.PodTemplateSpec, expectBootstrapSecret bool, expLivenessProbePath string, expReadinessProbePath string) *corev1.Container {
// check the env vars needed for the probes to work with auth
g.Expect(podTemplate.Spec.Containers).To(Not(BeEmpty()), "Solr Pod requires containers")
mainContainer := podTemplate.Spec.Containers[0]
@@ -282,18 +288,22 @@
g.Expect(basicAuthSecretVolMount).To(Not(BeNil()), "No Basic Auth volume mount used in Solr container")
g.Expect(basicAuthSecretVolMount.MountPath).To(Equal("/etc/secrets/"+secretName), "Wrong path used to mount Basic Auth volume")
- expProbeCmd := fmt.Sprintf("JAVA_TOOL_OPTIONS=\"-Dbasicauth=$(cat /etc/secrets/%s-solrcloud-basic-auth/username):$(cat /etc/secrets/%s-solrcloud-basic-auth/password) -Dsolr.httpclient.builder.factory=org.apache.solr.client.solrj.impl.PreemptiveBasicAuthClientBuilderFactory\" "+
+ expLivenessProbeCmd := fmt.Sprintf("JAVA_TOOL_OPTIONS=\"-Dbasicauth=$(cat /etc/secrets/%s-solrcloud-basic-auth/username):$(cat /etc/secrets/%s-solrcloud-basic-auth/password) -Dsolr.httpclient.builder.factory=org.apache.solr.client.solrj.impl.PreemptiveBasicAuthClientBuilderFactory\" "+
"solr api -get \"http://${SOLR_HOST}:8983%s\"",
- solrCloud.Name, solrCloud.Name, expProbePath)
+ solrCloud.Name, solrCloud.Name, expLivenessProbePath)
+ expReadinessProbeCmd := fmt.Sprintf("JAVA_TOOL_OPTIONS=\"-Dbasicauth=$(cat /etc/secrets/%s-solrcloud-basic-auth/username):$(cat /etc/secrets/%s-solrcloud-basic-auth/password) -Dsolr.httpclient.builder.factory=org.apache.solr.client.solrj.impl.PreemptiveBasicAuthClientBuilderFactory\" "+
+ "solr api -get \"http://${SOLR_HOST}:8983%s\"",
+ solrCloud.Name, solrCloud.Name, expReadinessProbePath)
+
g.Expect(mainContainer.LivenessProbe).To(Not(BeNil()), "main container should have a liveness probe defined")
g.Expect(mainContainer.LivenessProbe.Exec).To(Not(BeNil()), "liveness probe should have an exec when auth is enabled")
g.Expect(mainContainer.LivenessProbe.Exec.Command).To(Not(BeEmpty()), "liveness probe command cannot be empty")
- g.Expect(mainContainer.LivenessProbe.Exec.Command[2]).To(Equal(expProbeCmd), "liveness probe should invoke java with auth opts")
+ g.Expect(mainContainer.LivenessProbe.Exec.Command[2]).To(Equal(expLivenessProbeCmd), "liveness probe should invoke java with auth opts")
g.Expect(mainContainer.LivenessProbe.TimeoutSeconds).To(BeEquivalentTo(5), "liveness probe default timeout should be increased when using basicAuth")
g.Expect(mainContainer.ReadinessProbe).To(Not(BeNil()), "main container should have a readiness probe defined")
g.Expect(mainContainer.ReadinessProbe.Exec).To(Not(BeNil()), "readiness probe should have an exec when auth is enabled")
g.Expect(mainContainer.ReadinessProbe.Exec.Command).To(Not(BeEmpty()), "readiness probe command cannot be empty")
- g.Expect(mainContainer.ReadinessProbe.Exec.Command[2]).To(Equal(expProbeCmd), "readiness probe should invoke java with auth opts")
+ g.Expect(mainContainer.ReadinessProbe.Exec.Command[2]).To(Equal(expReadinessProbeCmd), "readiness probe should invoke java with auth opts")
g.Expect(mainContainer.ReadinessProbe.TimeoutSeconds).To(BeEquivalentTo(5), "readiness probe default timeout should be increased when using basicAuth")
}
diff --git a/controllers/solrcloud_controller_tls_test.go b/controllers/solrcloud_controller_tls_test.go
index 22daa96..c24013f 100644
--- a/controllers/solrcloud_controller_tls_test.go
+++ b/controllers/solrcloud_controller_tls_test.go
@@ -124,7 +124,7 @@
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Scheme: corev1.URISchemeHTTPS,
- Path: "/solr/admin/info/health",
+ Path: "/solr/admin/customreadiness",
Port: intstr.FromInt(8983),
},
},
@@ -134,7 +134,7 @@
solrCloud.Spec.SolrTLS = createTLSOptions(tlsSecretName, keystorePassKey, false)
})
FIt("has the correct resources", func(ctx context.Context) {
- Expect(util.GetCustomProbePaths(solrCloud)).To(ConsistOf("/solr/admin/info/health"), "Utility Probe paths command gives wrong result")
+ Expect(util.GetCustomProbePaths(solrCloud)).To(ConsistOf("/solr/admin/customreadiness"), "Utility Probe paths command gives wrong result")
verifyUserSuppliedTLSConfig(solrCloud.Spec.SolrTLS, tlsSecretName, keystorePassKey, tlsSecretName)
By("checking that the User supplied TLS Config is correct in the generated StatefulSet")
@@ -536,7 +536,7 @@
g.Expect(mainContainer.LivenessProbe.Exec).To(Not(BeNil()), "liveness probe should have an exec when mTLS is enabled")
g.Expect(mainContainer.LivenessProbe.Exec.Command).To(Not(BeNil()), "liveness probe should have an exec when mTLS is enabled")
g.Expect(mainContainer.LivenessProbe.Exec.Command).To(HaveLen(3), "liveness probe command has wrong number of args")
- path := "/solr" + util.DefaultProbePath
+ path := "/solr" + util.DefaultLivenessProbePath
if solrCloud.Spec.CustomSolrKubeOptions.PodOptions != nil && solrCloud.Spec.CustomSolrKubeOptions.PodOptions.LivenessProbe != nil {
path = solrCloud.Spec.CustomSolrKubeOptions.PodOptions.LivenessProbe.HTTPGet.Path
}
@@ -544,7 +544,7 @@
g.Expect(mainContainer.ReadinessProbe).To(Not(BeNil()), "main container should have a readiness probe defined")
g.Expect(mainContainer.ReadinessProbe.Exec).To(Not(BeNil()), "readiness probe should have an exec when mTLS is enabled")
g.Expect(mainContainer.ReadinessProbe.Exec.Command).To(HaveLen(3), "readiness probe command has wrong number of args")
- path = "/solr" + util.DefaultProbePath
+ path = "/solr" + util.DefaultReadinessProbePath
if solrCloud.Spec.CustomSolrKubeOptions.PodOptions != nil && solrCloud.Spec.CustomSolrKubeOptions.PodOptions.ReadinessProbe != nil {
path = solrCloud.Spec.CustomSolrKubeOptions.PodOptions.ReadinessProbe.HTTPGet.Path
}
@@ -553,7 +553,7 @@
g.Expect(mainContainer.StartupProbe).To(Not(BeNil()), "main container should have a startup probe defined")
g.Expect(mainContainer.StartupProbe.Exec).To(Not(BeNil()), "startup probe should have an exec when auth is enabled")
g.Expect(mainContainer.StartupProbe.Exec.Command).To(HaveLen(3), "startup probe command has wrong number of args")
- path = "/solr" + util.DefaultProbePath
+ path = "/solr" + util.DefaultStartupProbePath
if solrCloud.Spec.CustomSolrKubeOptions.PodOptions != nil && solrCloud.Spec.CustomSolrKubeOptions.PodOptions.StartupProbe != nil {
path = solrCloud.Spec.CustomSolrKubeOptions.PodOptions.StartupProbe.HTTPGet.Path
}
diff --git a/controllers/util/solr_security_util.go b/controllers/util/solr_security_util.go
index 8e76dc0..51caa31 100644
--- a/controllers/util/solr_security_util.go
+++ b/controllers/util/solr_security_util.go
@@ -39,9 +39,11 @@
)
const (
- SecurityJsonFile = "security.json"
- BasicAuthMd5Annotation = "solr.apache.org/basicAuthMd5"
- DefaultProbePath = "/admin/info/system"
+ SecurityJsonFile = "security.json"
+ BasicAuthMd5Annotation = "solr.apache.org/basicAuthMd5"
+ DefaultStartupProbePath = "/admin/info/system"
+ DefaultLivenessProbePath = DefaultStartupProbePath
+ DefaultReadinessProbePath = "/admin/info/health"
)
// Utility struct holding security related config and objects resolved at runtime needed during reconciliation,
@@ -433,7 +435,8 @@
// Gets a list of probe paths we need to setup authz for
func getProbePaths(solrCloud *solr.SolrCloud) []string {
- probePaths := []string{DefaultProbePath}
+ // Current startup and liveness probes use the same API, so liveness needn't be explicitly specified here
+ probePaths := []string{DefaultStartupProbePath, DefaultReadinessProbePath}
probePaths = append(probePaths, GetCustomProbePaths(solrCloud)...)
return uniqueProbePaths(probePaths)
}
diff --git a/controllers/util/solr_util.go b/controllers/util/solr_util.go
index 62a99c8..c6ede52 100644
--- a/controllers/util/solr_util.go
+++ b/controllers/util/solr_util.go
@@ -89,13 +89,9 @@
}
defaultProbeTimeout := int32(1)
- defaultHandler := corev1.ProbeHandler{
- HTTPGet: &corev1.HTTPGetAction{
- Scheme: probeScheme,
- Path: "/solr" + DefaultProbePath,
- Port: intstr.FromInt(solrPodPort),
- },
- }
+ defaultStartupProbe := createDefaultProbeHandlerForPath(probeScheme, solrPodPort, DefaultStartupProbePath)
+ defaultLivenessProbe := createDefaultProbeHandlerForPath(probeScheme, solrPodPort, DefaultLivenessProbePath)
+ defaultReadinessProbe := createDefaultProbeHandlerForPath(probeScheme, solrPodPort, DefaultReadinessProbePath)
labels := solrCloud.SharedLabelsWith(solrCloud.GetLabels())
selectorLabels := solrCloud.SharedLabels()
@@ -468,7 +464,7 @@
SuccessThreshold: 1,
FailureThreshold: 10,
PeriodSeconds: 5,
- ProbeHandler: defaultHandler,
+ ProbeHandler: defaultStartupProbe,
},
// Kill Solr if it is unavailable for any 60-second period
LivenessProbe: &corev1.Probe{
@@ -476,7 +472,7 @@
SuccessThreshold: 1,
FailureThreshold: 3,
PeriodSeconds: 20,
- ProbeHandler: defaultHandler,
+ ProbeHandler: defaultLivenessProbe,
},
// Do not route requests to solr if it is not available for any 20-second period
ReadinessProbe: &corev1.Probe{
@@ -484,7 +480,7 @@
SuccessThreshold: 1,
FailureThreshold: 2,
PeriodSeconds: 10,
- ProbeHandler: defaultHandler,
+ ProbeHandler: defaultReadinessProbe,
},
VolumeMounts: volumeMounts,
Env: envVars,
@@ -782,6 +778,16 @@
return containers
}
+func createDefaultProbeHandlerForPath(probeScheme corev1.URIScheme, solrPodPort int, path string) corev1.ProbeHandler {
+ return corev1.ProbeHandler{
+ HTTPGet: &corev1.HTTPGetAction{
+ Scheme: probeScheme,
+ Path: "/solr" + path,
+ Port: intstr.FromInt(solrPodPort),
+ },
+ }
+}
+
const DefaultSolrXML = `<?xml version="1.0" encoding="UTF-8" ?>
<solr>
%s
diff --git a/docs/solr-cloud/solr-cloud-crd.md b/docs/solr-cloud/solr-cloud-crd.md
index b30ea67..8c8874e 100644
--- a/docs/solr-cloud/solr-cloud-crd.md
+++ b/docs/solr-cloud/solr-cloud-crd.md
@@ -930,10 +930,10 @@
With a command, we can load the username and password from a secret; Kubernetes will
[update the mounted secret files](https://kubernetes.io/docs/concepts/configuration/secret/#mounted-secrets-are-updated-automatically) when the secret changes automatically.
-If you customize the HTTP path for any probes (under `spec.customSolrKubeOptions.podOptions`),
-then you must use `probesRequireAuth=false` as the operator does not reconfigure custom HTTP probes to use the command needed to support `probesRequireAuth=true`.
+By default, the operator will use Solr's `/admin/info/system` endpoint for startup and liveness probes, and the `/admin/info/health` endpoint for readiness probes.
+This default can be customized by changing the HTTP path for any probes (under `spec.customSolrKubeOptions.podOptions`), however this also requires users to set `probesRequireAuth=false` as the operator does not reconfigure custom HTTP probes to use the command needed to support `probesRequireAuth=true`.
-If you're running Solr 8+, then we recommend using the `/admin/info/health` endpoint for your probes using the following config:
+Custom readiness and liveness probes can be specified using configuration like the following:
```yaml
spec:
...
@@ -942,21 +942,28 @@
livenessProbe:
httpGet:
scheme: HTTP
- path: /solr/admin/info/health
+ path: /solr/admin/customliveness
port: 8983
readinessProbe:
httpGet:
scheme: HTTP
- path: /solr/admin/info/health
+ path: /solr/admin/customreadiness
port: 8983
```
-Consequently, the bootstrapped `security.json` will include an additional rule to allow access to the `/admin/info/health` endpoint:
+
+Consequently, the bootstrapped `security.json` will include additional rules to allow access to the endpoints used by the startup, liveness, and readiness probes:
```json
{
"name": "k8s-probe-1",
"role": null,
"collection": null,
- "path": "/admin/info/health"
+ "path": "/admin/customliveness"
+ },
+ {
+ "name": "k8s-probe-2",
+ "role": null,
+ "collection": null,
+ "path": "/admin/customreadiness"
}
```
@@ -993,7 +1000,7 @@
"name": "k8s-probe-0",
"role": null,
"collection": null,
- "path": "/admin/info/system"
+ "path": "/admin/info/health"
},
{
"name": "k8s-status",
@@ -1050,7 +1057,7 @@
"name": "k8s-probe-0",
"role": null,
"collection": null,
- "path": "/admin/info/system"
+ "path": "/admin/info/health"
}
```
In this case, the `"role":null` indicates this endpoint allows anonymous access by unknown users.
@@ -1146,7 +1153,6 @@
Users need to ensure their `security.json` contains the user supplied in the `basicAuthSecret` has read access to the following endpoints:
```
-/admin/info/system
/admin/info/health
/admin/collections
/admin/metrics
diff --git a/docs/upgrade-notes.md b/docs/upgrade-notes.md
index 128be56..3019e48 100644
--- a/docs/upgrade-notes.md
+++ b/docs/upgrade-notes.md
@@ -130,7 +130,11 @@
- The `POD_HOSTNAME` envVar in SolrCloud Pods has been deprecated. Use `POD_NAME` instead.
-- Use of the `hostPort` system property placeholder in custom solr.xml files has been deprecated. Use `<int name="hostPort">${solr.port.advertise:80}</int>`, the default value used by Solr, instead.
+- Use of the `hostPort` system property placeholder in custom solr.xml files has been deprecated.
+ Use `<int name="hostPort">${solr.port.advertise:80}</int>`, the default value used by Solr, instead.
+
+- By default `solrcloud` resources will now use `/admin/info/system` and `/admin/info/health` for liveness and readiness checks, respectively.
+ Administrators that provide custom `security.json` files for their clusters should either exempt both of these endpoints from authentication entirely, or configure permissions ensuring the relevant Solr user account can access them without issue.
### v0.7.0
- **Kubernetes support is now limited to 1.21+.**
diff --git a/hack/release/smoke_test/test_cluster.sh b/hack/release/smoke_test/test_cluster.sh
index 26d7ed4..27b3aa1 100755
--- a/hack/release/smoke_test/test_cluster.sh
+++ b/hack/release/smoke_test/test_cluster.sh
@@ -195,7 +195,7 @@
sleep 2
printf "\nCheck the admin URL to make sure it works\n"
-curl --silent "http://localhost:18983/solr/admin/info/system" | grep '"status":0' > /dev/null
+curl --silent "http://localhost:18983/solr/admin/info/health" | grep '"status":0' > /dev/null
printf "\nCreating a test collection\n"
curl --silent "http://localhost:18983/solr/admin/collections?action=CREATE&name=smoke-test&replicationFactor=1&numShards=2" | grep '"status":0' > /dev/null
diff --git a/helm/solr/Chart.yaml b/helm/solr/Chart.yaml
index f265d4b..5fda1c1 100644
--- a/helm/solr/Chart.yaml
+++ b/helm/solr/Chart.yaml
@@ -62,6 +62,13 @@
url: https://github.com/apache/solr-operator/issues/635
- name: Github PR
url: https://github.com/apache/solr-operator/pull/636
+ - kind: changed
+ description: Solr's `/admin/info/health` API is now used for readiness probes by default.
+ links:
+ - name: Github Issue
+ url: https://github.com/apache/solr-operator/issues/628
+ - name: Github PR
+ url: https://github.com/apache/solr-operator/pull/629
artifacthub.io/containsSecurityUpdates: "false"
artifacthub.io/recommendations: |
- url: https://artifacthub.io/packages/helm/apache-solr/solr-operator