-
Notifications
You must be signed in to change notification settings - Fork 34
BUG: fix race condition #477
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
bf4f198
to
a72d7b3
Compare
Signed-off-by: Simon Dickhoven <[email protected]>
a72d7b3
to
ea4cf3b
Compare
Signed-off-by: Simon Dickhoven <[email protected]>
4764f46
to
64fef36
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.
Thanks @sdickhoven for this. This certainly makes the logic more robust an less repetitive 👍
Can I ask you to please create a separate PR for the changes unrelated to the fix? We always squash commits when merging, so it'd be nice to have those as a separate commit.
You can disregard for now the CI failure; we're currently addressing that in other PRs.
Signed-off-by: Simon Dickhoven <[email protected]>
9c26082
to
438915e
Compare
hi @alpeb 👋 i have taken out the unrelated changes. ✅ happy to submit another pr for those unrelated changes but they are 100% cosmetic and 0% functional. so there's really no need. |
by the way, i just thought of a way in which specifically, the current picture this scenario: two new cni config files are created at very nearly the same time. let's call them
moving the updated but before we can deal with that inotifywait event, we first have to deal with the one that is already waiting in the queue for
moving the updated but before we can deal with that inotifywait event, we first have to deal with the one that is already waiting in the queue for ...and since so the cycle repeats... forever. should i fix this behavior in this pr or create a new pr? it is technically a different error case (not a race condition but an infinite loop). i would address this by updating the sha sum logic to store all sha sums in a variable so that the sha sum for the correct file can be looked up. if you want me to submit a separate pr, i'm going to wait until this pr is merged... because the required changes would be different for pre-/post-merge |
this is what my fix for the infinite patching loop would look like (using this pr as base): diff --git a/cni-plugin/deployment/scripts/install-cni.sh b/cni-plugin/deployment/scripts/install-cni.sh
index cdf166c..b6156d7 100755
--- a/cni-plugin/deployment/scripts/install-cni.sh
+++ b/cni-plugin/deployment/scripts/install-cni.sh
@@ -264,14 +264,16 @@ sync() {
# monitor_cni_config starts a watch on the host's CNI config directory
monitor_cni_config() {
+ local new_sha
inotifywait -m "${HOST_CNI_NET}" -e create,moved_to,modify |
while read -r directory action filename; do
if [[ "$filename" =~ .*.(conflist|conf)$ ]]; then
log "Detected change in $directory: $action $filename"
- sync "$filename" "$action" "$cni_conf_sha"
+ sync "$filename" "$action" "$(jq -r --arg file "$filename" '.[$file] | select(.)' <<< "$cni_conf_sha")"
# calculate file SHA to use in the next iteration
if [[ -e "$directory/$filename" ]]; then
- cni_conf_sha="$(sha256sum "$directory/$filename" | while read -r s _; do echo "$s"; done)"
+ new_sha="$(sha256sum "$directory/$filename" | while read -r s _; do echo "$s"; done)"
+ cni_conf_sha="$(jq -c --arg file "$filename" --arg sha "$new_sha" '. * {$file: $sha}' <<< "$cni_conf_sha")"
fi
fi
done
@@ -315,7 +317,7 @@ install_cni_bin
# Otherwise, new CNI config files can be created just _after_ the initial round
# of patching and just _before_ we set up the `inotifywait` loop to detect new
# CNI config files.
-cni_conf_sha="__init__"
+cni_conf_sha='{}'
monitor_cni_config &
# Append our config to any existing config file (*.conflist or *.conf) this will maintain cni config sha hashes in a json object like this: {
"05-cilium.conflist" : "0a08ee0b9360e2ee2c3ed1d83263a3168832101346d0528a2474c3f80b7c73d6",
"10-aws.conflist" : "7ed380c9100362003cde9861cc6ef09307245eba3ea963cdba36186a30284acd"
} the since the string
also ... <<< "$cni_conf_sha" to ... < <(echo "$cni_conf_sha") or echo "$cni_conf_sha" | ... |
Sorry for the late reply... As you correctly realized, this script was designed supposing the existence of a single cni config file. I'd like to spend more time investigating how linkerd-cni should behave in the presence of multiple such files before evaluating your solution. So let's for now leave your last diff posted as a comment out of the current PR. I'll give another look and round of tests to the current commits in the following days, so we can move forward. |
Testing this results in the following output:
Those extra events at the bottom are new with this change. Given that we're now calling |
which block are you referring to? this?: # Append our config to any existing config file (*.conflist or *.conf)
config_files=$(find "${HOST_CNI_NET}" -maxdepth 1 -type f \( -iname '*conflist' -o -iname '*conf' \))
if [ -z "$config_files" ]; then
log "No active CNI configuration files found"
else
config_file_count=$(echo "$config_files" | grep -v linkerd | sort | wc -l)
if [ "$config_file_count" -eq 0 ]; then
log "No active CNI configuration files found"
else
find "${HOST_CNI_NET}" -maxdepth 1 -type f \( -iname '*conflist' -o -iname '*conf' \) -print0 |
while read -r -d $'\0' file; do
log "Installing CNI configuration for $file"
... this block is definitely needed! because cni config files can already exist before the logic is:
i guess that the change in the block that follows but i figured that consolidating the patching logic made sense. does that answer your question? apologies if i did not understand your question correctly. the log output looks exactly like what i would expect from my changes. |
oh wait... i think i understand what you're asking...
you are right. i would not expect those last two events. however, i thought that this was maybe your ci check making sure that trying to update the same file twice would not lead to double-patching. 🤷 if that's not the case then you are correct: those last two events shouldn't happen and i'm not sure where they would be coming from. 😕 ...but only the last two. the two events before the last two are expected and a result of this patch. yes. before this change, so there wouldn't have been an now, when so the act of patching will trigger so: 1st log "Trigger CNI config detection for $file"
...
mv -f "$tmp_file" "$file" 2nd both of these events did not show up before because but, as you can see, the second event is ignored (as expected/intended). but then there are two more |
i hope this answers your question. please let me know if i'm not making sense. |
any progress on this? cni chaining is kind of a thing and it would be good for linkerd to work correctly in environments where cni chaining is already in place... i.e. where linkerd isn't the only chained cni plugin. chaining cilium to aws vpc cni is definitely a pretty common setup based on what i see in the cilium slack workspace. let me know if there's anything else i can do to help get this very real race condition fixed. |
Hi, thanks for pinging back; looking again into this... As a side note, I've just realized libcni recently released a way of doing safe subdirectory-based plugin config loading, which would save us from all this config patching nonsense... but that approach will have to wait till it gets picked up by the major cloud providers. Perhaps we could have users opt into that behavior. Not asking you to implement any of that, just wanted to share what I found out 🙂 |
yes. good point.
😍 that's great! i hope it becomes available soon. that would certainly make things easier. do you want me to update my pr with the also, do you want me to open another pr for the other race condition i pointed out about the infinite patching loop due to the flawed shasum logic? if so, then i'd want to wait until this pr is merged before i submit the second pr. |
Yes, please update the PR with the |
i personally think that this is a mistake and here's why: handling the concurrency (whether in bash or in another language) is actually not very complex. the the only thing you have to do is make sure that your logic can deal with filesystem events for multiple files in any order. and i can trivially provoke an infinite patching loop (given the current shasum logic) by running the following two commands in quick succession:
...which i just did on one of my worker nodes and it sent linkerd-cni into said infinite patching loop:
...and it's perfectly plausible that two cni plugin files are created in short succession. the race condition that is fixed by the current version of my pr may be "masking" the infinite patch loop race condition to a large extent. so fixing this race condition will probably make the other race condition more likely to occur. anyway, my patch to address the infinite patch loop is quite trivial imo. and i would personally include it in this pr. but... i don't want to talk you into doing something that you're not comfortable with. so... your call. |
log "Trigger CNI config detection for $file" | ||
# The following will trigger the `sync()` function via `inotifywait` in | ||
# `monitor_cni_config()`. | ||
touch "$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.
fyi: there may be filesystems out there that round the mtime
attribute to seconds.
this may render a touch
ineffective (because the old and new mtime
could end up being the same which may then not trigger an ATTRIB
event) but i have no way of knowing/testing that.
the safest / most universally compatible way to do this is probably what i had before. but this is definitely "cleaner".
up to you if you want to keep the touch
logic or revert back to the mv
logic. happy to revert if you tell me to.
also... no rush. we currently have the workaround outlined in the pr description deployed in all of our clusters. so we are fine. ✅ if you'd rather rewrite the cni patch logic in go or wait for the new subdirectory-based plugin config, that's fine by us. but other linkerd-cni users may run into this race condition and won't be able to effectively troubleshoot / mitigate it. 🤷 |
this pr fixes a race condition that occurs when new cni config files are created while the
install-cni.sh
script is executing anywhere here:https://github.com/linkerd/linkerd2-proxy-init/blob/cni-plugin/v1.6.0/cni-plugin/deployment/scripts/install-cni.sh#L323-L348
we have observed this race condition several times in our eks clusters over the past few days where we are chaining cilium to the aws vpc cni.
the
install-cni.sh
script simply fails to patch the cilium cni config sometimes. i.e. cilium and linkerd-cni must be starting up and manipulating/etc/cni/net.d
at just about the same time.i have a temporary workaround for this race condition in the form of the following kustomize patch: