clh: Improve hypervisor logging

[ port from runtime commit e1ee00d16ed621594a92ce0456eb048362962ff0 ]

Use systemd-cat to collect hypervisor output. The `systemd-cat` program
will open a journal fd and call `cat(1)` to redirect all the output to
the fd. This requires an extra binary to read from hypervisor stdout
(that has combined stdin, stderr and serial terminal). But because it is
cat the overhead is minimal and only is started on Kata debug mode.

Signed-off-by: Jose Carlos Venegas Munoz <jose.carlos.venegas.munoz@intel.com>
Signed-off-by: Peng Tao <bergwolf@hyper.sh>
This commit is contained in:
Jose Carlos Venegas Munoz 2020-06-29 20:00:34 -07:00 committed by Peng Tao
parent 21f83348e9
commit fd5d139470
2 changed files with 82 additions and 49 deletions

View File

@ -6,10 +6,11 @@
package virtcontainers package virtcontainers
import ( import (
"bytes"
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"io/ioutil"
"net" "net"
"net/http" "net/http"
"os" "os"
@ -59,9 +60,7 @@ const (
clhStopSandboxTimeout = 3 clhStopSandboxTimeout = 3
clhSocket = "clh.sock" clhSocket = "clh.sock"
clhAPISocket = "clh-api.sock" clhAPISocket = "clh-api.sock"
clhLogFile = "clh.log"
virtioFsSocket = "virtiofsd.sock" virtioFsSocket = "virtiofsd.sock"
clhSerial = "serial-tty.log"
supportedMajorVersion = 0 supportedMajorVersion = 0
supportedMinorVersion = 5 supportedMinorVersion = 5
defaultClhPath = "/usr/local/bin/cloud-hypervisor" defaultClhPath = "/usr/local/bin/cloud-hypervisor"
@ -122,7 +121,6 @@ type cloudHypervisor struct {
APIClient clhClient APIClient clhClient
version CloudHypervisorVersion version CloudHypervisorVersion
vmconfig chclient.VmConfig vmconfig chclient.VmConfig
cmdOutput bytes.Buffer
virtiofsd Virtiofsd virtiofsd Virtiofsd
store persistapi.PersistDriver store persistapi.PersistDriver
} }
@ -288,13 +286,8 @@ func (clh *cloudHypervisor) createSandbox(ctx context.Context, id string, networ
// set the serial console to the cloud hypervisor // set the serial console to the cloud hypervisor
if clh.config.Debug { if clh.config.Debug {
serialPath, err := clh.serialPath(clh.id)
if err != nil {
return err
}
clh.vmconfig.Serial = chclient.ConsoleConfig{ clh.vmconfig.Serial = chclient.ConsoleConfig{
Mode: cctFILE, Mode: cctTTY,
File: serialPath,
} }
} else { } else {
@ -370,17 +363,12 @@ func (clh *cloudHypervisor) startSandbox(timeout int) error {
var strErr string var strErr string
strErr, pid, err := clh.LaunchClh() strErr, pid, err := clh.LaunchClh()
if err != nil { if err != nil {
return fmt.Errorf("failed to launch cloud-hypervisor: %s, error messages from log: %s", err, strErr)
}
clh.state.PID = pid
if err := clh.waitVMM(clhTimeout); err != nil {
clh.Logger().WithField("error", err).WithField("output", clh.cmdOutput.String()).Warn("cloud-hypervisor init failed")
if shutdownErr := clh.virtiofsd.Stop(); shutdownErr != nil { if shutdownErr := clh.virtiofsd.Stop(); shutdownErr != nil {
clh.Logger().WithField("error", shutdownErr).Warn("error shutting down Virtiofsd") clh.Logger().WithField("error", shutdownErr).Warn("error shutting down Virtiofsd")
} }
return err return fmt.Errorf("failed to launch cloud-hypervisor: %q, hypervisor output:\n%s", err, strErr)
} }
clh.state.PID = pid
if err := clh.bootVM(ctx); err != nil { if err := clh.bootVM(ctx); err != nil {
return err return err
@ -809,18 +797,10 @@ func (clh *cloudHypervisor) vsockSocketPath(id string) (string, error) {
return utils.BuildSocketPath(clh.store.RunVMStoragePath(), id, clhSocket) return utils.BuildSocketPath(clh.store.RunVMStoragePath(), id, clhSocket)
} }
func (clh *cloudHypervisor) serialPath(id string) (string, error) {
return utils.BuildSocketPath(clh.store.RunVMStoragePath(), id, clhSerial)
}
func (clh *cloudHypervisor) apiSocketPath(id string) (string, error) { func (clh *cloudHypervisor) apiSocketPath(id string) (string, error) {
return utils.BuildSocketPath(clh.store.RunVMStoragePath(), id, clhAPISocket) return utils.BuildSocketPath(clh.store.RunVMStoragePath(), id, clhAPISocket)
} }
func (clh *cloudHypervisor) logFilePath(id string) (string, error) {
return utils.BuildSocketPath(clh.store.RunVMStoragePath(), id, clhLogFile)
}
func (clh *cloudHypervisor) waitVMM(timeout uint) error { func (clh *cloudHypervisor) waitVMM(timeout uint) error {
clhRunning, err := clh.isClhRunning(timeout) clhRunning, err := clh.isClhRunning(timeout)
@ -911,8 +891,6 @@ func (clh *cloudHypervisor) getAvailableVersion() error {
func (clh *cloudHypervisor) LaunchClh() (string, int, error) { func (clh *cloudHypervisor) LaunchClh() (string, int, error) {
errStr := ""
clhPath, err := clh.clhPath() clhPath, err := clh.clhPath()
if err != nil { if err != nil {
return "", -1, err return "", -1, err
@ -920,36 +898,71 @@ func (clh *cloudHypervisor) LaunchClh() (string, int, error) {
args := []string{cscAPIsocket, clh.state.apiSocket} args := []string{cscAPIsocket, clh.state.apiSocket}
if clh.config.Debug { if clh.config.Debug {
// Cloud hypervisor log levels
logfile, err := clh.logFilePath(clh.id) // 'v' occurrences increase the level
if err != nil { //0 => Error
return "", -1, err //1 => Warn
} //2 => Info
args = append(args, cscLogFile) //3 => Debug
args = append(args, logfile) //4+ => Trace
// Use Info, the CI runs with debug enabled
// a high level of logging increases the boot time
// and in a nested environment this could increase
// the chances to fail because agent is not
// ready on time.
args = append(args, "-vv")
} }
clh.Logger().WithField("path", clhPath).Info() clh.Logger().WithField("path", clhPath).Info()
clh.Logger().WithField("args", strings.Join(args, " ")).Info() clh.Logger().WithField("args", strings.Join(args, " ")).Info()
cmd := exec.Command(clhPath, args...) cmdHypervisor := exec.Command(clhPath, args...)
cmd.Stdout = &clh.cmdOutput var hypervisorOutput io.ReadCloser
cmd.Stderr = &clh.cmdOutput if clh.config.Debug {
cmdHypervisor.Env = os.Environ()
cmdHypervisor.Env = append(cmdHypervisor.Env, "RUST_BACKTRACE=full")
// Get StdoutPipe only for debug, without debug golang will redirect to /dev/null
hypervisorOutput, err = cmdHypervisor.StdoutPipe()
if err != nil {
return "", -1, err
}
}
cmdHypervisor.Stderr = cmdHypervisor.Stdout
err = utils.StartCmd(cmdHypervisor)
if err != nil {
return "", -1, err
}
if err := clh.waitVMM(clhTimeout); err != nil {
clh.Logger().WithField("error", err).Warn("cloud-hypervisor init failed")
var output string
if hypervisorOutput != nil {
b, errRead := ioutil.ReadAll(hypervisorOutput)
if errRead != nil {
output = "failed to read hypervisor output to get error information"
} else {
output = string(b)
}
} else {
output = "Please enable hypervisor logging to get stdout information"
}
return output, -1, err
}
if clh.config.Debug { if clh.config.Debug {
cmd.Env = os.Environ() cmdLogger := utils.NewProgramLogger("kata-hypervisor")
cmd.Env = append(cmd.Env, "RUST_BACKTRACE=full") clh.Logger().Debugf("Starting process logger(%s) for hypervisor", cmdLogger)
} if err := cmdLogger.StartLogger(hypervisorOutput); err != nil {
// Not critical to run a container, but output wont be logged
if err := utils.StartCmd(cmd); err != nil { clh.Logger().Warnf("Failed start process logger(%s) %s", cmdLogger, err)
fmt.Println("Error starting cloudHypervisor", err)
if cmd.Process != nil {
cmd.Process.Kill()
} }
return errStr, 0, err
} }
return errStr, cmd.Process.Pid, nil return "", cmdHypervisor.Process.Pid, nil
} }
//########################################################################### //###########################################################################
@ -960,13 +973,12 @@ func (clh *cloudHypervisor) LaunchClh() (string, int, error) {
const ( const (
cctOFF string = "Off" cctOFF string = "Off"
cctFILE string = "File"
cctNULL string = "Null" cctNULL string = "Null"
cctTTY string = "Tty"
) )
const ( const (
cscAPIsocket string = "--api-socket" cscAPIsocket string = "--api-socket"
cscLogFile string = "--log-file"
) )
//**************************************** //****************************************

View File

@ -9,6 +9,7 @@ import (
"crypto/rand" "crypto/rand"
"errors" "errors"
"fmt" "fmt"
"io"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@ -297,3 +298,23 @@ const (
MiB = KiB << 10 MiB = KiB << 10
GiB = MiB << 10 GiB = MiB << 10
) )
// Binary to use to log program output
const LoggerBinaryName = "systemd-cat"
type ProgramLogger struct {
cmd *exec.Cmd
}
func NewProgramLogger(loggerLabel string) ProgramLogger {
return ProgramLogger{cmd: exec.Command(LoggerBinaryName, "-t", loggerLabel)}
}
func (p *ProgramLogger) StartLogger(output io.ReadCloser) error {
p.cmd.Stdin = output
return StartCmd(p.cmd)
}
func (p ProgramLogger) String() string {
return p.cmd.Path
}