Skip to content

Commit 8c87528

Browse files
authored
Add prefork support for uwsgi and gunicorn (#288)
* Add prefork support for uwsgi and gunicorn
1 parent d5a9506 commit 8c87528

File tree

79 files changed

+2253
-637
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+2253
-637
lines changed

.github/workflows/CI.yaml

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,12 @@ jobs:
103103
name: Build Plugin and UT Matrix
104104
needs: [ license-and-lint, changes, plugin-doc-check ]
105105
if: |
106-
( always() && ! cancelled() ) &&
106+
( always() && ! cancelled() ) &&
107107
((github.event_name == 'schedule' && github.repository == 'apache/skywalking-python') || needs.changes.outputs.agent == 'true')
108108
runs-on: ubuntu-latest
109109
steps:
110110
- uses: actions/checkout@v3
111111
- id: set-matrix
112-
# TODO change to github output
113112
run: |
114113
sudo apt-get install jq
115114
echo "matrix=$(bash tests/gather_test_paths.sh)" >> $GITHUB_OUTPUT
@@ -158,7 +157,7 @@ jobs:
158157
timeout-minutes: 20
159158
strategy:
160159
matrix:
161-
python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11" ]
160+
python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11" ]
162161
test-path: ${{fromJson(needs.prep-plugin-and-unit-tests.outputs.matrix)}}
163162
fail-fast: false
164163
env:
@@ -229,12 +228,24 @@ jobs:
229228
matrix:
230229
python-image-variant: [ "3.7-slim", "3.8-slim", "3.9-slim", "3.10-slim", "3.11-slim" ]
231230
case:
232-
- name: gRPC
233-
path: tests/e2e/case/grpc/e2e.yaml
234-
- name: HTTP
235-
path: tests/e2e/case/http/e2e.yaml
236-
- name: Kafka
237-
path: tests/e2e/case/kafka/e2e.yaml
231+
- name: gRPC-single-process
232+
path: tests/e2e/case/grpc/single/e2e.yaml
233+
- name: gRPC-gunicorn
234+
path: tests/e2e/case/grpc/gunicorn/e2e.yaml
235+
- name: gRPC-uwsgi
236+
path: tests/e2e/case/grpc/uwsgi/e2e.yaml
237+
- name: HTTP-single-process
238+
path: tests/e2e/case/http/single/e2e.yaml
239+
- name: HTTP-gunicorn
240+
path: tests/e2e/case/http/gunicorn/e2e.yaml
241+
- name: HTTP-uwsgi
242+
path: tests/e2e/case/http/uwsgi/e2e.yaml
243+
- name: Kafka-single-process
244+
path: tests/e2e/case/kafka/single/e2e.yaml
245+
- name: Kafka-gunicorn
246+
path: tests/e2e/case/kafka/gunicorn/e2e.yaml
247+
- name: Kafka-uwsgi
248+
path: tests/e2e/case/kafka/uwsgi/e2e.yaml
238249
- name: profiling_threading
239250
path: tests/e2e/case/profiling/threading/e2e.yaml
240251
- name: profiling_greenlet
@@ -270,7 +281,7 @@ jobs:
270281
timeout-minutes: 60
271282
# wait upon them regardless of success or skipped or failure
272283
if: ${{ always() }}
273-
needs: [ license-and-lint, changes, plugin-and-unit-tests, plugin-doc-check, e2e-tests ]
284+
needs: [ license-and-lint, changes, plugin-and-unit-tests, plugin-doc-check, e2e-tests ]
274285
steps:
275286
- name: Merge Requirement
276287
# check license, lint, plugin and e2e tests, then naturally exits 0

CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
please check with the latest official documentation before upgrading. (#273, #282)
99
https://skywalking.apache.org/docs/skywalking-python/v1.0.0/en/setup/configuration/
1010
- **BREAKING**: All agent core capabilities are now covered by test cases and enabled by default (Trace, Log, PVM runtime metrics, Profiler)
11-
11+
- **BREAKING**: DockerHub Python agent images since v1.0.0 will no longer include the `run` part in `ENTRYPOINT ["sw-python", "run"]`,
12+
user should prefix their command with `[-d/--debug] run [-p/--prefork] <Command>` for extra flexibility.
1213

1314
- Feature:
1415
- Add support for Python 3.11 (#285)
@@ -19,7 +20,8 @@
1920
- Add support for the tags of Virtual Cache for Redis (#263)
2021
- Add a new configuration `kafka_namespace` to prefix the kafka topic names (#277)
2122
- Add log reporter support for loguru (#276)
22-
- Add **experimental** support for explicit os.fork(), restarts agent in new process (#286)
23+
- Add **experimental** support for explicit os.fork(), restarts agent in forked process (#286)
24+
- Add **experimental** sw-python CLI `sw-python run [-p]` flag (-p/--prefork) to enable non-intrusive uWSGI and Gunicorn postfork support (#288)
2325

2426
- Plugins:
2527
- Add aioredis, aiormq, amqp, asyncpg, aio-pika, kombu RMQ plugins (#230 Missing test coverage)

demo/docker-compose.yaml

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ services:
6565
ZOOKEEPER_TICK_TIME: 2000
6666
networks:
6767
- manual
68-
68+
6969
kafka:
7070
container_name: kafka
7171
image: confluentinc/cp-kafka
@@ -86,21 +86,21 @@ services:
8686
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
8787
networks:
8888
- manual
89-
90-
#
91-
# kafka-ui:
92-
# image: provectuslabs/kafka-ui
93-
# container_name: kafka-ui
94-
# ports:
95-
# - "8088:8080"
96-
# restart: always
97-
# environment:
98-
# - KAFKA_CLUSTERS_0_NAME=local
99-
# - KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS=kafka:9092
100-
# depends_on:
101-
# - kafka
102-
# networks:
103-
# - manual
89+
90+
91+
kafka-ui:
92+
image: provectuslabs/kafka-ui
93+
container_name: kafka-ui
94+
ports:
95+
- "8088:8080"
96+
restart: always
97+
environment:
98+
- KAFKA_CLUSTERS_0_NAME=local
99+
- KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS=kafka:9092
100+
depends_on:
101+
- kafka
102+
networks:
103+
- manual
104104

105105
networks:
106106
manual:

demo/flask_consumer_prefork.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to You under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
18+
"""
19+
sw-python -d run -p uwsgi --die-on-term \
20+
--http 0.0.0.0:9090 \
21+
--http-manage-expect \
22+
--workers 2 \
23+
--worker-reload-mercy 30 \
24+
--enable-threads \
25+
--threads 1 \
26+
--manage-script-name \
27+
--mount /=flask_consumer_prefork:app
28+
"""
29+
from flask import Flask
30+
import logging
31+
32+
app = Flask(__name__)
33+
34+
35+
# This is wrong!! Do not do this with prefork server, fork support (os.fork) do not work with uWSGI!
36+
# from skywalking import agent, config
37+
#
38+
# config.init(agent_collector_backend_services='127.0.0.1:11800', agent_name='your awesome service',
39+
# agent_logging_level ='DEBUG', agent_experimental_fork_support=True)
40+
#
41+
# agent.start()
42+
43+
44+
@app.route('/cat', methods=['POST', 'GET'])
45+
def artist():
46+
try:
47+
logging.critical('fun cat got a request')
48+
return {'Cat Fun Fact': 'Fact is cat, cat is fat'}
49+
except Exception as e: # noqa
50+
return {'message': str(e)}
51+
52+
53+
if __name__ == '__main__':
54+
# noinspection PyTypeChecker
55+
# app.run(host='0.0.0.0', port=9090)
56+
...

demo/gunicorn_consumer_prefork.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to You under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
"""
18+
This one demos how to use gunicorn with a simple fastapi uvicorn app.
19+
"""
20+
import logging
21+
22+
from fastapi import FastAPI
23+
24+
"""
25+
# Run this to see sw-python working with gunicorn
26+
sw-python -d run -p \
27+
gunicorn gunicorn_consumer_prefork:app \
28+
--workers 2 --worker-class uvicorn.workers.UvicornWorker \
29+
--bind 0.0.0.0:8088
30+
"""
31+
32+
app = FastAPI()
33+
34+
35+
@app.get('/cat')
36+
async def application():
37+
try:
38+
logging.critical('fun cat got a request')
39+
return {'Cat Fun Fact': 'Fact is cat, cat is fat'}
40+
except Exception as e: # noqa
41+
return {'message': str(e)}
42+
43+
44+
if __name__ == '__main__':
45+
# noinspection PyTypeChecker
46+
# uvicorn.run(app, host='0.0.0.0', port=8088)
47+
...

docker/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,4 @@ ARG SW_PYTHON_AGENT_VERSION
2424
RUN pip install --no-cache-dir "apache-skywalking[${SW_PYTHON_AGENT_PROTOCOL}]==${SW_PYTHON_AGENT_VERSION}"
2525

2626
# So that the agent can be auto-started when container is started
27-
ENTRYPOINT ["sw-python", "run"]
27+
ENTRYPOINT ["sw-python"]

docs/en/setup/CLI.md

Lines changed: 65 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
# SkyWalking Python Agent Command-Line Interface (sw-python CLI)
1+
# SkyWalking Python Agent Command Line Interface (sw-python CLI)
22

3-
In releases before 0.7.0, you would at least need to add the following lines to your applications to get the agent attached and running.
3+
**Now, SkyWalking Python Agent CLI is the recommended way of running your application with Python agent,
4+
the CLI is well-tested and used by all agent E2E & Plugin tests.**
45

5-
This is the recommended way of running your application with Python agent.
6+
7+
In releases before 0.7.0, you would at least need to add the following lines to your applications to get the agent attached and running,
8+
this can be tedious in many cases due to large number of services, DevOps practices and can cause problem when used with prefork servers.
69

710
```python
811
from skywalking import agent, config
@@ -11,56 +14,65 @@ agent.start()
1114
```
1215

1316

14-
Now the SkyWalking Python agent implements a command-line interface that can be utilized to attach the agent to your
17+
The SkyWalking Python agent implements a command-line interface that can be utilized to attach the agent to your
1518
awesome applications during deployment **without changing any application code**,
1619
just like the [SkyWalking Java Agent](https://github.com/apache/skywalking-java).
1720

21+
> The following feature is added in v1.0.0 as experimental flag, so you need to specify the -p flag to `sw-python run -p`.
22+
> In the future, this flag will be removed and agent will automatically enable prefork/fork support in a more comprehensive manner.
23+
24+
Especially with the new automatic postfork injection feature, you no longer have to worry about threading and forking incompatibility.
25+
26+
Check [How to use with uWSGI](faq/How-to-use-with-uwsgi.md) and [How to use with Gunicorn](faq/How-to-use-with-gunicorn.md) to understand
27+
the detailed background on what is post_fork, why you need them and how to easily overcome the trouble with `sw-python` CLI.
28+
29+
You should still read the [legacy way](Intrusive.md) to integrate agent in case the `sw-python` CLI is not working for you.
30+
31+
32+
1833
## Usage
1934

2035
Upon successful [installation of the SkyWalking Python agent via pip](Installation.md#from-pypi),
2136
a command-line script `sw-python` is installed in your environment (virtual env preferred).
2237

23-
run `sw-python` to see if it is available.
38+
> run `sw-python` to see if it is available, you will need to pass configuration by environment variables.
39+
40+
For example: `export SW_AGENT_COLLECTOR_BACKEND_SERVICES=localhost:11800`
2441

2542
### The `run` option
2643

27-
Currently, the `sw-python` CLI provides a `run` option, which you can use to execute your applications
44+
The `sw-python` CLI provides a `run` option, which you can use to execute your applications
2845
(either begins with the `python` command or Python-based programs like `gunicorn` on your path)
2946
just like you invoke them normally, plus a prefix, the following example demonstrates the usage.
3047

31-
If your previous command to run your gunicorn application is:
48+
If your previous command to run your gunicorn/uwsgi application is:
3249

33-
`gunicorn app.wsgi`
50+
`gunicorn your_app:app --workers 2 --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:8088`
3451

35-
Please change it to:
52+
or
3653

37-
`sw-python run gunicorn app.wsgi`
54+
`uwsgi --die-on-term --http 0.0.0.0:5000 --http-manage-expect --master --workers 3 --enable-threads --threads 3 --manage-script-name --mount /=main:app`
3855

39-
The SkyWalking Python agent will startup along with your application shortly.
56+
Please change it to (**the `-p` option starts one agent in each process, which is the correct behavior**):
4057

41-
Note that the command does work with multiprocessing and subprocess as long as the `PYTHONPATH` is inherited,
42-
please configure the environment variables configuration accordingly based on the general documentation.
58+
**Important:** if the call to uwsgi/gunicorn is prefixed with other commands, this approach will fail
59+
since agent currently looks for the command line input at index 0 for safety as an experimental feature.
4360

44-
When executing commands with `sw-python run command`, your command's Python interpreter will pick up the SkyWalking loader module.
61+
`sw-python run -p gunicorn your_app:app --workers 2 --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:8088`
4562

46-
It is not safe to attach SkyWalking Agent to those commands that resides in another Python installation
47-
because incompatible Python versions and mismatched SkyWalking versions can cause problems.
48-
Therefore, any attempt to pass a command that uses a different Python interpreter/ environment will not bring up
49-
SkyWalking Python Agent even if another SkyWalking Python agent is installed there(no matter the version),
50-
and will force exit with an error message indicating the reasoning.
63+
or
5164

52-
#### Disabling child processes from starting new agents
65+
`sw-python run -p uwsgi --die-on-term --http 0.0.0.0:5000 --http-manage-expect --master --workers 3 --enable-threads --threads 3 --manage-script-name --mount /=main:app`
5366

54-
Sometimes you don't actually need the agent to monitor anything in a child process.
5567

56-
If you do not need the agent to get loaded for application child processes, you can turn off the behavior by setting an environment variable.
68+
The SkyWalking Python agent will start up along with all your application workers shortly.
5769

58-
`SW_AGENT_SW_PYTHON_BOOTSTRAP_PROPAGATE` to `False`
70+
Note that `sw-python` also work with spawned subprocess (os.exec*/subprocess) as long as the `PYTHONPATH` is inherited.
5971

60-
Note the auto bootstrap depends on the environment inherited by child processes,
61-
thus prepending a new sitecustomize path to or removing the loader path from the `PYTHONPATH` could prevent the agent from loading in a child process.
72+
Additionally, `sw-python` started agent works well with `os.fork` when your application forks workers,
73+
as long as the `SW_AGENT_EXPERIMENTAL_FORK_SUPPORT` is turned on. (It will be automatically turned on when gunicorn is detected)
6274

63-
### Configuring the agent
75+
## Configuring the agent
6476

6577
You would normally want to provide additional configurations other than the default ones.
6678

@@ -69,13 +81,13 @@ You would normally want to provide additional configurations other than the defa
6981
The currently supported method is to provide the environment variables listed
7082
and explained in the [Environment Variables List](Configuration.md).
7183

72-
#### Through a sw-config.yaml (TBD)
84+
#### Through a sw-config.toml (TBD)
7385

74-
Currently, only environment variable configuration is supported; an optional `yaml` configuration is to be implemented.
86+
Currently, only environment variable configuration is supported; an optional `toml` configuration is to be implemented.
7587

76-
### Enabling CLI DEBUG mode
88+
## Enabling CLI DEBUG mode
7789

78-
Note the CLI is a new feature that manipulates the Python interpreter bootstrap behaviour, there could be unsupported cases.
90+
Note the CLI is a feature that manipulates the Python interpreter bootstrap behaviour, there could be unsupported cases.
7991

8092
If you encounter unexpected problems, please turn on the DEBUG mode by adding the `-d` or `--debug` flag to your `sw-python` command, as shown below.
8193

@@ -86,6 +98,29 @@ To: `sw-python -d run command`
8698
Please attach the debug logs to the [SkyWalking Issues](https://github.com/apache/skywalking/issues) section if you believe it is a bug,
8799
[idea discussions](https://github.com/apache/skywalking/discussions) and [pull requests](https://github.com/apache/skywalking-python/pulls) are always welcomed.
88100

101+
102+
## Additional Remarks
103+
104+
When executing commands with `sw-python run command`, your command's Python interpreter will pick up the SkyWalking loader module.
105+
106+
It is not safe to attach SkyWalking Agent to those commands that resides in another Python installation
107+
because incompatible Python versions and mismatched SkyWalking versions can cause problems.
108+
Therefore, any attempt to pass a command that uses a different Python interpreter/ environment will not bring up
109+
SkyWalking Python Agent even if another SkyWalking Python agent is installed there(no matter the version),
110+
and will force exit with an error message indicating the reasoning.
111+
112+
#### Disabling spawned processes from starting new agents
113+
114+
Sometimes you don't actually need the agent to monitor anything in a new process (when it's not a web service worker).
115+
(here we mean process spawned by subprocess and os.exec*(), os.fork() is not controlled by this flag but `experimental_fork_support`)
116+
117+
If you do not need the agent to get loaded for application child processes, you can turn off the behavior by setting an environment variable.
118+
119+
`SW_AGENT_SW_PYTHON_BOOTSTRAP_PROPAGATE` to `False`
120+
121+
Note the auto bootstrap depends on the environment inherited by child processes,
122+
thus prepending a new sitecustomize path to or removing the loader path from the `PYTHONPATH` could also prevent the agent from loading in a child process.
123+
89124
#### Known limitations
90125

91126
1. The CLI may not work properly with arguments that involve double quotation marks in some shells.

0 commit comments

Comments
 (0)