Skip to content
This repository was archived by the owner on Jan 27, 2021. It is now read-only.

Commit d16000f

Browse files
committed
Add support for StatefulSets
With this change, Osiris can now manage both Deployments and StatefulSets. I introduced a new service annotation `osiris.deislabs.io/statefulset` to link a service with its statefulset, otherwise everything works the same as with Deployments. Internally, I tried to avoid duplicated code, and introduced a `kind` parameter to make a distinction between deployments and statefulsets.
1 parent 17f760c commit d16000f

File tree

19 files changed

+713
-271
lines changed

19 files changed

+713
-271
lines changed

README.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ development.__
1212

1313
## How it works
1414

15-
For Osiris-enabled deployments, Osiris automatically instruments application
16-
pods with a __metrics-collecting proxy__ deployed as a sidecar container.
15+
For Osiris-enabled deployments or statefulSets, Osiris automatically instruments
16+
application pods with a __metrics-collecting proxy__ deployed as a sidecar container.
1717

18-
For any Osiris-enabled deployment that is _already_ scaled to a configurable
19-
minimum number of replicas (one, by default), the __zeroscaler__ component
20-
continuously analyzes metrics from each of that deployment's pods. When the
21-
aggregated metrics reveal that all of the deployment's pods are idling, the
22-
zeroscaler scales the deployment to zero replicas.
18+
For any Osiris-enabled deployment or statefulSet that is _already_ scaled to
19+
a configurable minimum number of replicas (one, by default), the __zeroscaler__
20+
component continuously analyzes metrics from each of that deployment/statefulSet's
21+
pods. When the aggregated metrics reveal that all of the deployment/statefulSet's
22+
pods are idling, the zeroscaler scales the deployment/statefulSet to zero replicas.
2323

