-
Notifications
You must be signed in to change notification settings - Fork 882
iottymux: Store logs for kubelet in the appropriate location #3798
Conversation
cc86f34
to
972fa62
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some comments.
rkt/run.go
Outdated
@@ -155,6 +156,7 @@ func init() { | |||
cmdRun.Flags().StringVar(&flagHostname, "hostname", "", `pod's hostname. If empty, it will be "rkt-$PODUUID"`) | |||
cmdRun.Flags().Var((*appsVolume)(&rktApps), "volume", "volumes to make available in the pod") | |||
cmdRun.Flags().StringVar(&flagIPCMode, "ipc", "", `whether to stay in the host IPC namespace. Syntax: --ipc=[auto|private|parent]`) | |||
cmdRun.Flags().StringVar(&flagKubeletLogDir, "kubelet-log-dir", "", "path to kubelet log dir") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this flag in rkt run
? It should be for rkt app sandbox
, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a function addAppFlags
which is used for almost every app
command, and for sure it's used in rkt app add
:
stage1/iottymux/iottymux.go
Outdated
@@ -411,7 +411,10 @@ func actionIOMux(statusFile string) error { | |||
case "k8s-plain": | |||
var err error | |||
// TODO(lucab): check what should be the target path with Euan | |||
logTarget := filepath.Join(pathPrefix, appName, "logfile") | |||
logDir := os.Getenv("KUBELET_LOG_DIR") | |||
kubernetesIndex := os.Getenv("KUBERNETES_INDEX") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is a per-app identifier, how can it work with only one variable?
972fa62
to
99890d4
Compare
rkt/app_sandbox.go
Outdated
flagAppPorts appPortList | ||
flagAnnotations kvMap | ||
flagLabels kvMap | ||
flagKubeletLogDir string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: here and everywhere else please s/kubelet/kubernetes/
rkt/app_sandbox.go
Outdated
@@ -62,6 +63,8 @@ func init() { | |||
flagAppPorts = appPortList{} | |||
cmdAppSandbox.Flags().Var(&flagAnnotations, "user-annotation", "optional, set the pod's annotations in the form of key=value") | |||
cmdAppSandbox.Flags().Var(&flagLabels, "user-label", "optional, set the pod's label in the form of key=value") | |||
|
|||
cmdAppSandbox.Flags().StringVar(&flagKubeletLogDir, "kubelet-log-dir", "", "Optional, path to kubelet log dir. Should be used only by rktlet.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we have a default matching the kubernetes one here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really, because when this string is non-empty, we bind mount it to the stage1 container. We don't want to do it when we don't use rktlet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack
stage1/init/common/units.go
Outdated
} | ||
file.WriteString(fmt.Sprintf("KUBELET_LOG_DIR=%s\n", p.KubeletLogDir)) | ||
|
||
kubeletLogPath, ok := ra.Annotations.Get("coreos.com/rkt/experiment/kubelet-log-path") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This name is a bit confusing, as it isn't a proper path, just a suffix/name (according to my understanding of your explanation yesterday). What about kubernetes-log-filename
or something similar?
stage1/iottymux/iottymux.go
Outdated
logFile, err = os.OpenFile(logTarget, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm) | ||
logDir := os.Getenv("KUBELET_LOG_DIR") | ||
logPath := os.Getenv("KUBELET_LOG_PATH") | ||
logFullPath := filepath.Join(logDir, logPath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This just works because there is an implicit invariant that "kubelet logpath on host == logpath in stage1". This should be either documented somewhere, or the logDir
part should be dropped from here and scoped instead somewhere under /rkt
or similar (probably better).
/cc @alban
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer the latter, to choose a fixed path inside stage1 so that the kubelet will not be able to overshadow some other file/directory by mistake.
Then, --bind=
in stage1/init/init.go
must be updated accordingly using 2 paths.
99890d4
to
faa2b64
Compare
Way to test that change:
|
1e30ae2
to
08ba149
Compare
This works:
Now if I do:
I get this on the sandbox:
Maybe because the log file has the same name? If I remove the app and add it again with a different log file:
I see this now:
Doing the same but with different log files seems to work 👍
|
stage1/init/common/units.go
Outdated
@@ -320,10 +320,20 @@ func (uw *UnitWriter) SetupAppIO(p *stage1commontypes.Pod, ra *schema.RuntimeApp | |||
// streaming mode brings in a `iomux@.service` before-dependency | |||
opts = append(opts, unit.NewUnitOption("Unit", "Requires", fmt.Sprintf("iomux@%s.service", ra.Name))) | |||
opts = append(opts, unit.NewUnitOption("Unit", "Before", fmt.Sprintf("iomux@%s.service", ra.Name))) | |||
logMode, ok := p.Manifest.Annotations.Get("coreos.com/rkt/experiment/logmode") | |||
logMode, ok := p.Manifest.UserAnnotations["coreos.com/rkt/experiment/logmode"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this was correct before as an annotation, not a user-annotation. Same for the one you added below. See https://github.com/appc/spec/blob/master/spec/pods.md.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the concern with this is how to pass a pod annotation in rktlet if we use the current rkt CLI. Since we're not using a pod manifest, we'd need to add an option to pass annotations to rkt run
and rkt app sandbox
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exaclty, using non-user annotations is just impossible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I'm following, I'm just commenting on stage1 logic here.
It shouldn't matter how you get this information from the user in stage0 (CLI flag, env flag, hardcoded value), you can still pass it as an annotation to stage1.
Unless you are trying to tell me that you want to pass this to stage0 CLI as --user-annotation=
, in which case I'm saying that you shouldn't (because of spec, and because it will appear back in k8s among user-facing annotations).
stage1/init/init.go
Outdated
@@ -135,6 +135,7 @@ func parseFlags() *stage1commontypes.RuntimePod { | |||
}) | |||
flag.Var(dnsConfMode, "dns-conf-mode", "DNS config file modes") | |||
flag.StringVar(&rp.IPCMode, "ipc", "", "IPC mode --ipc=[auto|private|parent]") | |||
flag.StringVar(&rp.KubernetesLogDir, "kuber A373 netes-log-dir", "", "Mount the kubernetes log directory to write CRI logs") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a bit heavy and needs to be put in the stage1 interface protocol. Can't we instead just generate a volume+mount pair in stage0 at app_add
time? If not, then an annotation on the pod is probably fine for the time being.
stage1/iottymux/iottymux.go
Outdated
logTarget := filepath.Join(pathPrefix, appName, "logfile") | ||
logFile, err = os.OpenFile(logTarget, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm) | ||
logPath := os.Getenv("KUBERNETES_LOG_PATH") | ||
logFullPath := filepath.Join("/rkt/kubernetes", logPath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As this is coming from the outside, I suggest to Clean
it before use and make sure the result is still within the prefix dir after.
@iaguis This is expected, you cannot log to the same file from two apps. And kubelet generates the another filename for each container, so that's totally fine. Or you mean that we should check whether the file isn't used already before doing anything? That could be cool in terms of UX. |
If it's not too difficult to implement I rather see errors than panics :) |
Yeah, can do that. I will just check whether the file exists in the dir we mount from host. |
#3814 adds the |
08ba149
to
bb434bc
Compare
d440d6d
to
26a3e25
Compare
26a3e25
to
75531ff
Compare
Since we changed the implementation a little bit and now everything relies on annotations (instead of user annotations or new CLI arguments), the current way of testing that branch is:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some small changes. Otherwise looks good.
stage1/iottymux/iottymux.go
Outdated
|
||
match, err := filepath.Match("/rkt/kubernetes/*", logFullPath) | ||
if err != nil { | ||
return fmt.Errorf("Couldn't analyze the full log path %s: %s", logFullPath, err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Errors should be lower case
stage1/iottymux/iottymux.go
Outdated
if err != nil { | ||
return fmt.Errorf("Couldn't analyze the full log path %s: %s", logFullPath, err) | ||
} else if !match { | ||
return fmt.Errorf("The log path isn't inside /rkt/kubernetes, which most probably means that user tried to escape that directory by using '..'") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Errors should be lower case
stage1/iottymux/iottymux.go
Outdated
if err != nil { | ||
return fmt.Errorf("Couldn't analyze the full log path %s: %s", logFullPath, err) | ||
} else if !match { | ||
return fmt.Errorf("The log path isn't inside /rkt/kubernetes, which most probably means that user tried to escape that directory by using '..'") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd just say:
log path is not inside /rkt/kubernetes, refusing path traversal
tests/rkt_app_sandbox_test.go
Outdated
const ( | ||
// Number of retries to read CRI log file. | ||
criLogsReadRetries = 5 | ||
// Delay between each retry attemtp in reading CRI log file. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
attempt
@@ -328,6 +328,16 @@ func (uw *UnitWriter) SetupAppIO(p *stage1commontypes.Pod, ra *schema.RuntimeApp | |||
if ok { | |||
file.WriteString(fmt.Sprintf("STAGE1_LOGMODE=%s\n", logMode)) | |||
F438 | } | ||
switch logMode { | |||
case "k8s-plain": | |||
kubernetesLogPath, ok := ra.Annotations.Get("coreos.com/rkt/experiment/kubernetes-log-path") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make this kubernetes-log-filename
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
log path
is the term that CRI and kubelet use and I just wanted to be consistent. But I don't care if you seriously care about it. ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. In the CRI they use LogDirectory
and LogPath
, which I find confusing. Since these annotations are k8s-specific I guess staying consistent with their names is ok. Disregard the comment then.
uw.err = fmt.Errorf("kubernetes-log-path annotation needs to be specified when k8s-plain logging mode is used") | ||
return nil | ||
} | ||
file.WriteString(fmt.Sprintf("KUBERNETES_LOG_PATH=%s\n", kubernetesLogPath)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here too, KUBERNETES_LOG_FILENAME
stage1/iottymux/iottymux.go
Outdated
// TODO(lucab): check what should be the target path with Euan | ||
logTarget := filepath.Join(pathPrefix, appName, "logfile") | ||
logFile, err = os.OpenFile(logTarget, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm) | ||
logPath := os.Getenv("KUBERNETES_LOG_PATH") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then this would be logFilename
stage1/iottymux/iottymux.go
Outdated
logTarget := filepath.Join(pathPrefix, appName, "logfile") | ||
logFile, err = os.OpenFile(logTarget, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm) | ||
logPath := os.Getenv("KUBERNETES_LOG_PATH") | ||
logFullPath := filepath.Clean(filepath.Join("/rkt/kubernetes", logPath)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And this logPath
stage1/init/init.go
Outdated
@@ -362,6 +362,11 @@ func getArgsEnv(p *stage1commontypes.Pod, flavor string, canMachinedRegister boo | |||
args = append(args, fmt.Sprintf("--register=false")) | |||
} | |||
|
|||
kubernetesLogDir, ok := p.Manifest.Annotations.Get("coreos.com/rkt/experiment/kubernetes-log-dir") | |||
if ok { | |||
args = append(args, fmt.Sprintf("--bind=%s:/rkt/kubernetes", kubernetesLogDir)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/rkt/kubernetes
seems too broad, maybe we can make it /rkt/kubernetes/log
or such
75531ff
to
690d6f5
Compare
stage1/iottymux/iottymux.go
Outdated
return fmt.Errorf("The log path isn't inside /rkt/kubernetes, which most probably means that user tried to escape that directory by using '..'") | ||
} | ||
|
||
logFile, err = os.OpenFile(logFullPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that according to the CRI KUBERNETES_LOG_PATH
can be a real path and not just a file name so this would fail if it turns out to be more than a file name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can just add a comment mentioning this because I don't think it's an issue for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... I don't think so, logPath
is always a file name. I didn't see any code in kubelet or fragment of spec which proves otherwise. The name is just confusing. But yes, I can add a comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, actually can you re-work the commit message, it doesn't seem to reflect the contents of the commit anymore.
eb4b52f
to
4aaedfe
Compare
@iaguis done |
4aaedfe
to
c302c34
Compare
Thanks! |
Tests are failing:
Probably because in the test |
@iaguis checking whether the log file contains the actual output is important, we cannot just accept empty logs. I assume that rkt is just working slightly slower in semaphoreci and we need to add some recheks and timeouts. |
Yes, I mean doing that in the test so we give some more time for the logs
to appear
…On Oct 6, 2017 15:08, "Michal Rostecki" ***@***.***> wrote:
@iaguis <https://github.com/iaguis> checking whether the log file
contains the actual output is important, we cannot just accept empty logs.
I assume that rkt is just working slightly slower in semaphoreci and we
need to add some recheks and timeouts.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#3798 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAe92gT8Cjpw_sY_xzyxwFOJOAXLIzJBks5spiZWgaJpZM4PbESX>
.
|
37d1b4d
to
64e165e
Compare
This is the change made for rktlet. Kubelet expect the CRI-formatted logs to be in specific directory and it provides that information with CRI. CRI-compatible logs are generated by iottymux. That specific directory which kubelet is looking at, exists in the host filesystem. In order to make it available for iottymux, we need to mount it into the stage1 container. That's why this commit introduces the new annotation called coreos.com/rkt/experiment/kubernetes-log-dir. This annotation is read by stage1 init and the directory by it is bind mounted to the stage1 nspawn container (the mountpoint inside container is /rkt/kubernetes/log). Kubelet provides the name of a logfile of a container via CRI. Since the container concept in Kubernetes refers to an app in rkt, we use the annotation named coreos.com/rkt/experiment/kubernetes-log-path to pass it from rktlet. Then stage1 init redirects that value in KUBERNETES_LOG_PATH environment variable for iottymux. Then finally, iottymux saves the logs to /rkt/kubernetes/log/$KUBERNETES_LOG_PATH file, i.e. /rkt/kubernetes/log/0a8f619b-df34-4c57-86e5-84a551452db1/etcd_0.log. Fixes rkt#3795
64e165e
to
b710725
Compare
rktlet was using journal2cri binary for converting journald logs to CRI format. Container with that binary was injected into each pod. Unfortunately, it couldn't disginguish stdout and stderr output. It also wasn't very stable and reliable. Currently rkt is able to store logs in CRI format in provided directory[1]. Therefore we can remove journal2cri completely and just point rkt to the directory which CRI provides. Fixes kubernetes-retired#107 [1] rkt/rkt#3798
rktlet was using journal2cri binary for converting journald logs to CRI format. Container with that binary was injected into each pod. Unfortunately, it couldn't disginguish stdout and stderr output. It also wasn't very stable and reliable. Currently rkt is able to store logs in CRI format in provided directory[1]. Therefore we can remove journal2cri completely and just point rkt to the directory which CRI provides. Fixes kubernetes-retired#107 [1] rkt/rkt#3798
rktlet was using journal2cri binary for converting journald logs to CRI format. Container with that binary was injected into each pod. Unfortunately, it couldn't disginguish stdout and stderr output. It also wasn't very stable and reliable. Currently rkt is able to store logs in CRI format in provided directory[1]. Therefore we can remove journal2cri completely and just point rkt to the directory which CRI provides. Fixes kubernetes-retired#107 [1] rkt/rkt#3798
rktlet was using journal2cri binary for converting journald logs to CRI format. Container with that binary was injected into each pod. Unfortunately, it couldn't disginguish stdout and stderr output. It also wasn't very stable and reliable. Currently rkt is able to store logs in CRI format in provided directory[1]. Therefore we can remove journal2cri completely and just point rkt to the directory which CRI provides. Fixes kubernetes-retired#107 [1] rkt/rkt#3798
rktlet was using journal2cri binary for converting journald logs to CRI format. Container with that binary was injected into each pod. Unfortunately, it couldn't disginguish stdout and stderr output. It also wasn't very stable and reliable. Currently rkt is able to store logs in CRI format in provided directory[1]. Therefore we can remove journal2cri completely and just point rkt to the directory which CRI provides. Fixes kubernetes-retired#107 [1] rkt/rkt#3798
rktlet was using journal2cri binary for converting journald logs to CRI format. Container with that binary was injected into each pod. Unfortunately, it couldn't disginguish stdout and stderr output. It also wasn't very stable and reliable. Currently rkt is able to store logs in CRI format in provided directory[1]. Therefore we can remove journal2cri completely and just point rkt to the directory which CRI provides. Fixes kubernetes-retired#107 [1] rkt/rkt#3798
rktlet was using journal2cri binary for converting journald logs to CRI form 72A8 at. Container with that binary was injected into each pod. Unfortunately, it couldn't disginguish stdout and stderr output. It also wasn't very stable and reliable. Currently rkt is able to store logs in CRI format in provided directory[1]. Therefore we can remove journal2cri completely and just point rkt to the directory which CRI provides. Fixes kubernetes-retired#107 [1] rkt/rkt#3798
rktlet was using journal2cri binary for converting journald logs to CRI format. Container with that binary was injected into each pod. Unfortunately, it couldn't disginguish stdout and stderr output. It also wasn't very stable and reliable. Currently rkt is able to store logs in CRI format in provided directory[1]. Therefore we can remove journal2cri completely and just point rkt to the directory which CRI provides. Fixes kubernetes-retired#107 [1] rkt/rkt#3798
rktlet was using journal2cri binary for converting journald logs to CRI format. Container with that binary was injected into each pod. Unfortunately, it couldn't disginguish stdout and stderr output. It also wasn't very stable and reliable. Currently rkt is able to store logs in CRI format in provided directory[1]. Therefore we can remove journal2cri completely and just point rkt to the directory which CRI provides. Fixes kubernetes-retired#107 [1] rkt/rkt#3798
rktlet was using journal2cri binary for converting journald logs to CRI format. Container with that binary was injected into each pod. Unfortunately, it couldn't disginguish stdout and stderr output. It also wasn't very stable and reliable. Currently rkt is able to store logs in CRI format in provided directory[1]. Therefore we can remove journal2cri completely and just point rkt to the directory which CRI provides. Fixes kubernetes-retired#107 [1] rkt/rkt#3798
rktlet was using journal2cri binary for converting journald logs to CRI format. Container with that binary was injected into each pod. Unfortunately, it couldn't disginguish stdout and stderr output. It also wasn't very stable and reliable. Currently rkt is able to store logs in CRI format in provided directory[1]. Therefore we can remove journal2cri completely and just point rkt to the directory which CRI provides. Fixes kubernetes-retired#107 [1] rkt/rkt#3798
This is the change made for rktlet. Kubelet expect the CRI-formatted
logs to be in specific directory and it provides that information with
CRI.
CRI-compatible logs are generated by iottymux. That specific directory
which kubelet is looking at, exists in the host filesystem. In order to
make it available for iottymux, we need to mount it into the stage1
container. That's why this commit introduces the new annotation called
coreos.com/rkt/experiment/kubernetes-log-dir. This annotation is read
by stage1 init and the directory by it is bind mounted to the stage1
nspawn container (the mountpoint inside container is /rkt/kubernetes/log).
Kubelet provides the name of a logfile of a container via CRI. Since the
container concept in Kubernetes refers to an app in rkt, we use the
annotation named coreos.com/rkt/experiment/kubernetes-log-path to pass it
from rktlet. Then stage1 init redirects that value in KUBERNETES_LOG_PATH
environment variable for iottymux.
Then finally, iottymux saves the logs to /rkt/kubernetes/log/$KUBERNETES_LOG_PATH
file, i.e. /rkt/kubernetes/log/0a8f619b-df34-4c57-86e5-84a551452db1/etcd_0.log.
Fixes #3795