Skip to content

Commit

Permalink
node-servant: add preflight-convert command
Browse files Browse the repository at this point in the history
  • Loading branch information
Peeknut committed Dec 20, 2021
1 parent 21d4e86 commit 40fc6e4
Show file tree
Hide file tree
Showing 17 changed files with 922 additions and 49 deletions.
6 changes: 4 additions & 2 deletions cmd/yurt-node-servant/convert/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,10 @@ func setFlags(cmd *cobra.Command) {
"The yurthub image.")
cmd.Flags().Duration("yurthub-healthcheck-timeout", defaultYurthubHealthCheckTimeout,
"The timeout for yurthub health check.")
cmd.Flags().String("kubeadm-conf-path", "",
"The path to kubelet service conf that is used by kubelet component to join the cluster on the work node.")
cmd.Flags().StringP("kubeadm-conf-path", "k", "",
"The path to kubelet service conf that is used by kubelet component to join the cluster on the work node."+
"Support multiple values, will search in order until get the file.(e.g -k kbcfg1,kbcfg2)",
)
cmd.Flags().String("join-token", "", "The token used by yurthub for joining the cluster.")
cmd.Flags().String("working-mode", "edge", "The node type cloud/edge, effect yurthub workingMode.")
}
4 changes: 3 additions & 1 deletion cmd/yurt-node-servant/node-servant.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/spf13/cobra"