2424
Under normal circumstances, scaling a deployment to zero replicas poses a
2525
problem: any services that select pods from that deployment (and only that
@@ -148,7 +148,7 @@ spec:
148148
Deploy the example application `hello-osiris` :
149149

150150
```
151-
kubectl create -f ./example/hello-osiris.yaml
151+
kubectl create -f ./example/hello-osiris-deployment.yaml
152152
```
153153

154154
This will create an Osiris-enabled deployment and service named `hello-osiris`.
@@ -168,6 +168,10 @@ replicas and the one `hello-osiris` pod should be terminated.
168168
Make a request again, and watch as Osiris scales the deployment back to one
169169
replica and your request is handled successfully.
170170

171+
You can also do the same with the
172+
[hello-osiris-statefulset.yaml](example/hello-osiris-statefulset.yaml) example,
173+
which uses a Kubernetes StatefulSet instead of a Deployment.
174+
171175
## Limitations
172176

173177
It is a specific goal of Osiris to enable greater resource efficiency within

chart/osiris/templates/cluster-role.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ rules:
3232
- apps
3333
resources:
3434
- deployments
35+
- statefulsets
3536
verbs:
3637
- get
3738
- list

chart/osiris/templates/proxy-injector-webhook-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ webhooks:
4242
- v1
4343
resources:
4444
- deployments
45+
- statefulsets
4546
operations:
4647
- CREATE
4748
- UPDATE
File renamed without changes.
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
apiVersion: v1
2+
kind: Service
3+
metadata:
4+
name: hello-osiris
5+
labels:
6+
app: hello-osiris
7+
annotations:
8+
osiris.deislabs.io/enabled: "true"
9+
osiris.deislabs.io/statefulset: hello-osiris
10+
osiris.deislabs.io/loadBalancerHostname: hello-osiris.contoso.io
11+
spec:
12+
type: LoadBalancer
13+
ports:
14+
- name: http
15+
port: 8080
16+
targetPort: 8080
17+
- name: h2c
18+
port: 8081
19+
targetPort: 8081
20+
- name: grpc
21+
port: 8082
22+
targetPort: 8082
23+
- name: https
24+
port: 4430
25+
targetPort: 4430
26+
selector:
27+
app: hello-osiris
28+
---
29+
apiVersion: v1
30+
kind: Secret
31+
metadata:
32+
name: hello-osiris-cert
33+
labels:
34+
app: hello-osiris
35+
data:
36+
server.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQyRENDQXNBQ0NRQy9HeC92dExTTlZEQU5CZ2txaGtpRzl3MEJBUXNGQURDQnJURUxNQWtHQTFVRUJoTUMKVlZNeEV6QVJCZ05WQkFnTUNsZGhjMmhwYm1kMGIyNHhFREFPQmdOVkJBY01CMUpsWkcxdmJtUXhFakFRQmdOVgpCQW9NQ1VSbGFYTWdUR0ZpY3pFVU1CSUdBMVVFQ3d3TFJXNW5hVzVsWlhKcGJtY3hJVEFmQmdOVkJBTU1HR2hsCmJHeHZMVzl6YVhKcGN5NWtaV2x6YkdGaWN5NXBiekVxTUNnR0NTcUdTSWIzRFFFSkFSWWJhMlZ1ZEM1eVlXNWoKYjNWeWRFQnRhV055YjNOdlpuUXVZMjl0TUI0WERURTVNREl5TXpBd01UazBNVm9YRFRJd01ESXlNekF3TVRrMApNVm93Z2EweEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlEQXBYWVhOb2FXNW5kRzl1TVJBd0RnWURWUVFICkRBZFNaV1J0YjI1a01SSXdFQVlEVlFRS0RBbEVaV2x6SUV4aFluTXhGREFTQmdOVkJBc01DMFZ1WjJsdVpXVnkKYVc1bk1TRXdId1lEVlFRRERCaG9aV3hzYnkxdmMybHlhWE11WkdWcGMyeGhZbk11YVc4eEtqQW9CZ2txaGtpRwo5dzBCQ1FFV0cydGxiblF1Y21GdVkyOTFjblJBYldsamNtOXpiMlowTG1OdmJUQ0NBU0l3RFFZSktvWklodmNOCkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFMZEJZTFlwT2NaSUdEWWs2Qzl5T1hreE8wcUZQbVkzNGVWanBrc0oKZjZqaUVEUDFWZlBoWXV6TnRwMUY0ZWhlR1h3WEU0cmt4ZjQ5bExtcmU4L3g4NUh6RHNKK1NNdnlaZ09XZjZMcgpoNE53aVBKcmNjcDhGTVlXMmtJenJiVWZFS0wxYUZ1VCtkRXc3NkgxRlhPUmsvS0Y0V3JMYXhkRlBPbDhLMWVPCm1NazFtSkU3NTNYZzVYd2FVVUVHZ2tGbUZkZHJhQ2N3Y1U0QmtnbXRObTdFTExJQ2Nnb3MzNHVmR21ndmN2ZkwKSWhuenZxNmxCNDM4a0hyaG16OG11WFVwYjhQa1k2NmtxRGpxTk53YlBsLzJrMjYvVTg1RUVlazI0YnowMzlBZApsUnpKUTFndUhacmxKOTBQckl0aFJzM3NNZmdzWGtIaGZDR0J5dlVXYWZubUZPY0NBd0VBQVRBTkJna3Foa2lHCjl3MEJBUXNGQUFPQ0FRRUFzckltVURVK0E2YWNHVGloK3N5c2lpQWVWYWtsL0FNcytvWXJIa1NPK2NrOXVNcFUKMUw3dUtrNDZBSGZ0dkplbXJqaFBObHJpSmZOSEh5bEZ0YlpkTjRqM2RmL3p1L1ExbVRMa2dLUXJWZkl6ZFF2eApVUlEyOXB2ZWFBdFJyL0x6VXZINllWRE5lTk9wWXk3ZEJJT1ZqcGpjZFJ5amRHZE1xejBLbEhvUGlnbEdiUEFWCldzdXBmbWI3Nzd1Q3ZtUGRHc1lwb2wvTE9jOU44ZUE0VUdPNk9sWmtWU0NGOTJjSk9oaCtyd1c0cktTOXZFTTcKRzJmaXZVVDZJWCtFamdGQzB0ZytLMkNSSjRoTElnNTFSc0lmdEllNk01MFBpOCsvc3d6andYV2ZuQmpkUWkyegpudC9XYmR3THA2Q2pMR01UdVNrZmVGam00Z2QzM2cyMmRSY09RZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
37+
server.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBdDBGZ3RpazV4a2dZTmlUb0wzSTVlVEU3U29VK1pqZmg1V09tU3dsL3FPSVFNL1ZWCjgrRmk3TTIyblVYaDZGNFpmQmNUaXVURi9qMlV1YXQ3ei9IemtmTU93bjVJeS9KbUE1Wi9vdXVIZzNDSThtdHgKeW53VXhoYmFRak90dFI4UW92Vm9XNVA1MFREdm9mVVZjNUdUOG9YaGFzdHJGMFU4Nlh3clY0Nll5VFdZa1R2bgpkZURsZkJwUlFRYUNRV1lWMTJ0b0p6QnhUZ0dTQ2EwMmJzUXNzZ0p5Q2l6Zmk1OGFhQzl5OThzaUdmTytycVVICmpmeVFldUdiUHlhNWRTbHZ3K1JqcnFTb09PbzAzQnMrWC9hVGJyOVR6a1FSNlRiaHZQVGYwQjJWSE1sRFdDNGQKbXVVbjNRK3NpMkZHemV3eCtDeGVRZUY4SVlISzlSWnArZVlVNXdJREFRQUJBb0lCQUdVS2lDK0lSWkc5V0pRcAovMWVCekl5MUIzTU1TcDZEdTJzR2FiOC82b0tNdXRCYk9sd3c3cUdRdjFxeUdHQk4yaEZnaStidVF2anVyVjArClh4TUYzZjJnSFloQnB4UEVnRmtFRnpZV1ZXNjBrdDNQUGp1ZDlMcFFDV0d0S3Q4TjFOZDFKbWd3Qy9NNjN6WFcKYzFCNGVUR2tmZWlyWmsyN1lGMkFtRWs3bDZTQWw4SXVSbHNvUnpSVjBjTGxDQ1ZqeGZrWFFaTko3d0tnQzk2RAovSXppTVhzZ2h3MEd5NFg2L1FadjZnSGFIdHN0d1lITkFyeUF4eExYMVNQOTJhc1BEdWFXYUFMbEJzdGRSc2RtCkpIdU43dWtuMGFHZEMyeVAwQWlDS2prM1NjUnpVdHhyMlBqb3d4WGYwd3BDYUFYN2xVenk2RWpId29VTmg1VEcKenlhRTRoa0NnWUVBN2o2T21lSzdqN1E4SlMwR2NtTDVxbmMzdkRkUkdSdFlhclk2UzR1Qko2UmVnMkJGSFZ4MApPU2lrV1E2Z2dTTkJvMnEwR3pvcVpYZ3FSdGVtYjh1WUhUR25ad2NlRGd4dVhMcXg2cHRKWTlvajJJcDNXQXFaCjdWei95WGl6aWpIWmJZTW5IQmUyODFEMmFUQzlJRXpwdGg2NmlTZ2VYa1pWOFhNL1BCQjk1ZVVDZ1lFQXhPbXcKM04wY2J6QkxPeUN5Zkdhc2phRnAxNml3djFMYVB2b3ZSaG9ubDhkczVmVy9GdC9kMTY1YVorOCtnTU1rN1kxTwpFdkxmVjFkZXVUS0Y0VDV4dC90ZTVYcU5ocXJRbWt5RmZUY0tFem8rODZnUnpxTWRqSXh1eDhjN3FRWjg3Y2xxCitSNTBUbzZraGt1YXlpc1hWT05CV2VxekFSWk9QWmh2L2xSaUl0c0NnWUVBb0xZZ1dkeGg2OW1JTFFmSGJvZ24KcFA5UTRLMXNEb1NzeXlkc0FhUDBsdnBCSzF4WW95ckgxL3I3aW52Y2QrQ0JtYXdVSEwzSzliSHV5dVVVQ0J3TgoyN3V3RWtieDFrWTZlR0VVUFk5TkhZZDhZTWxmSWt2Y2RBc2xIUkpJQXJRSDJPRDlFKzFIWTdFODE4Nmg5ZFVNClh1Y3hxKzRkTmprNkptczR2OXJjSXFVQ2dZQUJrTDRJTTNYTGFIM2duWFR0eWo4cTdSS1RWVkw2WW1VN3hPOWwKUmtYMFRmQ09yM0p5Y3hzbllNcDFNeEN6STFvQ3pYSEdjc25WdnVzUTI5YjJvSEYwL2ZtV0ozQkNsczhMdXZvQQpzZFJSck0vZFRnTytPY3U5VjB4MktCNVFUSzNua2dkWXJhWk5EWk0vUWhDYjlOVzlwZ1RaK3lTcktJczhzQjZMCnpnM3Rxd0tCZ1FDTzhrNkVZRVVoZW5DMWNldSs0ejdEWmZrL01CTWlJci9ob1NySllaSmVOWldBSDJOd2p5M0cKUlhYWTZzdVRZRFRXVUJYWTZZMDl2STdOQzhmRk11ZmhyM28zaThMMVNWMlNCQ0VyMlV4T3RHWnN4TEVMMnhUQwpCbFIrMEF2MnUzSFBKRTBiV3ptVGh3U1RlQ2h0Z3pZQ2tIUlZlNlJMZVhET0w3SkFnQWNyM1E9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
38+
---
39+
apiVersion: apps/v1
40+
kind: StatefulSet
41+
metadata:
42+
name: hello-osiris
43+
labels:
44+
app: hello-osiris
45+
annotations:
46+
osiris.deislabs.io/enabled: "true"
47+
osiris.deislabs.io/minReplicas: "1"
48+
spec:
49+
replicas: 1
50+
serviceName: hello-osiris
51+
selector:
52+
matchLabels:
53+
app: hello-osiris
54+
template:
55+
metadata:
56+
labels:
57+
app: hello-osiris
58+
spec:
59+
containers:
60+
- name: hello-osiris
61+
image: krancour/hello-osiris:v0.1.0
62+
args:
63+
- --https-cert
64+
- /hello-osiris/cert/server.crt
65+
- --https-key
66+
- /hello-osiris/cert/server.key
67+
ports:
68+
- containerPort: 8080
69+
- containerPort: 8081
70+
- containerPort: 8082
71+
- containerPort: 4430
72+
volumeMounts:
73+
- name: cert
74+
mountPath: /hello-osiris/cert
75+
readOnly: true
76+
livenessProbe:
77+
httpGet:
78+
path: /healthz
79+
port: 8080
80+
readinessProbe:
81+
httpGet:
82+
path: /healthz
83+
port: 8080
84+
volumes:
85+
- name: cert
86+
secret:
87+
secretName: hello-osiris-cert

pkg/deployments/activator/activating.go

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,23 @@ import (
1212

1313
func (a *activator) activateDeployment(
1414
app *app,
15-
) (*deploymentActivation, error) {
15+
) (*appActivation, error) {
1616
deploymentsClient := a.kubeClient.AppsV1().Deployments(app.namespace)
1717
deployment, err := deploymentsClient.Get(
18-
app.deploymentName,
18+
app.name,
1919
metav1.GetOptions{},
2020
)
2121
if err != nil {
2222
return nil, err
2323
}
24-
da := &deploymentActivation{
24+
da := &appActivation{
2525
readyAppPodIPs: map[string]struct{}{},
2626
successCh: make(chan struct{}),
2727
timeoutCh: make(chan struct{}),
2828
}
2929
glog.Infof(
3030
"Activating deployment %s in namespace %s",
31-
app.deploymentName,
31+
app.name,
3232
app.namespace,
3333
)
3434
go da.watchForCompletion(
@@ -50,7 +50,54 @@ func (a *activator) activateDeployment(
5050
}}
5151
patchesBytes, _ := json.Marshal(patches)
5252
_, err = deploymentsClient.Patch(
53-
app.deploymentName,
53+
app.name,
54+
k8s_types.JSONPatchType,
55+
patchesBytes,
56+
)
57+
return da, err
58+
}
59+
60+
func (a *activator) activateStatefulSet(
61+
app *app,
62+
) (*appActivation, error) {
63+
statefulSetsClient := a.kubeClient.AppsV1().StatefulSets(app.namespace)
64+
statefulSet, err := statefulSetsClient.Get(
65+
app.name,
66+
metav1.GetOptions{},
67+
)
68+
if err != nil {
69+
return nil, err
70+
}
71+
da := &appActivation{
72+
readyAppPodIPs: map[string]struct{}{},
73+
successCh: make(chan struct{}),
74+
timeoutCh: make(chan struct{}),
75+
}
76+
glog.Infof(
77+
"Activating statefulSet %s in namespace %s",
78+
app.name,
79+
app.namespace,
80+
)
81+
go da.watchForCompletion(
82+
a.kubeClient,
83+
app,
84+
labels.Set(statefulSet.Spec.Selector.MatchLabels).AsSelector(),
85+
)
86+
if statefulSet.Spec.Replicas == nil || *statefulSet.Spec.Replicas > 0 {
87+
// We don't need to do this, as it turns out! Scaling is either already
88+
// in progress-- perhaps initiated by another process-- or may even be
89+
// completed already. Just return dr and allow the caller to move on to
90+
// verifying / waiting for this activation to be complete.
91+
return da, nil
92+
}
93+
patches := []kubernetes.PatchOperation{{
94+
Op: "replace",
95+
Path: "/spec/replicas",
96+
Value: kubernetes.GetMinReplicas(statefulSet.Annotations, 1),
97+
}}
98+
patchesBytes, _ := json.Marshal(patches)
99+
_, err = statefulSetsClient.Patch(
100+
app.name,
54101
k8s_types.JSONPatchType,
55102
patchesBytes,
56103
)

pkg/deployments/activator/activator.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ type activator struct {
2929
nodeAddresses map[string]struct{}
3030
appsByHost map[string]*app
3131
indicesLock sync.RWMutex
32-
deploymentActivations map[string]*deploymentActivation
33-
deploymentActivationsLock sync.Mutex
32+
appActivations map[string]*appActivation
33+
appActivationsLock sync.Mutex
3434
dynamicProxyListenAddrStr string
3535
dynamicProxy tcp.DynamicProxy
3636
httpClient *http.Client
@@ -56,7 +56,7 @@ func NewActivator(kubeClient kubernetes.Interface) (Activator, error) {
5656
services: map[string]*corev1.Service{},
5757
nodeAddresses: map[string]struct{}{},
5858
appsByHost: map[string]*app{},
59-
deploymentActivations: map[string]*deploymentActivation{},
59+
appActivations: map[string]*appActivation{},
6060
httpClient: &http.Client{
6161
Timeout: time.Minute * 1,
6262
},
@@ -127,7 +127,7 @@ func (a *activator) syncService(obj interface{}) {
127127
a.indicesLock.Lock()
128128
defer a.indicesLock.Unlock()
129129
svc := obj.(*corev1.Service)
130-
svcKey := getKey(svc.Namespace, svc.Name)
130+
svcKey := getKey(svc.Namespace, "Service", svc.Name)
131131
if k8s.ResourceIsOsirisEnabled(svc.Annotations) {
132132
a.services[svcKey] = svc
133133
} else {
@@ -140,7 +140,7 @@ func (a *activator) syncDeletedService(obj interface{}) {
140140
a.indicesLock.Lock()
141141
defer a.indicesLock.Unlock()
142142
svc := obj.(*corev1.Service)
143-
svcKey := getKey(svc.Namespace, svc.Name)
143+
svcKey := getKey(svc.Namespace, "Service", svc.Name)
144144
delete(a.services, svcKey)
145145
a.updateIndex()
146146
}

pkg/deployments/activator/app.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
package activator
22

3+
type appKind string
4+
5+
const (
6+
appKindDeployment appKind = "Deployment"
7+
appKindStatefulSet appKind = "StatefulSet"
8+
)
9+
310
type app struct {
4-
namespace string
5-
serviceName string
6-
deploymentName string
7-
targetHost string
8-
targetPort int
11+
namespace string
12+
serviceName string
13+
name string
14+
kind appKind
15+
targetHost string
16+
targetPort int
917
}

0 commit comments

Comments
 (0)