Skip to content

Commit

Permalink
feat: support overriding base OCI spec for CRI
Browse files Browse the repository at this point in the history
Fixes #9827

Signed-off-by: Andrey Smirnov <[email protected]>
  • Loading branch information
smira committed Nov 28, 2024
1 parent fc3b315 commit 91b1f30
Show file tree
Hide file tree
Showing 18 changed files with 471 additions and 6 deletions.
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -735,7 +735,7 @@ COPY --chmod=0644 hack/udevd/90-selinux.rules /rootfs/usr/lib/udev/rules.d/
COPY --chmod=0644 hack/lvm.conf /rootfs/etc/lvm/lvm.conf
RUN <<END
ln -s /usr/share/zoneinfo/Etc/UTC /rootfs/etc/localtime
touch /rootfs/etc/{extensions.yaml,resolv.conf,hosts,os-release,machine-id,cri/conf.d/cri.toml,cri/conf.d/01-registries.part,cri/conf.d/20-customization.part,ssl/certs/ca-certificates}
touch /rootfs/etc/{extensions.yaml,resolv.conf,hosts,os-release,machine-id,cri/conf.d/cri.toml,cri/conf.d/01-registries.part,cri/conf.d/20-customization.part,cri/conf.d/base-spec.json,ssl/certs/ca-certificates}
ln -s ca-certificates /rootfs/etc/ssl/certs/ca-certificates.crt
ln -s /etc/ssl /rootfs/etc/pki
ln -s /etc/ssl /rootfs/usr/share/ca-certificates
Expand Down Expand Up @@ -809,7 +809,7 @@ COPY --chmod=0644 hack/udevd/90-selinux.rules /rootfs/usr/lib/udev/rules.d/
COPY --chmod=0644 hack/lvm.conf /rootfs/etc/lvm/lvm.conf
RUN <<END
ln -s /usr/share/zoneinfo/Etc/UTC /rootfs/etc/localtime
touch /rootfs/etc/{extensions.yaml,resolv.conf,hosts,os-release,machine-id,cri/conf.d/cri.toml,cri/conf.d/01-registries.part,cri/conf.d/20-customization.part,ssl/certs/ca-certificates}
touch /rootfs/etc/{extensions.yaml,resolv.conf,hosts,os-release,machine-id,cri/conf.d/cri.toml,cri/conf.d/01-registries.part,cri/conf.d/20-customization.part,cri/conf.d/base-spec.json,ssl/certs/ca-certificates}
ln -s /etc/ssl /rootfs/etc/pki
ln -s ca-certificates /rootfs/etc/ssl/certs/ca-certificates.crt
ln -s /etc/ssl /rootfs/usr/share/ca-certificates
Expand Down
3 changes: 3 additions & 0 deletions hack/cri-plugin.part
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ version = 3

[plugins."io.containerd.cri.v1.images"]
discard_unpacked_layers = true

[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.runc]
base_runtime_spec = "/etc/cri/conf.d/base-spec.json"
137 changes: 137 additions & 0 deletions internal/app/machined/pkg/controllers/files/cri_base_runtime_spec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package files

import (
"context"
"encoding/json"
"fmt"

"github.com/containerd/containerd/v2/core/containers"
"github.com/containerd/containerd/v2/pkg/namespaces"
"github.com/containerd/containerd/v2/pkg/oci"
"github.com/containerd/platforms"
"github.com/cosi-project/runtime/pkg/controller"
"github.com/cosi-project/runtime/pkg/safe"
"github.com/cosi-project/runtime/pkg/state"
"github.com/siderolabs/gen/optional"
"go.uber.org/zap"

"github.com/siderolabs/talos/pkg/machinery/config/merge"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/resources/config"
"github.com/siderolabs/talos/pkg/machinery/resources/files"
)

// CRIBaseRuntimeSpecController generates parts of the CRI config for base OCI runtime configuration.
type CRIBaseRuntimeSpecController struct{}

// Name implements controller.Controller interface.
func (ctrl *CRIBaseRuntimeSpecController) Name() string {
return "files.CRIBaseRuntimeSpecController"
}

// Inputs implements controller.Controller interface.
func (ctrl *CRIBaseRuntimeSpecController) Inputs() []controller.Input {
return []controller.Input{
{
Namespace: config.NamespaceName,
Type: config.MachineConfigType,
ID: optional.Some(config.V1Alpha1ID),
Kind: controller.InputWeak,
},
}
}

// Outputs implements controller.Controller interface.
func (ctrl *CRIBaseRuntimeSpecController) Outputs() []controller.Output {
return []controller.Output{
{
Type: files.EtcFileSpecType,
Kind: controller.OutputShared,
},
}
}

// Run implements controller.Controller interface.
//
//nolint:gocyclo
func (ctrl *CRIBaseRuntimeSpecController) Run(ctx context.Context, r controller.Runtime, _ *zap.Logger) error {
for {
select {
case <-ctx.Done():
return nil
case <-r.EventCh():
}

cfg, err := safe.ReaderGetByID[*config.MachineConfig](ctx, r, config.V1Alpha1ID)
if err != nil {
if state.IsNotFoundError(err) {
// wait for machine config to be available
continue
}

return fmt.Errorf("error getting machine config: %w", err)
}

if cfg.Config().Machine() == nil {
// wait for machine config to be available
continue
}

platform := platforms.DefaultString()

defaultSpec, err := oci.GenerateSpecWithPlatform(
namespaces.WithNamespace(ctx, constants.K8sContainerdNamespace),
nil,
platform,
&containers.Container{},
)
if err != nil {
return fmt.Errorf("error generating default spec: %w", err)
}

// compatibility with CRI defaults:
// * remove default rlimits (See https://github.com/containerd/cri/issues/515)
defaultSpec.Process.Rlimits = nil

if len(cfg.Config().Machine().BaseRuntimeSpecOverrides()) > 0 {
var overrides oci.Spec

jsonOverrides, err := json.Marshal(cfg.Config().Machine().BaseRuntimeSpecOverrides())
if err != nil {
return fmt.Errorf("error marshaling runtime spec overrides: %w", err)
}

if err := json.Unmarshal(jsonOverrides, &overrides); err != nil {
return fmt.Errorf("error unmarshaling runtime spec overrides: %w", err)
}

if err := merge.Merge(defaultSpec, &overrides); err != nil {
return fmt.Errorf("error merging runtime spec overrides: %w", err)
}
}

contents, err := json.Marshal(defaultSpec)
if err != nil {
return fmt.Errorf("error marshaling runtime spec: %w", err)
}

if err := safe.WriterModify(ctx, r, files.NewEtcFileSpec(files.NamespaceName, constants.CRIBaseRuntimeSpec),
func(r *files.EtcFileSpec) error {
spec := r.TypedSpec()

spec.Contents = contents
spec.Mode = 0o600
spec.SelinuxLabel = constants.EtcSelinuxLabel

return nil
}); err != nil {
return fmt.Errorf("error modifying resource: %w", err)
}

r.ResetRestartBackoff()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package files_test

import (
"encoding/json"
"testing"
"time"

"github.com/containerd/containerd/v2/pkg/oci"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"

"github.com/siderolabs/talos/internal/app/machined/pkg/controllers/ctest"
filesctrl "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/files"
"github.com/siderolabs/talos/pkg/machinery/config/container"
"github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/resources/config"
"github.com/siderolabs/talos/pkg/machinery/resources/files"
)

type CRIBaseRuntimeSpecSuite struct {
ctest.DefaultSuite
}

func (suite *CRIBaseRuntimeSpecSuite) TestDefaults() {
cfg := config.NewMachineConfig(
container.NewV1Alpha1(
&v1alpha1.Config{
ConfigVersion: "v1alpha1",
MachineConfig: &v1alpha1.MachineConfig{
MachineType: "controlplane",
},
},
),
)

suite.Require().NoError(suite.State().Create(suite.Ctx(), cfg))

ctest.AssertResource(suite, constants.CRIBaseRuntimeSpec, func(etcFile *files.EtcFileSpec, asrt *assert.Assertions) {
contents := etcFile.TypedSpec().Contents

var ociSpec oci.Spec

asrt.NoError(json.Unmarshal(contents, &ociSpec))

asrt.Empty(ociSpec.Process.Rlimits)
})
}

func (suite *CRIBaseRuntimeSpecSuite) TestOverrides() {
cfg := config.NewMachineConfig(
container.NewV1Alpha1(
&v1alpha1.Config{
ConfigVersion: "v1alpha1",
MachineConfig: &v1alpha1.MachineConfig{
MachineType: "controlplane",
MachineBaseRuntimeSpecOverrides: v1alpha1.Unstructured{
Object: map[string]any{
"process": map[string]any{
"rlimits": []map[string]any{
{
"type": "RLIMIT_NOFILE",
"hard": 1024,
"soft": 1024,
},
},
},
},
},
},
},
),
)

suite.Require().NoError(suite.State().Create(suite.Ctx(), cfg))

ctest.AssertResource(suite, constants.CRIBaseRuntimeSpec, func(etcFile *files.EtcFileSpec, asrt *assert.Assertions) {
contents := etcFile.TypedSpec().Contents

var ociSpec oci.Spec

asrt.NoError(json.Unmarshal(contents, &ociSpec))

asrt.NotEmpty(ociSpec.Process.Rlimits)
asrt.Equal("RLIMIT_NOFILE", ociSpec.Process.Rlimits[0].Type)
asrt.Equal(uint64(1024), ociSpec.Process.Rlimits[0].Hard)
asrt.Equal(uint64(1024), ociSpec.Process.Rlimits[0].Soft)
})
}

func TestCRIBaseRuntimeSpecSuite(t *testing.T) {
t.Parallel()

suite.Run(t, &CRIBaseRuntimeSpecSuite{
DefaultSuite: ctest.DefaultSuite{
Timeout: 10 * time.Second,
AfterSetup: func(suite *ctest.DefaultSuite) {
suite.Require().NoError(suite.Runtime().RegisterController(&filesctrl.CRIBaseRuntimeSpecController{}))
},
},
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error
&etcd.PKIController{},
&etcd.SpecController{},
&etcd.MemberController{},
&files.CRIBaseRuntimeSpecController{},
&files.CRIConfigPartsController{},
&files.CRIRegistryConfigController{},
&files.EtcFileController{
Expand Down
102 changes: 102 additions & 0 deletions internal/integration/api/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ package api
import (
"context"
"strings"
"testing"
"time"

"github.com/siderolabs/talos/internal/integration/base"
"github.com/siderolabs/talos/pkg/machinery/config/machine"
"github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1"
)

// CommonSuite verifies some default settings such as ulimits.
Expand Down Expand Up @@ -157,6 +160,105 @@ func (suite *CommonSuite) TestDNSResolver() {
}
}

// TestBaseOCISpec verifies that the base OCI spec can be modified.
func (suite *CommonSuite) TestBaseOCISpec() {
if suite.Cluster != nil && suite.Cluster.Provisioner() == "docker" {
suite.T().Skip("skipping ulimits test since provisioner is docker")
}

if testing.Short() {
suite.T().Skip("skipping test in short mode.")
}

node := suite.RandomDiscoveredNodeInternalIP(machine.TypeWorker)

k8sNode, err := suite.GetK8sNodeByInternalIP(suite.ctx, node)
suite.Require().NoError(err)

nodeName := k8sNode.Name

suite.T().Logf("adjusting base OCI specs on %s/%s", node, nodeName)

suite.AssertRebooted(
suite.ctx, node, func(nodeCtx context.Context) error {
suite.PatchMachineConfig(nodeCtx, &v1alpha1.Config{
MachineConfig: &v1alpha1.MachineConfig{
MachineBaseRuntimeSpecOverrides: v1alpha1.Unstructured{
Object: map[string]any{
"process": map[string]any{
"rlimits": []map[string]any{
{
"type": "RLIMIT_NOFILE",
"hard": 1024,
"soft": 1024,
},
},
},
},
},
},
})

return nil
}, assertRebootedRebootTimeout,
)

suite.ClearConnectionRefused(suite.ctx, node)

ociUlimits1PodDef, err := suite.NewPod("oci-ulimits-test-1")
suite.Require().NoError(err)

ociUlimits1PodDef = ociUlimits1PodDef.WithNodeName(nodeName)

suite.Require().NoError(ociUlimits1PodDef.Create(suite.ctx, 5*time.Minute))

stdout, stderr, err := ociUlimits1PodDef.Exec(
suite.ctx,
"ulimit -n",
)
suite.Require().NoError(err)

suite.Require().Equal("", stderr)
suite.Require().Equal("1024\n", stdout)

suite.Assert().NoError(ociUlimits1PodDef.Delete(suite.ctx))

// revert the patch
suite.AssertRebooted(
suite.ctx, node, func(nodeCtx context.Context) error {
suite.PatchMachineConfig(nodeCtx, map[string]any{
"machine": map[string]any{
"baseRuntimeSpecOverrides": map[string]any{
"$patch": "delete",
},
},
})

return nil
}, assertRebootedRebootTimeout,
)

suite.ClearConnectionRefused(suite.ctx, node)

ociUlimits2PodDef, err := suite.NewPod("oci-ulimits-test-2")
suite.Require().NoError(err)

ociUlimits2PodDef = ociUlimits2PodDef.WithNodeName(nodeName)

suite.Require().NoError(ociUlimits2PodDef.Create(suite.ctx, 5*time.Minute))

stdout, stderr, err = ociUlimits2PodDef.Exec(
suite.ctx,
"ulimit -n",
)
suite.Require().NoError(err)

suite.Require().Equal("", stderr)
suite.Require().Equal("1048576\n", stdout)

suite.Assert().NoError(ociUlimits2PodDef.Delete(suite.ctx))
}

func init() {
allSuites = append(allSuites, &CommonSuite{})
}
Loading

0 comments on commit 91b1f30

Please sign in to comment.