Skip to content

Swtich both PROD and CI images to use virtual environments - #19189

Closed
potiuk wants to merge 1 commit into
apache:mainfrom
potiuk:switch-to-venv
Closed

Swtich both PROD and CI images to use virtual environments#19189
potiuk wants to merge 1 commit into
apache:mainfrom
potiuk:switch-to-venv

Conversation

@potiuk

@potiuk potiuk commented Oct 23, 2021

Copy link
Copy Markdown
Member

Seems that the future direction of package installation for Python
is to always use virtualenvs to install dependencies. There was a
heated discussion about it in the issue here:

pypa/pip#10556

and general consensus is that virtualenv building should also be
used in Docker images as it can help to avoid multiple
problems related to interference between distro-managed and
PIP-managed files.

This change implements it - both PROD and CI images are converted
to use virtualenv for installation and instead of copying
the .local directory between image segments they do the same
with newly created /.venv virtual environment. All dependencies
and shared libraries are installed there are and shared between
all users using the images.


^ Add meaningful description above

Read the Pull Request Guidelines for more information.
In case of fundamental code change, Airflow Improvement Proposal (AIP) is needed.
In case of a new dependency, check compliance with the ASF 3rd Party License Policy.
In case of backwards incompatible changes please leave a note in UPDATING.md.

@potiuk

potiuk commented Oct 24, 2021

Copy link
Copy Markdown
Member Author

Hey @uranusjr @kaxil @ashb (and others). I have an interesting case that I need your opinion on.
This has been result of (rather heated) discussion we have in pypa/pip#10556 .

Saving you some need to read the (unpleaseant at times) discussion there - short context here (at least my view of it).

Seems that the "recommended" way forward for any future installations of pip dependencies is via virtualenv. This is a bit implicit and (as I see it at least) not stated clearly and strongly enough to make it the "only" apprioach, but on the other hand it results in some unremovable warnings in our image that migh confuse our users.

Since it seems that no-one had good answers to my questins, I decided to take a stab on it and see what it means for our images (both CI and PROD) to switch from the --user approach to uising venv.

I already investigated and fixed a few issues connected (documented also in the PyPI discussion) and I plan to improve the recommendation (and possibly even to help to make it a bit more firm/stronger recommendation - especially for people who build docker images and want to get them optimized by size and follow "aribitrary user id" recommendations from OpenShift. It's mainly about copying venvs between stages in docker image building and applicability of re-using the single venv among mutliple users. I have a few observations and recomendation for other people trying to do it already, but one question remains - so far about our approach of creating venv by PythonVirtualenvOperator.

The main problem is (and it is partially an Airflow bug, partially the way how virtualenv work) - that even currently PythonVirtualenvOperator will behave DIFFERENTLY when airflow is instaled as "system" package and when it is installed in virtualenv. This results in tests/operators/test_python.py::TestPythonVirtualenvOperator::test_airflow_context failing (you will see it failing in this PR),

The problem is that if Airflow (and all its dependencies) are installed in virtualenv, then PythonVirtualenvOperator will behave wrongly. Specifically in this case it will fail if "dill" and "system_site_packages" are enabled (even without dill it will behave differently but with dill is pretty straightforward what happens).

  1. First problem (smaller) - instead of "airflow" version of pip - it will use the "system" version of pip. In our case we have pip 23.1 installed in airflow venv, on the other hand pip 21.2.4 is installed in "debian-buster" by default. If airflow (and pip) is installed in venv, the new venv created will have (not surprisingly) pip 21.2.4 even if --system-site-packages is used. This will be different if airflow is installed as "system" package or with --user flag. It would be nice to get the same version as the one used in airflow venv, however - one might say "this is as expected" . Not sure if we want to do anything about it - I have asked pip maintainers if they have some solutions to that. We could do several things here: - we can accept that as "expected" behaviour, we could reinstall pip, setuptools, wheels to the same versions that are installed in airflow venv as well. I am not too woried about it - though I would love to hear your opinion.

  2. Second problem (much bigger and actually at least a bit undocumented Airflow behaviour) - as the test shows, if airflow is installed in venv only, the new virtualenv will neither have no airflow no dill, no lazy-proxy installed (in the current PythonVirtualenv) - you would have to specify all those as requirements explicitly. This might be considered as expected behaviour, however - again - it will work fine when airlfow is installed as "System" package or with --user flag - becuase the --system-site-packages flag will take care of carrying those packages to the new environment (with --user flag it will even actually work WITHOUT --system-site-packages). It looks like this behaviour was made "Somewhat" consistent in case no ---system-site-packages were used - the python virtualenv has "if" when the lazy-proxy and dill are added to requirements in this case. But apparently the case when "Airflow" is installed in virtualenv is not handled similarly.
    So we have pretty inconsistent behaviour here and I would love to make it consistent (or at least precisely document the inconsistencies in those different cases).

I tried to fix it in by adding lazy-proxy and dill even if --system-site-packages are not present (last fixup) but this is not a good fix - dill imports pendulum which is also needed in this case and also it does not handle the case of airflow macros and custom macros that will also be missing in this case because airflow is missing as well.

I'd love to hear your opinion on that. I see three possible approaches:

  1. we clearly document the current behaviour and inconsistencies - which is to expect airflow / dill and other needed packages - to be added explicitly when airflow is installed in virtualenv. This seems like most "bare" solution that will need the users to understand differences between airflow installed as system package and venv.

  2. we handle "system_site_packages" parameter differently when airlfow is installed in venv. We could in this case not only use --system-site-packages but also make sure that all the packages that are in virtualenv of airflow are also present in the new virtualenv. This can be done rather easily with .pth approach or usinng virtualenv-clone package, This one seems to be best for consistency - no matter if airflow is installed as system package, --user, virtualenv, this parameter will make all tha packages of airflow, dill etc. available in the new venv.

  3. we add third parameter "clone_virtualenv" or similar and only then clone the venv (if airflow is used as virtualenv) - we might need to discuss how it should interfere with "system_site_packages" parameter but this seems most "versatile" solution.

Would love to hear what others think about it.

@potiuk

potiuk commented Oct 24, 2021

Copy link
Copy Markdown
Member Author

Hmm. I see e problem of it interacting with python_version parameter. If we specify different python version than the current one, then we have even more complex relationship, because then if python_version is set (and different than current version), then even when Airflow is installed as system or user package, it will behave the same way as if airlfow was installed via venv: it will NOT copy airflow/dill/others :(. The more I think about it, the more I think we should just - document this behaviour.

It's a mess and I think we will not be able to have a consistent behaviour - seems like users will have to understand the differences between different ways airflow might be installed and it's effect on PythonVirtualenvOperator.

@potiuk
potiuk requested review from ashb and kaxil October 24, 2021 19:23
@potiuk

potiuk commented Oct 24, 2021

Copy link
Copy Markdown
Member Author

Actually. I made it works with "virtualenv-clone" approach (so approach number 3).

  • by default 'clone_airflow_virtualenv' is set to True (to maintain backwards compatibility of the behaviour in our base image) where we use virtualenv now
  • however clone_airflow_virtualenv=True only performs the clone when:
    • no python= is used in VirtualenvOperator
    • airflow actualy is installed via Virtualenv
    • --system-site-packages are ignored in this case (cloning virtualenv automatically clones all packages in airflow venv so it makes no sense to add system-site-packages anyway)

If Airflow is installed as system/user package or when someone specified python= in VirtualEnv, an empty virtualenv is created (possibly with --system-site-packages - similarly as before.

Let me know what you think about this approach - I think it is as "close" as it can get to expected behaviour and as little potentialy disruptive as possible (I think the case where someone had virtualenv with airflow and no dill/airflow specified in requirements, it would not not work anyway so this is not really breaking change - it fixes a case which was broken and non-functional.

@potiuk

potiuk commented Oct 24, 2021

Copy link
Copy Markdown
Member Author

I am also happy to split this change in two if we agree it's good and if we see it works:

  • first adding the clone_airflow_virtualenv to PythonVirtualenv Operator (must be merged first otherwise tests will fail)
  • second switching Airflow images to use virtualenv

@potiuk potiuk closed this Oct 25, 2021
@potiuk potiuk reopened this Oct 25, 2021
@potiuk
potiuk marked this pull request as ready for review October 25, 2021 00:48
@potiuk

potiuk commented Oct 25, 2021

Copy link
Copy Markdown
Member Author

Looks like it is ready for review - all tests should be passing (there was one intermittent test that failed).

Comment thread Dockerfile Outdated
Comment thread Dockerfile Outdated
@uranusjr

Copy link
Copy Markdown
Member

I've be hesitant to propose this since this is technically a backward incompatibility change for those using PROD as a base image for their Dockerfile, the most significant part being (obviously) the location of the interpreter. So while I think this is a good thing to do in a vacuum, this should probably either be done by introducing a new image tag series and deprecating the venv-less one until 3.0.

@potiuk

potiuk commented Oct 25, 2021

Copy link
Copy Markdown
Member Author

I've be hesitant to propose this since this is technically a backward incompatibility change for those using PROD as a base image for their Dockerfile, the most significant part being (obviously) the location of the interpreter. So while I think this is a good thing to do in a vacuum, this should probably either be done by introducing a new image tag series and deprecating the venv-less one until 3.0.

First of all I do not thing this is backwards-incompatible, secondly - I do not really think this is a problem even if it was becuase airflow incompatibility has nothing to do with image incompatibility (especially that our image is not yet "official stable" image - it's a "reference" image).

Why I think it is not incompatible?

Because all the examples and recommendation we had about extending and customising the image, remain unchanged. The image, airlfow, providers and all the tools inside will continue to work if peopel were using all our examples and following them (and we have PLENTY of them). Even more - those examples are automatically validated during the CI build (except image customisation that I run separately every time I make significant change like this one - so I am pretty sure they are working fine. All our prod 'image tests" are also working fine with it (we test if all the imports work, if all providers are installed and are importable, etc. etc. From the user's point of view - who either customizes or extends the image - nothing changes. The only change is where the packages are installed. But if they use (as they should) pip to manipulate their packages, nothing changes.

Even if they manualy added --user flag in their PIP, this will continue to work (except some really obscure changes) - althought they were not even encouraged to do that - we had PIP_USER variable set in the image which made this behaviour automatic (and this variable is gone with that change).

This is really equivalent to refactoring code wihch is not "public" API in Python. The "location" of the instaled packages is not "public API". The 'pip' commands to manipulate those are the API (and those have not changed).

Now why this would not be a big problem even if it was more "backwards-incompatible"?

The Airflow X.Y compatibility is all about "Airflow", not about the image. There is no "guarantee" that the image will remain unchanged - in fact we have done quite a number of incompatible variable names when customizing the image in the past without any major disruptions to our users. The Image we publish is not "official" release - it is a "convenience binary" and I often even name it "reference image". It does not bring the same "guarantees" as official release, details of it can change without breaking Airflow MAJOR version compatibility. I try - of course - not to do it and I think we had far more of those changes between 2.0.0 and 2.1.0 - where we got a lot of feedback from the users (for example OpenShift compatibility came from that) and were able to incorpoarate a lot of that without waiting for Airflow 3. That's a major win for the quality of the image I think. Even Python base images did some backwards-incompatible changes in the past. For example by replacing the 3.* images suddenly with removal of Python 2.7 (!) without even bumping patchlevel (!). That's not a "nice" approach of course - but technically speaking it did not break Python3.* compatibility (otherwise they would have to wait with releasing the images without Python 2.7 until version 4).

This situation will change however (from my point of view at least - apparently Python maintainers have a different view on that) when we apply for the "official docker image status" - https://docs.docker.com/docker-hub/official_images/. Then I would be far more careful about similar changes. This is about the last two changes I am still hesitant about completing because there were a few open things (like the .venv). When I look at the rate of changes of the image it stabilized significantly. We handle all the cases we want to handle, the API to build those images was significantly simplified and more intuitive, we had far more issues raised by the users that my answer is "Yes - this is supported already by the image see the doc here" (for example when people want to build image in air-gaped environment or when they want to verify provenence of all the python packages, or when they want to add custom entrypoint etc. etc. ).

I was building up the knowledge and documentation and I think I am rather close to say "yeah we are ready to get the official image status". By then as well I plan to extract a separate "read-only" repo where only relevant files will be present (I plan to use copybara to only copy relevant commits/code from Airflow repo) and then it will be much easier for users to "officially" build their custom images and it will be actually built automatically by Docker's Official Image Maintainers team.

Plus we will get extra security checks and notifications as the "official" images by Docker get special treatment and got some automated scanning and notifications - and then we will likely also have to build a bit faster loop on rebuilding the images when security issues are discovered in base image. That's another topic to be discussed when we apply for the "official" status. Then such images will be available to pull as docker pull apache-airflow and then yeah - I agree such change could be seen as backwards-incompatibile.

See the issues there: https://github.com/apache/airflow/projects/3 - not having "official" status is the only reason why AIP-26 is still "in-progress".

@potiuk

potiuk commented Oct 25, 2021

Copy link
Copy Markdown
Member Author

Just another commment here also @uranusjr (to clarify as I realized it was not clear).

My "no broken compatibility" comment relates to "Image" changes, Not the "virtualenvoperator" changes. Currently they are in one PR (so that the tests are passing). But I am ok to split the virtualenv operato changes out and treat them differently.
I am not very strong on that one (althought I also consider it as a "fix of what was broken" rather than "changed behaviour").

But I am open for discusion and arguments on that one.

@potiuk

potiuk commented Oct 25, 2021

Copy link
Copy Markdown
Member Author

BTW. @uranusjr I really got to like the idea of using venv eventually. After solving all the initial problems and figuring out how to clone the venv when needed I think this is really good idea. One of the nice side effects is that when you enter the interactive session of the images (both CI and PROD) you got the prompt indication that you are in airflow virtualenv. Regardless of any other prompt tools you have. Really nice touch:

docker run -it ghcr.io/apache/airflow/main/prod/python3.6 bash
(airflow) airflow@b740439435ec:/opt/airflow$ 

@potiuk

potiuk commented Nov 25, 2021

Copy link
Copy Markdown
Member Author

Finally had time to return to that one. I will make it pass and then I will look at the remaining comments about PythonVirtualenv operator

@potiuk

potiuk commented Nov 25, 2021

Copy link
Copy Markdown
Member Author

@uranusjr - I removed the "activation" and left only the variables setting to emulate what activate does - there are however problems with serializing Proxy, which makes me think that the venv in venv will haave to be solved differently.

@potiuk

potiuk commented Nov 25, 2021

Copy link
Copy Markdown
Member Author

I will take a look tomorrow, but any hints are welcome.

@potiuk

potiuk commented Nov 25, 2021

Copy link
Copy Markdown
Member Author

After the whole discussion, I somehow have a feeling (without recalling back all the sentiments during the discussion), that the case that airflow should run in venv in the image is NOT the best idea if we are going to support venv creation by it.

Let me know what you think @uranusjr - but if we come to that conclusion, I think we might have a good case where venv is not recommended (which might be good input to PEP 668 that I am going to come back to as well.

Seems that the future direction of package installation for Python
is to always use virtualenvs to install dependencies. There was a
heated discussion about it in the issue here:

pypa/pip#10556

and general consensus is that virtualenv building should also be
used in Docker images as it can help to avoid multiple
problems related to interference between distro-managed and
PIP-managed files.

This change implements it - both PROD and CI images are converted
to use virtualenv for installation and instead of copying
the `.local` directory between image segments they do the same
with newly created `/.venv` virtual environment. All dependencies
and shared libraries are installed there are and shared between
all users using the images.
Comment thread Dockerfile
AIRFLOW_CONSTRAINTS_REFERENCE=${AIRFLOW_CONSTRAINTS_REFERENCE} \
AIRFLOW_CONSTRAINTS_LOCATION=${AIRFLOW_CONSTRAINTS_LOCATION} \
DEFAULT_CONSTRAINTS_BRANCH=${DEFAULT_CONSTRAINTS_BRANCH} \
# Install Airflow in a virtual environment, so that we can copy the whole

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What code is this comment referring to?

@potiuk

potiuk commented Dec 12, 2021

Copy link
Copy Markdown
Member Author

@uranusjr @mik-laj - I split the Dockerfile modiffcation from this one and #19210 into #20238.

I do not think I am going to pursue the venv route actually (at least not shortly).

The way how we could easily create venv from within an already active venv is at odds with how PythonVirtualenv works for Apache Airflow.

I thought a bit on that and gave myself time to think about it and I think this case (Creating a venv dynamically from another venv) is where the venv approach cannot really work in the same way as if "system" installed application, so I am not going to make Airflow work this way.

I will think if I want to come back at all to my suggestions to PEP 668. I gave myself time to think, and to be honest I found myself quite demotivated. I thought I've followed the advice on trying to contribute there, I've followed up, gave the context, presented my experience and willingness to clarify things, but I do not see any interest of the people who are involved there. Seems like clarifying soime of the "venv" statements and prefering to leave them in "vague" state without any user guidance, rather than clarifying cases where it might not be the best approach (for cases like ours for exampel). Which I think is not best approach, but then, who am I to judge.

So I think I will simply ignore that PEP for now (which is still in draft) - let's see if it gets an approval and adoption. For now I will just add a comment in our build process that "our case is different and the warning from PIP is not relevant".

@potiuk potiuk closed this Dec 12, 2021
potiuk added a commit to potiuk/airflow that referenced this pull request Jan 7, 2022
PIP produces a warning when root user is used to run pip install.
This is done for a good reason - because installing PIP this way
clashes with a number of distro-managed python packages.

The warning cannot be disabled even if our use case is legitimate
as has been extensively discussed in
pypa/pip#10556.

However, the advice given by the warning is a bit misleading - it
suggests to use virtualenv, but since this is considered a bad practice
for container building and because we need to create virtualenvs
dynamically inside the image, using virtualenv is a bad solution for us.
It's been attempted in apache#19189 and failed.

Instead we create an airflow user and use PIP_USER="true" which
installs all dependencies in build segment to ~/.local folder
from where we can copy it to the main image.

That get rids of the warning and at the same time allows us to
keep the best practices of building the images.
potiuk added a commit to potiuk/airflow that referenced this pull request Jan 8, 2022
PIP produces a warning when root user is used to run pip install.
This is done for a good reason - because installing PIP this way
clashes with a number of distro-managed python packages.

The warning cannot be disabled even if our use case is legitimate
as has been extensively discussed in
pypa/pip#10556.

However, the advice given by the warning is a bit misleading - it
suggests to use virtualenv, but since this is considered a bad practice
for container building and because we need to create virtualenvs
dynamically inside the image, using virtualenv is a bad solution for us.
It's been attempted in apache#19189 and failed.

Instead we create an airflow user and use PIP_USER="true" which
installs all dependencies in build segment to ~/.local folder
from where we can copy it to the main image.

That get rids of the warning and at the same time allows us to
keep the best practices of building the images.
potiuk added a commit that referenced this pull request Jan 8, 2022
PIP produces a warning when root user is used to run pip install.
This is done for a good reason - because installing PIP this way
clashes with a number of distro-managed python packages.

The warning cannot be disabled even if our use case is legitimate
as has been extensively discussed in
pypa/pip#10556.

However, the advice given by the warning is a bit misleading - it
suggests to use virtualenv, but since this is considered a bad practice
for container building and because we need to create virtualenvs
dynamically inside the image, using virtualenv is a bad solution for us.
It's been attempted in #19189 and failed.

Instead we create an airflow user and use PIP_USER="true" which
installs all dependencies in build segment to ~/.local folder
from where we can copy it to the main image.

That get rids of the warning and at the same time allows us to
keep the best practices of building the images.
potiuk added a commit to potiuk/pip that referenced this pull request Jan 8, 2022
Using PIP as rot is not recommended, however the recommendation
given by PIP in the warning message when someone usess PIP as root
contains recommendeation that might be misleading.

The message only mentiones recommendation of using virtualenv,
however this recommendation does not apply to building containers.

This has been extensively discussed with varying opinions in
the pypa#10556. There are a number of users who use PIP to build
containers who consider using virtualenv as an antipattern,
however there are also PIP maintainers who claim that the
warning message is ok, even if there were attempts to
implement the recommended approach as documented in
apache/airflow#19189 where attempts
to follow the recommendation failed.

This PR attempts to modify the message in a minimal way to still
keep the original recommendation but also provides the container
developers with more appropriate option of switching to another
user and using --user flag instead.
potiuk added a commit that referenced this pull request Jan 22, 2022
PIP produces a warning when root user is used to run pip install.
This is done for a good reason - because installing PIP this way
clashes with a number of distro-managed python packages.

The warning cannot be disabled even if our use case is legitimate
as has been extensively discussed in
pypa/pip#10556.

However, the advice given by the warning is a bit misleading - it
suggests to use virtualenv, but since this is considered a bad practice
for container building and because we need to create virtualenvs
dynamically inside the image, using virtualenv is a bad solution for us.
It's been attempted in #19189 and failed.

Instead we create an airflow user and use PIP_USER="true" which
installs all dependencies in build segment to ~/.local folder
from where we can copy it to the main image.

That get rids of the warning and at the same time allows us to
keep the best practices of building the images.

(cherry picked from commit 3feb057)
jedcunningham pushed a commit that referenced this pull request Jan 27, 2022
PIP produces a warning when root user is used to run pip install.
This is done for a good reason - because installing PIP this way
clashes with a number of distro-managed python packages.

The warning cannot be disabled even if our use case is legitimate
as has been extensively discussed in
pypa/pip#10556.

However, the advice given by the warning is a bit misleading - it
suggests to use virtualenv, but since this is considered a bad practice
for container building and because we need to create virtualenvs
dynamically inside the image, using virtualenv is a bad solution for us.
It's been attempted in #19189 and failed.

Instead we create an airflow user and use PIP_USER="true" which
installs all dependencies in build segment to ~/.local folder
from where we can copy it to the main image.

That get rids of the warning and at the same time allows us to
keep the best practices of building the images.

(cherry picked from commit 3feb057)
@potiuk
potiuk deleted the switch-to-venv branch July 29, 2022 20:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants