Best practices (to my little knowledge) is to build and provide your own custom docker image that has all the required dependencies.
Once you got that you push it to a repository of your choice and then there is a specific set of option you can use in your DAG file to declare what docker image to use for each task in your DAG, as in :
def use_airflow_binary():
rc = os.system("airflow -h")
assert rc == 0
# You don't have to use any special KubernetesExecutor configuration if you don't want to
start_task = PythonOperator(
task_id="start_task", python_callable=print_stuff, dag=dag
)
# But you can if you want to
one_task = PythonOperator(
task_id="one_task", python_callable=print_stuff, dag=dag,
executor_config={"KubernetesExecutor": {"image": "airflow:latest"}}
)
# Use the airflow -h binary
two_task = PythonOperator(
task_id="two_task", python_callable=use_airflow_binary, dag=dag,
executor_config={"KubernetesExecutor": {"image": "airflow:latest"}}
)
The interesting bit here is :
executor_config={"KubernetesExecutor": {"image": "airflow:latest"}}
This is where you can use your custom built docker image.
There is even more option available (resources allocation, affinity etc ...)
All credits goes to Marc Lamberti