在上週某天突然發現 Production 上的資料全部都變成 RC 環境的資料,project settings 都是 RC 環境的參數,這是一個非常嚴重的錯誤發生在 Production,第一時間我還還毫無頭緒。
先來看看 VM 裡面的目錄長這樣:
(先別嗆我為什麼三個環境的目錄會放在同一個 VM…)
project-admin/
└──project-prod/
| ├─app.py
| └─...
└──project-rc/
| ├─app.py
| └─...
└──project-test/
├─app.py
└─...
乍看之下沒問題,每個環境的 Code 都分別放在不同的目錄下,也都有個別使用虛擬環境來執行 gunicorn,出事當下也確認過 Production 環境的執行目錄是在 project-prod 底下沒錯,那怎麼還會有事?!
原因就是和 Python 模組的引用順序有關。
我們的專案是用 Flask 開發的,app.py 就是很簡單的實例化一個 Flask 物件。
# app.py
from flask import Flask
app = Flask(__name__)
接著我們來看看 Flask 的 Source Code:
# https://github.com/pallets/flask/blob/1.1.x/src/flask/app.py
class Flask(_PackageBoundObject):
...
def __init__(
self,
import_name,
static_url_path=None,
static_folder="static",
static_host=None,
host_matching=False,
subdomain_matching=False,
template_folder="templates",
instance_path=None,
instance_relative_config=False,
root_path=None,
):
_PackageBoundObject.__init__(
self, import_name, template_folder=template_folder, root_path=root_path
)
self.static_url_path = static_url_path
self.static_folder = static_folder
if instance_path is None:
instance_path = self.auto_find_instance_path()
elif not os.path.isabs(instance_path):
raise ValueError(
"If an instance path is provided it must be absolute."
" A relative path was given instead."
)
...
建構式中有個 Optional 參數 root_path
會被傳進 _PackageCoundObject.__init__()
中, 接著來看這個函式:
# https://github.com/pallets/flask/blob/1.1.x/src/flask/helpers.py
class _PackageBoundObject(object):
#: The name of the package or module that this app belongs to. Do not
#: change this once it is set by the constructor.
import_name = None
#: Location of the template files to be added to the template lookup.
#: ``None`` if templates should not be added.
template_folder = None
#: Absolute path to the package on the filesystem. Used to look up
#: resources contained in the package.
root_path = None
def __init__(self, import_name, template_folder=None, root_path=None):
self.import_name = import_name
self.template_folder = template_folder
if root_path is None:
root_path = get_root_path(self.import_name)
self.root_path = root_path
self._static_folder = None
self._static_url_path = None
# circular import
from .cli import AppGroup
#: The Click command group for registration of CLI commands
#: on the application and associated blueprints. These commands
#: are accessible via the :command:`flask` command once the
#: application has been discovered and blueprints registered.
self.cli = AppGroup()
...
def get_root_path(import_name):
"""Returns the path to a package or cwd if that cannot be found. This
returns the path of a package or the folder that contains a module.
Not to be confused with the package path returned by :func:`find_package`.
"""
# Module already imported and has a file attribute. Use that first.
mod = sys.modules.get(import_name)
if mod is not None and hasattr(mod, "__file__"):
return os.path.dirname(os.path.abspath(mod.__file__))
# Next attempt: check the loader.
loader = pkgutil.get_loader(import_name)
# Loader does not exist or we're referring to an unloaded main module
# or a main module without path (interactive sessions), go with the
# current working directory.
if loader is None or import_name == "__main__":
return os.getcwd()
...
如果沒有傳入指定的 root_path
, Flask 會調用 get_root_path 來搜尋跟目錄,看到這邊有一行在閃閃發亮 sys.module.get(import_name)
原來連我們寫的 app = Flask(__name__)
不是從當前的工作目錄開始找而是 sys.path
?
來做個實驗,建立兩個 flask 的 project:
/home/kevin/
└──repo1/
| ├─app.py
└──repo2/
├─app.py
# repo1/app.py
from flask import Flask
app = Flask(__name__)
print("repo1 app!")
# repo2/app.py
from flask import Flask
app = Flask(__name__)
print("repo2 app!")
接著使用錯誤的環境變數來改變 Python 尋找模組的順序:
export PYTHONPATH="home/kevin/repo2:home/kevin/repo1"
再用 gunicorn 執行看看會發生什麼事
$ gunicorn app:app
# repo2 app!
如果再把 mod = sys.modules.get(import_name)
這行的 mod 變數印出來則會看到
<module 'app' from '/home/kevin/repo2/app.py'>
這樣就重現了這個錯誤,修改的方法也很簡單,有幾種修正的方式
在開發 Python 專案中,print 一直是我們除錯的好幫手,可以快速把資訊輸出到螢幕上,那我們可以用 print 打天下不使用 logger 嗎?
May 28, 2025常常聽到 IAM,在公司使用 Google Cloud Platform (以下簡稱 GCP) 時也有遇到權限不足需要去申請的時候,但根本不了解裡面的內容,趁著最近讀完官方文件後簡單的寫個筆記。 What is IAM? 在 Google Cloud API 中的權限控管 (Access control) 可以大致拆分成三部分: Authentication Authorization Auditing 其中 Authentication 負責識別你是誰,Authorization 決定你能做什麼事,Auditing 則是記錄你做了什麼事,其中 Authorization 就是本文提到的 IAM。
Aug 23, 2022or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up