"github.com/openyurtio/openyurt/cmd/yurt-node-servant/convert"
preflightconvert "github.com/openyurtio/openyurt/cmd/yurt-node-servant/preflight-convert"
"github.com/openyurtio/openyurt/cmd/yurt-node-servant/revert"
"github.com/openyurtio/openyurt/pkg/projectinfo"
)
Expand All @@ -38,12 +39,13 @@ func main() {
version := fmt.Sprintf("%#v", projectinfo.Get())
rootCmd := &cobra.Command{
Use: "node-servant",
Short: "node-servant do convert/revert specific node",
Short: "node-servant do preflight-convert/convert/revert specific node",
Version: version,
}
rootCmd.PersistentFlags().String("kubeconfig", "", "The path to the kubeconfig file")
rootCmd.AddCommand(convert.NewConvertCmd())
rootCmd.AddCommand(revert.NewRevertCmd())
rootCmd.AddCommand(preflightconvert.NewxPreflightConvertCmd())

if err := rootCmd.Execute(); err != nil { // run command
os.Exit(1)
Expand Down
68 changes: 68 additions & 0 deletions cmd/yurt-node-servant/preflight-convert/preflight.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
Copyright 2021 The OpenYurt 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 preflight_convert

import (
"os"

"github.com/spf13/cobra"
"k8s.io/klog/v2"

preflightconvert "github.com/openyurtio/openyurt/pkg/node-servant/preflight-convert"
)

const (
latestYurtHubImage = "openyurt/yurthub:latest"
latestYurtTunnelAgentImage = "openyurt/yurt-tunnel-agent:latest"
)

// NewxPreflightConvertCmd generates a new preflight-convert check command
func NewxPreflightConvertCmd() *cobra.Command {
o := preflightconvert.NewPreflightConvertOptions()
cmd := &cobra.Command{
Use: "preflight-convert",
Short: "",
Run: func(cmd *cobra.Command, args []string) {
if err := o.Complete(cmd.Flags()); err != nil {
klog.Errorf("Fail to complete the preflight-convert option: %s", err)
os.Exit(1)
}
preflighter := preflightconvert.NewPreflighterWithOptions(o)
if err := preflighter.Do(); err != nil {
klog.Errorf("Fail to run pre-flight checks: %s", err)
os.Exit(1)
}
klog.Info("convert pre-flight checks success")
},
}
setFlags(cmd)

return cmd
}

func setFlags(cmd *cobra.Command) {
cmd.Flags().StringP("kubeadm-conf-path", "k", "",
"The path to kubelet service conf that is used by kubelet component to join the cluster on the work node."+
"Support multiple values, will search in order until get the file.(e.g -k kbcfg1,kbcfg2)",
)
cmd.Flags().String("yurthub-image", latestYurtHubImage, "The yurthub image.")
cmd.Flags().String("yurt-tunnel-agent-image", latestYurtTunnelAgentImage, "The yurt-tunnel-agent image.")
cmd.Flags().BoolP("deploy-yurttunnel", "t", false, "If set, yurt-tunnel-agent will be deployed.")
cmd.Flags().String("ignore-preflight-errors", "", "A list of checks whose errors will be shown as warnings. "+
"And value needs to be lowercase. Example: 'isprivilegeduser,imagepull'.Value 'all' ignores errors from all checks.",
)
}
23 changes: 16 additions & 7 deletions pkg/node-servant/components/kubelet.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,13 @@ const (
)

type kubeletOperator struct {
openyurtDir string
kubeadmConfPath string
openyurtDir string
}

// NewKubeletOperator create kubeletOperator
func NewKubeletOperator(openyurtDir, kubeadmConfPath string) *kubeletOperator {
func NewKubeletOperator(openyurtDir string) *kubeletOperator {
return &kubeletOperator{
openyurtDir: openyurtDir,
kubeadmConfPath: kubeadmConfPath,
openyurtDir: openyurtDir,
}
}

Expand Down Expand Up @@ -187,8 +185,19 @@ func restartKubeletService() error {
}

// GetApiServerAddress parse apiServer address from conf file
func GetApiServerAddress(kubeadmConfPath string) (string, error) {
kubeletConfPath, err := enutil.GetSingleContentFromFile(kubeadmConfPath, kubeletConfigRegularExpression)
func GetApiServerAddress(kubeadmConfPaths []string) (string, error) {
var kbcfg string
for _, path := range kubeadmConfPaths {
if exist, _ := enutil.FileExists(path); exist {
kbcfg = path
break
}
}
if kbcfg == "" {
return "", fmt.Errorf("get apiserverAddr err: no file exists in list %s", kubeadmConfPaths)
}

kubeletConfPath, err := enutil.GetSingleContentFromFile(kbcfg, kubeletConfigRegularExpression)
if err != nil {
return "", err
}
Expand Down
180 changes: 180 additions & 0 deletions pkg/node-servant/components/runtime.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/*
Copyright 2021 The OpenYurt 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 components

import (
"os"
"path/filepath"
goruntime "runtime"
"strings"

"github.com/pkg/errors"
utilsexec "k8s.io/utils/exec"
)

const (
dockerSocket = "/var/run/docker.sock" // The Docker socket is not CRI compatible
containerdSocket = "/run/containerd/containerd.sock"
// DefaultDockerCRISocket defines the default Docker CRI socket
DefaultDockerCRISocket = "/var/run/dockershim.sock"

// PullImageRetry specifies how many times ContainerRuntime retries when pulling image failed
PullImageRetry = 5
)

// ContainerRuntime is an interface for working with container runtimes
type ContainerRuntimeForImage interface {
IsDocker() bool
PullImage(image string) error
ImageExists(image string) (bool, error)
}

// CRIRuntime is a struct that interfaces with the CRI
type CRIRuntime struct {
exec utilsexec.Interface
criSocket string
}

// DockerRuntime is a struct that interfaces with the Docker daemon
type DockerRuntime struct {
exec utilsexec.Interface
}

// NewContainerRuntime sets up and returns a ContainerRuntime struct
func NewContainerRuntimeForImage(execer utilsexec.Interface, criSocket string) (ContainerRuntimeForImage, error) {
var toolName string
var runtime ContainerRuntimeForImage

if criSocket != DefaultDockerCRISocket {
toolName = "crictl"
// !!! temporary work around crictl warning:
// Using "/var/run/crio/crio.sock" as endpoint is deprecated,
// please consider using full url format "unix:///var/run/crio/crio.sock"
if filepath.IsAbs(criSocket) && goruntime.GOOS != "windows" {
criSocket = "unix://" + criSocket
}
runtime = &CRIRuntime{execer, criSocket}
} else {
toolName = "docker"
runtime = &DockerRuntime{execer}
}

if _, err := execer.LookPath(toolName); err != nil {
return nil, errors.Wrapf(err, "%s is required for container runtime", toolName)
}
return runtime, nil
}

// IsDocker returns true if the runtime is docker
func (runtime *CRIRuntime) IsDocker() bool {
return false
}

// IsDocker returns true if the runtime is docker
func (runtime *DockerRuntime) IsDocker() bool {
return true
}

// PullImage pulls the image
func (runtime *CRIRuntime) PullImage(image string) error {
var err error
var out []byte
for i := 0; i < PullImageRetry; i++ {
out, err = runtime.exec.Command("crictl", "-r", runtime.criSocket, "pull", image).CombinedOutput()
if err == nil {
return nil
}
}
return errors.Wrapf(err, "output: %s, error", out)
}

// PullImage pulls the image
func (runtime *DockerRuntime) PullImage(image string) error {
var err error
var out []byte
for i := 0; i < PullImageRetry; i++ {
out, err = runtime.exec.Command("docker", "pull", image).CombinedOutput()
if err == nil {
return nil
}
}
return errors.Wrapf(err, "output: %s, error", out)
}

// ImageExists checks to see if the image exists on the system
func (runtime *CRIRuntime) ImageExists(image string) (bool, error) {
err := runtime.exec.Command("crictl", "-r", runtime.criSocket, "inspecti", image).Run()
return err == nil, nil
}

// ImageExists checks to see if the image exists on the system
func (runtime *DockerRuntime) ImageExists(image string) (bool, error) {
err := runtime.exec.Command("docker", "inspect", image).Run()
return err == nil, nil
}

// detectCRISocketImpl is separated out only for test purposes, DON'T call it directly, use DetectCRISocket instead
func detectCRISocketImpl(isSocket func(string) bool) (string, error) {
foundCRISockets := []string{}
knownCRISockets := []string{
// Docker and containerd sockets are special cased below, hence not to be included here
"/var/run/crio/crio.sock",
}

if isSocket(dockerSocket) {
// the path in dockerSocket is not CRI compatible, hence we should replace it with a CRI compatible socket
foundCRISockets = append(foundCRISockets, DefaultDockerCRISocket)
} else if isSocket(containerdSocket) {
// Docker 18.09 gets bundled together with containerd, thus having both dockerSocket and containerdSocket present.
// For compatibility reasons, we use the containerd socket only if Docker is not detected.
foundCRISockets = append(foundCRISockets, containerdSocket)
}

for _, socket := range knownCRISockets {
if isSocket(socket) {
foundCRISockets = append(foundCRISockets, socket)
}
}

switch len(foundCRISockets) {
case 0:
// Fall back to Docker if no CRI is detected, we can error out later on if we need it
return DefaultDockerCRISocket, nil
case 1:
// Precisely one CRI found, use that
return foundCRISockets[0], nil
default:
// Multiple CRIs installed?
return "", errors.Errorf("Found multiple CRI sockets, please use --cri-socket to select one: %s", strings.Join(foundCRISockets, ", "))
}

}

// isExistingSocket checks if path exists and is domain socket
func isExistingSocket(path string) bool {
fileInfo, err := os.Stat(path)
if err != nil {
return false
}

return fileInfo.Mode()&os.ModeSocket != 0
}

// DetectCRISocket uses a list of known CRI sockets to detect one. If more than one or none is discovered, an error is returned.
func DetectCRISocket() (string, error) {
return detectCRISocketImpl(isExistingSocket)
}
37 changes: 37 additions & 0 deletions pkg/node-servant/components/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
Copyright 2021 The OpenYurt 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 components

import (
"os"
)

const (
KubeletSvcEnv = "KUBELET_SVC"
KubeletSvcPathSystemUsr = "/usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf"
KubelerSvcPathSystemEtc = "/etc/systemd/system/kubelet.service.d/10-kubeadm.conf"
)

func GetDefaultKubeadmConfPath() []string {
kubeadmConfPath := []string{}
path := os.Getenv(KubeletSvcEnv)
if path != "" && path != KubeletSvcPathSystemUsr && path != KubelerSvcPathSystemEtc {
kubeadmConfPath = append(kubeadmConfPath, path)
}
kubeadmConfPath = append(kubeadmConfPath, KubeletSvcPathSystemUsr, KubelerSvcPathSystemEtc)
return kubeadmConfPath
}
Loading

0 comments on commit 40fc6e4

Please sign in to comment.