Skip to content

Commit 3ec0de5

Browse files
committed
sgx: set epc limits via NRI annotations
Signed-off-by: Mikko Ylinen <[email protected]>
1 parent 2c6e3b1 commit 3ec0de5

File tree

9 files changed

+104
-0
lines changed

9 files changed

+104
-0
lines changed

deployments/operator/crd/bases/deviceplugin.intel.com_sgxdeviceplugins.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ spec:
7878
description: NodeSelector provides a simple way to constrain device
7979
plugin pods to nodes with particular labels.
8080
type: object
81+
nriImage:
82+
description: |-
83+
NRIImage is a container image with SGX Node Resource Interface (NRI) plugin executable. Set
84+
this value if SGX EPC cgroups limits enforcement is wanted.
85+
TODO: is this a good name?
86+
type: string
8187
provisionLimit:
8288
description: ProvisionLimit is a number of containers that can share
8389
the same SGX provision device.

deployments/operator/samples/deviceplugin_v1_sgxdeviceplugin.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ metadata:
44
name: sgxdeviceplugin-sample
55
spec:
66
image: intel/intel-sgx-plugin:0.30.0
7+
nriImage: ghcr.io/containers/nri-plugins/nri-sgx-epc:v0.3.2
78
enclaveLimit: 110
89
provisionLimit: 110
910
logLevel: 4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
resources:
2+
- ../../base
3+
4+
patches:
5+
- path: nri_plugin_patch.yaml
6+
target:
7+
name: intel-sgx-plugin
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
apiVersion: apps/v1
2+
kind: DaemonSet
3+
metadata:
4+
name: intel-sgx-plugin
5+
spec:
6+
template:
7+
spec:
8+
containers:
9+
- name: nri-sgx-epc
10+
image: ghcr.io/containers/nri-plugins/nri-sgx-epc:unstable
11+
securityContext:
12+
readOnlyRootFilesystem: true
13+
allowPrivilegeEscalation: false
14+
imagePullPolicy: IfNotPresent
15+
volumeMounts:
16+
- name: nrisockets
17+
mountPath: /var/run/nri
18+
volumes:
19+
- name: nrisockets
20+
hostPath:
21+
path: /var/run/nri

pkg/apis/deviceplugin/v1/sgxdeviceplugin_types.go

+5
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ type SgxDevicePluginSpec struct {
3535
// Recommendation is to leave this unset and prefer the SGX NodeFeatureRule instead.
3636
InitImage string `json:"initImage,omitempty"`
3737

38+
// NRIImage is a container image with SGX Node Resource Interface (NRI) plugin executable. Set
39+
// this value if SGX EPC cgroups limits enforcement is wanted.
40+
// TODO: is this a good name?
41+
NRIImage string `json:"nriImage,omitempty"`
42+
3843
// Specialized nodes (e.g., with accelerators) can be Tainted to make sure unwanted pods are not scheduled on them. Tolerations can be set for the plugin pod to neutralize the Taint.
3944
Tolerations []v1.Toleration `json:"tolerations,omitempty"`
4045

pkg/controllers/sgx/controller.go

+45
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,27 @@ func setInitContainer(spec *v1.PodSpec, imageName string) {
112112
addVolumeIfMissing(spec, "nfd-features", "/etc/kubernetes/node-feature-discovery/source.d/", v1.HostPathDirectoryOrCreate)
113113
}
114114

115+
func setNRIContainer(spec *v1.PodSpec, imageName string) {
116+
yes := true
117+
no := false
118+
spec.Containers = append(spec.Containers, v1.Container{
119+
Name: "nri-sgx-epc",
120+
Image: imageName,
121+
ImagePullPolicy: "IfNotPresent",
122+
SecurityContext: &v1.SecurityContext{
123+
ReadOnlyRootFilesystem: &yes,
124+
AllowPrivilegeEscalation: &no,
125+
},
126+
VolumeMounts: []v1.VolumeMount{
127+
{
128+
Name: "nrisockets",
129+
MountPath: "/var/run/nri",
130+
},
131+
},
132+
})
133+
addVolumeIfMissing(spec, "nrisockets", "/var/run/nri", v1.HostPathDirectoryOrCreate)
134+
}
135+
115136
func (c *controller) NewDaemonSet(rawObj client.Object) *apps.DaemonSet {
116137
devicePlugin := rawObj.(*devicepluginv1.SgxDevicePlugin)
117138

@@ -135,6 +156,10 @@ func (c *controller) NewDaemonSet(rawObj client.Object) *apps.DaemonSet {
135156
if devicePlugin.Spec.InitImage != "" {
136157
setInitContainer(&daemonSet.Spec.Template.Spec, devicePlugin.Spec.InitImage)
137158
}
159+
// add the optional NRI plugin container
160+
if devicePlugin.Spec.NRIImage != "" {
161+
setNRIContainer(&daemonSet.Spec.Template.Spec, devicePlugin.Spec.NRIImage)
162+
}
138163

139164
return daemonSet
140165
}
@@ -171,6 +196,26 @@ func (c *controller) UpdateDaemonSet(rawObj client.Object, ds *apps.DaemonSet) (
171196
updated = true
172197
}
173198

199+
// remove NRI plugin
200+
if len(ds.Spec.Template.Spec.Containers) > 1 && dp.Spec.NRIImage == "" {
201+
ds.Spec.Template.Spec.Containers = []v1.Container{ds.Spec.Template.Spec.Containers[0]}
202+
ds.Spec.Template.Spec.Volumes = removeVolume(ds.Spec.Template.Spec.Volumes, "nrisockets")
203+
updated = true
204+
}
205+
206+
// update NRI plugin image
207+
if len(ds.Spec.Template.Spec.Containers) > 1 && ds.Spec.Template.Spec.Containers[1].Image != dp.Spec.NRIImage {
208+
ds.Spec.Template.Spec.Containers[1].Image = dp.Spec.NRIImage
209+
updated = true
210+
}
211+
212+
// add NRI plugin image
213+
if len(ds.Spec.Template.Spec.Containers) == 1 && dp.Spec.NRIImage != "" {
214+
setNRIContainer(&ds.Spec.Template.Spec, dp.Spec.NRIImage)
215+
216+
updated = true
217+
}
218+
174219
if len(dp.Spec.NodeSelector) > 0 {
175220
if !reflect.DeepEqual(ds.Spec.Template.Spec.NodeSelector, dp.Spec.NodeSelector) {
176221
ds.Spec.Template.Spec.NodeSelector = dp.Spec.NodeSelector

pkg/webhooks/sgx/sgx.go

+3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ func (s *Mutator) SetupWebhookWithManager(mgr ctrl.Manager) error {
4343
}
4444

4545
const (
46+
epcLimitKey = "epc-limit.nri.io/container"
4647
namespace = "sgx.intel.com"
4748
encl = namespace + "/enclave"
4849
epc = namespace + "/epc"
@@ -156,6 +157,8 @@ func (s *Mutator) Default(ctx context.Context, obj runtime.Object) error {
156157
continue
157158
}
158159

160+
pod.Annotations[fmt.Sprintf("%s.%s", epcLimitKey, container.Name)] = fmt.Sprintf("%d", epcSize)
161+
159162
totalEpc += epcSize
160163

161164
// Quote Generation Modes:

test/e2e/sgxadmissionwebhook/sgxaadmissionwebhook.go

+8
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ func describe() {
6969

7070
ginkgo.By("checking the pod total EPC size annotation is correctly set")
7171
gomega.Expect(pod.Annotations["sgx.intel.com/epc"]).To(gomega.Equal("1Mi"))
72+
gomega.Expect(pod.Annotations["epc-limit.nri.io/container.test"]).To(gomega.Equal("1048576"))
7273
})
7374
ginkgo.It("mutates created pods when the container contains the quote generation libraries", func(ctx context.Context) {
7475
ginkgo.By("submitting the pod")
@@ -79,6 +80,7 @@ func describe() {
7980

8081
ginkgo.By("checking the pod total EPC size annotation is correctly set")
8182
gomega.Expect(pod.Annotations["sgx.intel.com/epc"]).To(gomega.Equal("1Mi"))
83+
gomega.Expect(pod.Annotations["epc-limit.nri.io/container.test"]).To(gomega.Equal("1048576"))
8284
})
8385
ginkgo.It("mutates created pods when the container uses aesmd from a side-car container to generate quotes", func(ctx context.Context) {
8486
ginkgo.By("submitting the pod")
@@ -93,6 +95,8 @@ func describe() {
9395
gomega.Expect(pod.Spec.Containers[0].Env[0].Value).To(gomega.Equal("1"))
9496
ginkgo.By("checking the pod total EPC size annotation is correctly set")
9597
gomega.Expect(pod.Annotations["sgx.intel.com/epc"]).To(gomega.Equal("2Mi"))
98+
gomega.Expect(pod.Annotations["epc-limit.nri.io/container.test"]).To(gomega.Equal("1048576"))
99+
gomega.Expect(pod.Annotations["epc-limit.nri.io/container.aesmd"]).To(gomega.Equal("1048576"))
96100
})
97101
ginkgo.It("mutates created pods where one container uses host/daemonset aesmd to generate quotes", func(ctx context.Context) {
98102
ginkgo.By("submitting the pod")
@@ -106,6 +110,7 @@ func describe() {
106110
gomega.Expect(pod.Spec.Containers[0].Env[0].Value).To(gomega.Equal("1"))
107111
ginkgo.By("checking the pod total EPC size annotation is correctly set")
108112
gomega.Expect(pod.Annotations["sgx.intel.com/epc"]).To(gomega.Equal("1Mi"))
113+
gomega.Expect(pod.Annotations["epc-limit.nri.io/container.test"]).To(gomega.Equal("1048576"))
109114
})
110115
ginkgo.It("mutates created pods where three containers use host/daemonset aesmd to generate quotes", func(ctx context.Context) {
111116
ginkgo.By("submitting the pod")
@@ -125,6 +130,9 @@ func describe() {
125130
gomega.Expect(pod.Spec.Containers[2].Env[0].Value).To(gomega.Equal("1"))
126131
ginkgo.By("checking the pod total EPC size annotation is correctly set")
127132
gomega.Expect(pod.Annotations["sgx.intel.com/epc"]).To(gomega.Equal("3Mi"))
133+
gomega.Expect(pod.Annotations["epc-limit.nri.io/container.test1"]).To(gomega.Equal("1048576"))
134+
gomega.Expect(pod.Annotations["epc-limit.nri.io/container.test2"]).To(gomega.Equal("1048576"))
135+
gomega.Expect(pod.Annotations["epc-limit.nri.io/container.test3"]).To(gomega.Equal("1048576"))
128136
})
129137
ginkgo.It("checks that Volumes and VolumeMounts are created only once", func(ctx context.Context) {
130138
ginkgo.By("submitting the pod")

test/envtest/sgxdeviceplugin_controller_test.go

+8
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ var _ = Describe("SgxDevicePlugin Controller", func() {
3939
spec := devicepluginv1.SgxDevicePluginSpec{
4040
Image: "sgx-testimage",
4141
InitImage: "sgx-testinitimage",
42+
NRIImage: "sgx-testnriimage",
4243
NodeSelector: map[string]string{"sgx-nodeselector": "true"},
4344
}
4445

@@ -78,13 +79,15 @@ var _ = Describe("SgxDevicePlugin Controller", func() {
7879
By("updating SgxDevicePlugin successfully")
7980
updatedImage := "updated-sgx-testimage"
8081
updatedInitImage := "updated-sgx-testinitimage"
82+
updatedNRIImage := "updated-sgx-testnriimage"
8183
updatedLogLevel := 2
8284
updatedEnclaveLimit := 2
8385
updatedProvisionLimit := 2
8486
updatedNodeSelector := map[string]string{"updated-sgx-nodeselector": "true"}
8587

8688
fetched.Spec.Image = updatedImage
8789
fetched.Spec.InitImage = updatedInitImage
90+
fetched.Spec.NRIImage = updatedNRIImage
8891
fetched.Spec.LogLevel = updatedLogLevel
8992
fetched.Spec.EnclaveLimit = updatedEnclaveLimit
9093
fetched.Spec.ProvisionLimit = updatedProvisionLimit
@@ -114,13 +117,17 @@ var _ = Describe("SgxDevicePlugin Controller", func() {
114117
Expect(ds.Spec.Template.Spec.Containers[0].Args).Should(ConsistOf(expectArgs))
115118
Expect(ds.Spec.Template.Spec.Containers[0].Image).Should(Equal(updatedImage))
116119
Expect(ds.Spec.Template.Spec.InitContainers).To(HaveLen(1))
120+
Expect(ds.Spec.Template.Spec.Containers).To(HaveLen(2))
121+
Expect(ds.Spec.Template.Spec.Containers[1].Image).Should(Equal(updatedNRIImage))
117122
Expect(ds.Spec.Template.Spec.InitContainers[0].Image).To(Equal(updatedInitImage))
118123
Expect(ds.Spec.Template.Spec.NodeSelector).Should(Equal(updatedNodeSelector))
119124

120125
By("updating SgxDevicePlugin with different values successfully")
121126
updatedInitImage = ""
127+
updatedNRIImage = ""
122128
updatedNodeSelector = map[string]string{}
123129
fetched.Spec.InitImage = updatedInitImage
130+
fetched.Spec.NRIImage = updatedNRIImage
124131
fetched.Spec.NodeSelector = updatedNodeSelector
125132

126133
Expect(k8sClient.Update(context.Background(), fetched)).Should(Succeed())
@@ -130,6 +137,7 @@ var _ = Describe("SgxDevicePlugin Controller", func() {
130137
err = k8sClient.Get(context.Background(), types.NamespacedName{Namespace: ns, Name: expectedDsName}, ds)
131138
Expect(err).To(BeNil())
132139
Expect(ds.Spec.Template.Spec.InitContainers).To(HaveLen(0))
140+
Expect(ds.Spec.Template.Spec.Containers).To(HaveLen(1))
133141
Expect(ds.Spec.Template.Spec.NodeSelector).Should(And(HaveLen(1), HaveKeyWithValue("kubernetes.io/arch", "amd64")))
134142

135143
By("updating SgxDevicePlugin with tolerations")

0 commit comments

Comments
 (0)