Useful resources:
If you know the exact command you want to execute in the remote shell then you can craft a command for the local shell. I think the command for the remote shell should not be:
# wrong
kubectl exec -it mypod -n mynamespace -- sh -c ls -l
because it would run kubectl that would run sh -c ls -l. Try the latter code locally and you will see it doesn't run ls -l. This is because -l does not belong to the code sh is told to run. The code for sh should be a single option-argument after -c; further arguments (if any) are different (compare this question).
This means ultimately you need a command that in a shell would look like this: sh -c 'ls -l'. Your kubectl exec will run this if ls -l is a
single argument to it. So you need this in the remote shell:
kubectl exec -it mypod -n mynamespace -- sh -c 'ls -l'
# or
kubectl exec -it mypod -n mynamespace -- sh -c "ls -l"
(Let's pick the latter one.) Now you know the exact command you want to execute in the remote shell. Pass it to your local ssh as a single argument:
ssh xxx-a@machine.com 'kubectl exec -it mypod -n mynamespace -- sh -c "ls -l"'
The outer (single) quotes will be removed by the local shell, but thanks to them ssh will get the large code string (kubectl … "ls -l") as a single argument. The code will contain the inner (double) quotes. These will be removed by the remote shell (spawned by the SSH server), but thanks to them kubectl will get the small code string (ls -l) as a single argument; therefore it will spawn sh that will get the code as a single argument.
In this case it doesn't matter which quoting uses single-, which uses double-quotes. What matters is the quote pairs are different (as opposed to e.g. a "b "c"" where the quotes are not inner and outer, they are two separate pairs with c being unquoted).
Note different tools digest their arguments and build "code" for the next step differently:
ssh can concatenate multiple arguments to build code for the remote shell (that's why your attempt kinda worked); then the remote shell interprets a single string. When I need to protect things (e.g. quotes) from the local shell (and this is the case here) I prefer to quote the whole code locally and pass it to ssh as a single argument; but this is my choice.
So ssh builds one string from one or more arguments.
kubectl exec treats arguments after -- as an array. In our case it runs sh with arguments -c and ls -l (in your attempt the arguments were -c, ls and -l).
So kubectl exec does not build any string. From the array of arguments it builds an array to execute.
sh -c interprets only one argument after -c as shell code. It does not concatenate multiple arguments like ssh can.
Imagine sh -c as an interface to run code in a local shell; and ssh …@… as an interface to run code in a remote shell. They are similar, each spawns a shell that will interpret code written as a single string. Yet they are different, the difference is in how the string is created: sh -c takes code from exactly one argument, ssh builds code from possibly many arguments. kubectl exec is an interface to run an executable in a container. It differs the most from the other two interfaces because it uses its arguments directly as an array.
Do you really need sh -c? I think in this case
ssh xxx-a@machine.com 'kubectl exec -it mypod -n mynamespace -- ls -l'
is enough. Note if you used "ls -l" (still inside the single-quoted code) here then kubectl exec would try to run an executable named ls -l (instead of ls with -l as an argument). Here you don't want to apply inner quotes to ls -l.
OTOH any of these:
ssh xxx-a@machine.com kubectl exec -it mypod -n mynamespace -- ls -l
ssh xxx-a@machine.com kubectl exec -it mypod -n mynamespace -- "ls -l"
ssh xxx-a@machine.com kubectl exec -it "mypod -n mynamespace -- ls" -l
will work because ssh will build the same string for the remote shell (from different number of different arguments though). The last example is especially bizarre; it locally gathers multiple "final" arguments into one, seemingly braking the logic (I mean ls has more to do with -l than with -n, right?). Nevertheless it works and the reason is the way ssh builds one string from many.
sh -c is a must in cases where instead of ls -l you have anything that truly needs a shell (e.g. a pipeline with |, a command with redirections like >file, shell syntax like if).
Your command (or my fixed command) consists of few nested tools. In each step one (the outer) tool digests its arguments somehow and spawns another (the inner) tool on the same or another host somehow. The final conclusion is you need to know all the tools you're using, the ways they parse their arguments and invoke the next tool. Note in our example there is a local shell before ssh and there is a remote shell between the SSH daemon on the server and kubectl; you need to take each implicit shell into account as well.