Das VPN läuft, das interne Dashboard öffnet sich im Browser, und das Python-Skript wirft trotzdem einen Proxy-Fehler. Am Tunnel ist in diesem Bild nichts falsch: Der Interpreter liest eine Proxy-Adresse aus seiner eigenen Umgebung und schickt die Anfrage dorthin. Dieses Runbook liest die Umgebung, die der Prozess tatsächlich sieht, wendet die engste Ausnahme an und erwägt erst danach eine ausdrückliche Sitzungsrichtlinie. Jeder Schritt endet mit einem Befehl, dessen Ausgabe entscheidet, ob Sie weitermachen.

Lesen Sie die Umgebung, die der Prozess tatsächlich sieht

Proxy-Einstellungen des Desktops, Shell-Exporte und die Zeilen Environment= einer systemd-Unit sind drei verschiedene Quellen, und Requests liest nur die letzte, die es erbt. Führen Sie die Diagnose unten in derselben Shell, demselben Container oder derselben Unit aus, die das fehlschlagende Skript startet, nicht in Ihrem interaktiven Terminal. Eine Variable, die in Ihrer Login-Shell exportiert wurde, ist für einen Container-Prozess unsichtbar, und diese Lücke erklärt viele Meldungen über einen Proxy, der aus dem Nichts auftaucht. Die beiden Befehle geben dieselben Daten in unterschiedlicher Ausführlichkeit aus.

env | grep -i -E 'proxy|requests_ca_bundle|curl_ca_bundle'
python3 -c "import os;[print(k,'=',v) for k,v in sorted(os.environ.items()) if 'proxy' in k.lower()]"

Beide Schreibweisen jeder Variable werden gelesen, und doppelte Einträge werden in der Reihenfolge angewendet, in der der Prozess sie erhalten hat, sodass ein veraltetes HTTP_PROXY den gerade exportierten Wert überschreiben kann. Die CA-Bundle-Variablen sind hier ebenfalls wichtig, weil sie ändern, wozu verify auflöst, ohne irgendwo im Code aufzutauchen. Ist die Ausgabe leer, ist die Umgebung nicht Ihr Problem, und Sie können diesen Abschnitt nicht mehr weiterlesen. Listet sie einen Proxy auf, den Sie nicht kennen, finden Sie die Datei oder Unit, die ihn setzt, bevor Sie etwas ändern.

Fragen Sie Requests, was es mit einer bestimmten URL tun würde. Dieser Aufruf zeigt die zusammengeführten Proxy-, Verify- und Cert-Werte für diese Anfrage, ohne ein Paket zu senden.

python3 -c "import requests;s=requests.Session();print(s.merge_environment_settings('https://api.corp.example/status',{},None,None,None))"

Ein nicht leerer proxies-Eintrag für den privaten Host ist Ihre Antwort, und der ausgegebene verify-Wert sagt Ihnen, welchem CA-Bundle die Anfrage vertrauen würde. Wenn beides korrekt aussieht, liegt der Fehler später im Stack, und Sie sollten zum Verifikationsabschnitt springen. Raten Sie nicht aus dem Code: Die Zusammenführung erfolgt pro Anfrage, weshalb sich eine Sitzung für zwei Hosts unterschiedlich verhalten kann.

Schließen Sie den privaten Host aus, bevor Sie etwas deaktivieren

Wenn der Proxy für andere Ziele wirklich erforderlich ist, ist die engste Lösung eine Ausnahme für diesen Host. Hängen Sie an die vorhandene Liste an, statt sie zu ersetzen, denn das Löschen von NO_PROXY bricht jedes andere interne Ziel, das sich darauf verlassen hat. Die folgenden Befehle hängen den Eintrag an und fragen dann Requests, ob die Umgehung greift.

export NO_PROXY="${NO_PROXY:+$NO_PROXY,}api.corp.example,.corp.example"
python3 -c "import requests;print(requests.utils.should_bypass_proxies('https://api.corp.example/status',None))"

Die Übereinstimmung erfolgt nach Host-Suffix, sodass .corp.example die gesamte Domain abdeckt, während api.corp.example einen Namen abdeckt. Ein bloßes Sternchen deaktiviert Proxying für jeden Host und ist fast nie das, was ein Produktionsjob will. Ob ein CIDR-Eintrag beachtet wird, hängt von den Requests- und urllib3-Versionen im Image ab, also testen Sie den genauen Eintrag, statt anzunehmen, dass er funktioniert. Prüfen Sie das Ergebnis von should_bypass_proxies für die echte URL, nicht für einen Hostnamen, den Sie aus dem Gedächtnis getippt haben.

