In early March we considered adding performance profiling and analysis tools to the Kubernetes cluster (mainly targeting Python, especially uwsgi applications). After researching several profiling schemes, we chose py-spy for uwsgi application profiling. This blog introduces the concrete implementation flow and some debugging strategies; the appendix introduces what I learned.
Profile Tools
Our cluster mainly profiles and analyzes Python applications. Our original non-kubernetes scheme was pyflame, but it’s no longer maintained. Another colleague suggested py-spy — still under fairly active development, with more features than pyflame and simpler installation — so we switched to py-spy.
System Implementation Scheme
Running Directly
I quite like its top feature — you can immediately confirm current stack information.

Its README also introduces how to run py-spy in docker containers: PTRACE permission must be added, and hostPID must also be readable — if you can only see your own pid inside the container, sampling is impossible.

Running in a kubernetes Environment
The Scheme Referenced from github
Below is pyflame’s implementation scheme: https://github.com/monsterxx03/kube-pyflame/blob/master/kubectl-pyflame
1 | nodeName=$(kubectl get pod ${POD} -n ${NAMESPACE} -o jsonpath='{.spec.nodeName}') |
This is the simplest implementation. Two points to note: the hostPID: true and securityContext: privileged: true here — these grant special permissions.
Our Implementation
Based on the pyflame method above, using py-spy is about the same — the configuration is very similar, but some places differ:
1 |
|
The differences are as follows:
- We use a job instead of a pod — mainly to standardize the code; also, jobs have this parameter:
ttlSecondsAfterFinished - I default the namespace to
defaultrather than the application’s namespace. The main reason: in an earlier article I introduced: A Solution for Providing Dashboard Support on a Kubernetes-based PaaS Platform. There we opened operation permissions on users’ own application namespaces to users — i.e. they can exec into containers. That creates a problem: because this sampling container has hostPID permission, if ordinary users could enter this privileged container, they could kill other programs — an operation that cannot be allowed. Therefore such privileged containers must be uniformly placed in namespaces users cannot access. - nodeSelector specifies the machine on which the sampling task must run; this machine is randomly selected by the PaaS — users needn’t care which machine their application is on
- The environment variables store the sampling parameters; after the task starts running, sampling begins automatically, and after sampling ends the result is uploaded — users get the data with a few button clicks
Code Details in the Sampling Container
This program does several jobs:
- Read the configuration from environment variables
- Confirm the process id to sample
- Sample
- Report the flame graph
I’ll paste the main code; error handling is omitted:
1 | def _run_profile_task(): |
Program Security Considerations
The security issue is mainly the namespace restriction strategy — the concrete reasons and scheme were given in the implementation comparison above.
Summary
This article mainly wanted to introduce the profiling tool we added in a kubernetes environment according to our PaaS platform’s situation. Currently it stops at Python applications, but extending to other languages’ programs in future is easy. Another point to note is handling privileged containers: grant as few permissions as possible, and don’t let users see them.
Appendix 1: Job Debugging Strategy
In pyflame’s implementation scheme there’s a line of configuration worth learning — simply a debugging godsend for job tasks and pod containers:
1 | - command: ['sh', '-c', 'while true; do sleep 10; done;'] |
Appendix 2: Learning the kubectl flame Tool
After our feature went live, I also saw this sampling tool — provided via the kubectl plugin development approach.
If you only need a simple sampling tool, there’s no need to develop a whole system; try this kubectl plugin:
Introducing Kubectl Flame: Effortless Profiling on Kubernetes
It contains this sentence: Profiling is a non-trivial task.
1 | kubectl flame mypod -t 1m -f /tmp/flamegraph.svg |
I also read through the code: https://github.com/VerizonMedia/kubectl-flame The codebase isn’t long; let me briefly introduce its implementation.
Code Implementation
cli/cmd/kubernetes/root.go — the entry code is here:
1 | cmd := &cobra.Command{ |
The Flame function is the concrete sampling function; I’ve omitted all error handling:
1 | func Flame(cfg *data.FlameConfig) { |
I won’t paste the later code — it’s just for launching a Job. Fast-forward directly to the concrete profile code:
1 | // agent/profiler/python.go |
How Applications in Different Languages Should Be Profiled
Also learned from reading the codebase…
| Language | Profiling tool |
|---|---|
| Java | async-profiler |
| Python | py-spy |
| Golang | bcc-profiler |
| Ruby | rbspy |