Skip to content

Commit

Permalink
Merge pull request #3406 from jnummelin/feat/standalone-autopilot-upd…
Browse files Browse the repository at this point in the history
…ates

Standalone autopilot update logic
  • Loading branch information
jnummelin authored Sep 25, 2023
2 parents c431615 + a542a86 commit c2495dd
Show file tree
Hide file tree
Showing 25 changed files with 1,618 additions and 38 deletions.
14 changes: 12 additions & 2 deletions cmd/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"syscall"
"time"

"github.com/avast/retry-go"
workercmd "github.com/k0sproject/k0s/cmd/worker"
"github.com/k0sproject/k0s/internal/pkg/dir"
"github.com/k0sproject/k0s/internal/pkg/file"
Expand All @@ -36,6 +37,7 @@ import (
"github.com/k0sproject/k0s/internal/pkg/sysinfo"
"github.com/k0sproject/k0s/pkg/apis/k0s/v1beta1"
"github.com/k0sproject/k0s/pkg/applier"
apclient "github.com/k0sproject/k0s/pkg/autopilot/client"
"github.com/k0sproject/k0s/pkg/build"
"github.com/k0sproject/k0s/pkg/certificate"
"github.com/k0sproject/k0s/pkg/component/controller"
Expand All @@ -48,12 +50,11 @@ import (
"github.com/k0sproject/k0s/pkg/component/worker"
"github.com/k0sproject/k0s/pkg/config"
"github.com/k0sproject/k0s/pkg/constant"
k0sctx "github.com/k0sproject/k0s/pkg/context"
"github.com/k0sproject/k0s/pkg/kubernetes"
"github.com/k0sproject/k0s/pkg/performance"
"github.com/k0sproject/k0s/pkg/telemetry"
"github.com/k0sproject/k0s/pkg/token"

"github.com/avast/retry-go"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"golang.org/x/exp/slices"
Expand Down Expand Up @@ -142,6 +143,9 @@ func (c *command) start(ctx context.Context) error {
return fmt.Errorf("invalid node config: %w", errors.Join(errs...))
}

// Add the node config to the context so it can be used by components deep in the "stack"
ctx = context.WithValue(ctx, k0sctx.ContextNodeConfigKey, nodeConfig)

nodeComponents := manager.New(prober.DefaultProber)
clusterComponents := manager.New(prober.DefaultProber)

Expand Down Expand Up @@ -505,6 +509,12 @@ func (c *command) start(ctx context.Context) error {
EnableWorker: c.EnableWorker,
})

apClientFactory, err := apclient.NewClientFactory(adminClientFactory.GetRESTConfig())
if err != nil {
return err
}
clusterComponents.Add(ctx, controller.NewUpdateProber(apClientFactory, leaderElector))

perfTimer.Checkpoint("starting-cluster-components-init")
// init Cluster components
if err := clusterComponents.Init(ctx); err != nil {
Expand Down
36 changes: 30 additions & 6 deletions docs/autopilot.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,16 @@ metadata:
namespace: default
spec:
channel: edge_release
updateServer: https://docs.k0sproject.io/
updateServer: https://updates.k0sproject.io/
upgradeStrategy:
cron: "0 12 * * TUE,WED" # Check for updates at 12:00 on Tuesday and Wednesday.
type: periodic
periodic:
# The folowing fields configures updates to happen only on Tue or Wed at 13:00-15:00
days: [Tuesdsay,Wednesday]
startTime: "13:00"
length: 2h
planSpec: # This defines the plan to be created IF there are updates available
...
```
## Safeguards
Expand Down Expand Up @@ -354,12 +361,24 @@ Similar to the **Plan Status**, the individual nodes can have their own statuses

#### `spec.updateServer <string> (optional)`

* Update server url.
* Update server url. Defaults to `https://updates.k0sproject.io`

#### `spec.upgradeStrategy.type <enum:cron|periodic>`

* Select which update strategy to use.

#### `spec.upgradeStrategy.cron <string> (optional)`
#### `spec.upgradeStrategy.cron <string> (optional)` **DEPRECATED**

* Schedule to check for updates in crontab format.

#### `spec.upgradeStrategy.cron <object>`

Fields:

* `days`: On which weekdays to check for updates
* `startTime`: At which time of day to check updates
* `length`: The length of the update window

#### `spec.planSpec <string> (optional)`

* Describes the behavior of the autopilot generated `Plan`
Expand All @@ -375,7 +394,12 @@ spec:
channel: stable
updateServer: https://updates.k0sproject.io/
upgradeStrategy:
cron: "0 12 * * TUE,WED" # Check for updates at 12:00 on Tuesday and Wednesday.
type: periodic
periodic:
# The folowing fields configures updates to happen only on Tue or Wed at 13:00-15:00
days: [Tuesdsay,Wednesday]
startTime: "13:00"
length: 2h
# Optional. Specifies a created Plan object
planSpec:
commands:
Expand Down Expand Up @@ -410,7 +434,7 @@ spec:

### Q: How do I apply the `Plan` and `ControlNode` CRDs?

A: These CRD definitions are embedded in the **autopilot** binary and applied on startup.
A: These CRD definitions are embedded in the **k0s** binary and applied on startup.
No additional action is needed.

### Q: How will `ControlNode` instances get removed?
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ require (
github.com/hashicorp/terraform-exec v0.19.0
github.com/imdario/mergo v0.3.16
github.com/k0sproject/dig v0.2.0
github.com/k0sproject/version v0.3.1-0.20220411075111-0270bb85e7f8
github.com/kardianos/service v1.2.2
github.com/logrusorgru/aurora/v3 v3.0.0
github.com/mesosphere/toml-merge v0.2.0
Expand Down Expand Up @@ -83,6 +84,8 @@ require (
sigs.k8s.io/yaml v1.3.0
)

require gopkg.in/yaml.v2 v2.4.0 // indirect

require (
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect
Expand Down Expand Up @@ -268,7 +271,6 @@ require (
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiserver v0.28.1 // indirect
k8s.io/controller-manager v0.28.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,8 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/k0sproject/dig v0.2.0 h1:cNxEIl96g9kqSMfPSZLhpnZ0P8bWXKv08nxvsMHop5w=
github.com/k0sproject/dig v0.2.0/go.mod h1:rBcqaQlJpcKdt2x/OE/lPvhGU50u/e95CSm5g/r4s78=
github.com/k0sproject/version v0.3.1-0.20220411075111-0270bb85e7f8 h1:jxqyyKDoio9LKTynl17J52QKvFj7UWfhQ/tSaobgcLs=
github.com/k0sproject/version v0.3.1-0.20220411075111-0270bb85e7f8/go.mod h1:oEjuz2ItQQtAnGyRgwEV9m5R6/9rjoFC6EiEEzbkFdI=
github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60=
github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk=
Expand Down
4 changes: 3 additions & 1 deletion inttest/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ check-footloose-alpine-buildx:
$(dir $(K0S_PATH))
touch $@

.update-server.stamp: .footloose-alpine.stamp update-server/Dockerfile update-server/html/stable/index.yaml
.update-server.stamp: .footloose-alpine.stamp update-server/Dockerfile $(wildcard update-server/html/**/*.html)
docker build -t update-server --build-arg BASE=footloose-alpine -f update-server/Dockerfile update-server
touch $@

Expand Down Expand Up @@ -101,6 +101,8 @@ check-dualstack-dynamicconfig: export K0S_ENABLE_DYNAMIC_CONFIG=true
check-dualstack-dynamicconfig: TEST_PACKAGE=dualstack

check-ap-updater: .update-server.stamp
check-ap-updater-periodic: .update-server.stamp
check-ap-updater-periodic: TIMEOUT=10m

check-network-conformance-kuberouter: TIMEOUT=15m
check-network-conformance-kuberouter: export K0S_NETWORK_CONFORMANCE_CNI=kuberouter
Expand Down
1 change: 1 addition & 0 deletions inttest/Makefile.variables
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ smoketests := \
check-ap-selector \
check-ap-single \
check-ap-updater \
check-ap-updater-periodic \
check-backup \
check-basic \
check-byocri \
Expand Down
182 changes: 182 additions & 0 deletions inttest/ap-updater-periodic/updater_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
// Copyright 2023 k0s authors
//
// 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 updater

import (
"fmt"
"testing"
"time"

"github.com/k0sproject/k0s/inttest/common"
aptest "github.com/k0sproject/k0s/inttest/common/autopilot"

apconst "github.com/k0sproject/k0s/pkg/autopilot/constant"
appc "github.com/k0sproject/k0s/pkg/autopilot/controller/plans/core"
"github.com/k0sproject/k0s/pkg/kubernetes/watch"
"github.com/stretchr/testify/suite"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)

const (
ManifestTestDirPerms = "775"
)

type plansSingleControllerSuite struct {
common.FootlooseSuite
}

var envTemplate = `
export K0S_UPDATE_SERVER={{.Address}}
export K0S_UPDATE_PERIOD=1m
export K0S_UPDATE_CHECK_INTERVAL=1m
`

// SetupTest prepares the controller and filesystem, getting it into a consistent
// state which we can run tests against.
func (s *plansSingleControllerSuite) SetupTest() {
ctx := s.Context()
s.Require().NoError(s.WaitForSSH(s.ControllerNode(0), 2*time.Minute, 1*time.Second))

// Dump some env vars for testing in /etc/conf.d/k0scontroller
vars := struct {
Address string
}{
Address: fmt.Sprintf("http://%s", s.GetUpdateServerIPAddress()),
}
s.PutFileTemplate(s.ControllerNode(0), "/etc/conf.d/k0scontroller", envTemplate, vars)

s.Require().NoError(s.InitController(0), "--disable-components=metrics-server")
s.Require().NoError(s.WaitJoinAPI(s.ControllerNode(0)))

kc, err := s.KubeClient(s.ControllerNode(0))
s.Require().NoError(err)

client, err := s.ExtensionsClient(s.ControllerNode(0))
s.Require().NoError(err)

s.Require().NoError(aptest.WaitForCRDByName(ctx, client, "plans"))
s.Require().NoError(aptest.WaitForCRDByName(ctx, client, "controlnodes"))
s.Require().NoError(aptest.WaitForCRDByName(ctx, client, "updateconfigs"))
// Wait that we see an event for update before proceeding to actual update testing
err = watch.Events(kc.CoreV1().Events("")).
Until(s.Context(), func(e *corev1.Event) (done bool, err error) {
return e.Type == "Normal" &&
e.Source.Component == "k0s" &&
e.Reason == "NewVersionAvailable", nil
})
s.Require().NoError(err)
// Get the first line of access logs to verify that the update headers are present
ssh, err := s.SSH(s.Context(), "updateserver0")
s.Require().NoError(err)
defer ssh.Disconnect()
logs, err := ssh.ExecWithOutput(s.Context(), "head -1 /var/log/nginx/access.log")
s.Require().NoError(err)
s.verifyUpdateHeaders(kc, logs)
}

func (s *plansSingleControllerSuite) verifyUpdateHeaders(kc kubernetes.Interface, logLine string) {
// Verify that the update headers are present in the update server logs
s.Require().Contains(logLine, `K0S_StorageType="etcd"`)
s.Require().Contains(logLine, "K0S_ControlPlaneNodesCount=1")
s.Require().Contains(logLine, fmt.Sprintf(`K0S_ClusterID="%s"`, s.getClusterID(kc)))
s.Require().Contains(logLine, `K0S_CNIProvider="kuberouter"`)
}

func (s *plansSingleControllerSuite) getClusterID(kc kubernetes.Interface) string {
ns, err := kc.CoreV1().Namespaces().Get(s.Context(), "kube-system", metav1.GetOptions{})
s.Require().NoError(err)
return fmt.Sprintf("%s:%s", ns.Name, ns.UID)
}

// TestApply applies a well-formed `plan` yaml, and asserts that all of the correct values
// across different objects are correct.
func (s *plansSingleControllerSuite) TestApply() {
updaterConfig := `
apiVersion: autopilot.k0sproject.io/v1beta2
kind: UpdateConfig
metadata:
name: autopilot
spec:
channel: latest
updateServer: {{.Address}}
upgradeStrategy:
type: periodic
periodic:
days: [Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday]
startTime: 00:00
length: 24h
planSpec:
commands:
- k0supdate:
forceupdate: true
targets:
controllers:
discovery:
selector: {}
workers:
discovery:
selector: {}
`

vars := struct {
Address string
}{
Address: fmt.Sprintf("http://%s", s.GetUpdateServerIPAddress()),
}

manifestFile := "/tmp/updateconfig.yaml"
s.PutFileTemplate(s.ControllerNode(0), manifestFile, updaterConfig, vars)

out, err := s.RunCommandController(0, fmt.Sprintf("/usr/local/bin/k0s kubectl apply -f %s", manifestFile))
s.T().Logf("kubectl apply output: '%s'", out)
s.Require().NoError(err)

client, err := s.AutopilotClient(s.ControllerNode(0))
s.Require().NoError(err)
s.NotEmpty(client)

// The plan has enough information to perform a successful update of k0s, so wait for it.
_, err = aptest.WaitForPlanState(s.Context(), client, apconst.AutopilotName, appc.PlanCompleted)
s.Require().NoError(err)

kc, err := s.KubeClient(s.ControllerNode(0))
s.Require().NoError(err)

// Verify that the update headers are present in the update server logs,
// ignoring the "grab" user-agent as that's the actual k0s bin download which we don't care
ssh, err := s.SSH(s.Context(), "updateserver0")
s.Require().NoError(err)
defer ssh.Disconnect()
logs, err := ssh.ExecWithOutput(s.Context(), "grep -v grab /var/log/nginx/access.log | tail -1")
s.Require().NoError(err)

s.verifyUpdateHeaders(kc, logs)

}

// TestPlansSingleControllerSuite sets up a suite using a single controller, running various
// autopilot upgrade scenarios against it.
func TestPlansSingleControllerSuite(t *testing.T) {
suite.Run(t, &plansSingleControllerSuite{
common.FootlooseSuite{
ControllerCount: 1,
WorkerCount: 0,
WithUpdateServer: true,
LaunchMode: common.LaunchModeOpenRC,
},
})
}
1 change: 1 addition & 0 deletions inttest/ap-updater/updater_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ spec:
channel: stable
updateServer: {{.Address}}
upgradeStrategy:
type: cron
cron: "* * * * * *"
planSpec:
commands:
Expand Down
1 change: 1 addition & 0 deletions inttest/update-server/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ RUN apk add nginx
RUN rc-update add nginx boot && mkdir -p /run/nginx/

ADD html /var/lib/nginx/html
ADD nginx.conf /etc/nginx/nginx.conf
ADD default.conf /etc/nginx/http.d/default.conf
12 changes: 12 additions & 0 deletions inttest/update-server/html/latest/index.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
channel: latest
version: v5.6.7
downloadURLs:
- arch: amd64
os: linux
k0s: http://localhost/dist/k0s
- arch: arm64
os: linux
k0s: http://localhost/dist/k0s
- arch: arm
os: linux
k0s: http://localhost/dist/k0s
Loading

0 comments on commit c2495dd

Please sign in to comment.