Halten Sie die beiden Schichten gedanklich getrennt. Das Umgehen des HTTP-Proxys umgeht nicht das VPN, denn die Proxy-Entscheidung fällt in der Bibliothek und die Tunnel-Entscheidung in der Routing-Tabelle des Kernels.

Deaktivieren Sie trust_env nur als bewusste Sitzungsrichtlinie

Wenn der gesamte Job die Umgebungskonfiguration ignorieren soll, bauen Sie die Sitzung explizit auf und halten Sie die Entscheidung im Code fest. Das ist eine Richtlinie, kein Workaround, und sie gehört in einen Diff, den ein Reviewer sieht. Das folgende Beispiel behält ein Timeout, einen expliziten CA-Pfad und eine Statusprüfung bei.

import requests

with requests.Session() as session:
    session.trust_env = False
    response = session.get(
        "https://api.corp.example/status",
        timeout=(5, 20),
        verify="/etc/ssl/certs/corp-ca.pem",
    )
    response.raise_for_status()

Das Abschalten von trust_env ändert drei Verhaltensweisen auf einmal, und nur eine davon betrifft Proxys. Requests liest keine .netrc-Anmeldedaten mehr, und die Variablen REQUESTS_CA_BUNDLE und CURL_CA_BUNDLE beeinflussen verify nicht mehr, das dann auf den mitgelieferten Zertifikatsspeicher zurückfällt. Wenn Ihre Organisation TLS mit ihrer eigenen CA beendet, erzeugt dieser Rückfall einen Verifizierungsfehler, der nichts mit der gerade vorgenommenen Änderung zu tun zu haben scheint. Das ausdrückliche Übergeben von verify hält die beiden Effekte getrennt.

Was Sie nicht tun sollten, ist, verify=False zu setzen, um den resultierenden Fehler zu unterdrücken. Das tauscht eine Proxy-Fehlkonfiguration gegen eine ungeprüfte Verbindung und verbirgt das Zertifikatsproblem, das der Ausschluss aufdecken sollte.

Verifizieren Sie die fehlschlagende Schicht und behalten Sie einen Rollback

Drei Fehler sehen im Log gleich aus und haben nichts gemeinsam: Ein Proxy, der das Ziel nicht erreicht, wirft ProxyError oder gibt 502 zurück, ein Vertrauensproblem wirft SSLError, und eine Anwendungsablehnung gibt 401 oder 403 zurück. Reproduzieren Sie die Anfrage außerhalb von Requests, um zu sehen, welchen Sie haben. Die folgenden Befehle entfernen den Proxy, testen die Zertifikatskette und wiederholen dann den Aufruf über die Bibliothek.

curl -sv --noproxy '*' https://api.corp.example/status -o /dev/null
openssl s_client -connect api.corp.example:443 -servername api.corp.example </dev/null
python3 -c "import requests;r=requests.get('https://api.corp.example/status',timeout=10);print(r.status_code)"

Wenn der curl-Aufruf erfolgreich ist und der Python-Aufruf fehlschlägt, ist die Umgebung immer noch der Unterschied. Wenn beide in der TLS-Phase fehlschlagen, war der Proxy nie das Problem, sondern die Zertifikatskette. Wenn beide 401 zurückgeben, hat die Anfrage die Anwendung erreicht, und die verbleibende Arbeit ist die Authentifizierung. Festzuhalten, welchen der drei Sie beobachtet haben, spart der nächsten Person eine Stunde.

Der Rollback ist eine Zeile in beide Richtungen: Entfernen Sie den Ausschluss oder stellen Sie trust_env zurück. Speichern Sie den vorherigen NO_PROXY-Wert, bevor Sie etwas anhängen, und bevorzugen Sie ein Wrapper-Skript oder einen Environment=-Eintrag auf Unit-Ebene gegenüber einem Export, der nur in Ihrer Terminal-Historie lebt. Beide Änderungen sind in der Prozessumgebung sichtbar, sodass ein Neustart Sie normalerweise zum alten Verhalten zurückbringt.

Schließen Sie ab, indem Sie vier Dinge bestätigen: Die zusammengeführten Einstellungen für die private URL zeigen keinen Proxy, das Zertifikat validiert gegen die beabsichtigte CA, die Anfrage gibt den erwarteten Status zurück, und die Änderung ist an einer reproduzierbaren Stelle committet. Ein grünes Skript mit unerklärter Umgebung ist eine Korrektur, die auf der nächsten Maschine wieder bricht. Die Befehle in diesem Runbook sind die, die Sie in das Ticket einfügen.

Ähnliche Artikel