When preparing server nodes for YugabyteDB Anywhere (YBA), thereβs a small but critical prerequisite thatβs easy to overlook:
- Database nodes must have the Python SELinux package that corresponds to the Python version in use.
This requirement is explicitly called out in the YugabyteDB documentation, Python for database nodes, but it commonly trips teams up, especially on modern RHEL-family distributions where multiple Python versions often coexist on the same system.
In this tip, weβll explain why this check matters, what typically goes wrong, and provide a ready-to-run validation script that works for both humans and automation.
π€ Why this check is needed
YugabyteDB Anywhere relies on Python tooling (including Ansible-based workflows) that interacts with SELinux on the database nodes.
For this to work correctly:
β The SELinux Python bindings must be installed
β They must match the exact Python interpreter being used
On systems like AlmaLinux, Rocky Linux, or RHEL 8/9, itβs very common to see situations like:
β
python3-libselinuxis installed ββ But
python3points to Python 3.11, while the OS packages target Python 3.9 ββ Result:
No module named 'selinux'
From the OS perspective everything looks installed, but at runtime the bindings are simply invisible to the active Python interpreter.
β What βgoodβ looks like
A database node meets the YugabyteDB requirement when the following succeeds:
python3 -c "import selinux"
If that import works, YugabyteDB Anywhere can proceed.
π A single script to validate (human + automation)
To make this easy and repeatable, hereβs a script you can hand to customers or bake into preflight checks.
What the script does
β Detects OS and Python details
β Tests
import selinuxusing the activepython3β Checks for installed SELinux Python RPMs
β Probes the OS platform Python (when present)
β Emits clear recommendations
β Supports JSON output for automation
β Supports strict mode for compliance checks
yb_python_selinux_check.sh
#!/usr/bin/env bash
set -euo pipefail
# yb_python_selinux_check.sh
# Verifies YugabyteDB Anywhere database-node prerequisite:
# "Install the Python SELinux package corresponding to your version of Python."
#
# Modes:
# - Default (human readable)
# - JSON: --json (machine readable; stable keys; exit code still meaningful)
#
# Exit codes:
# 0 = PASS (import selinux works in the active python3)
# 1 = FAIL (import selinux fails)
# 2 = ERROR (python3 missing or unexpected runtime issue)
JSON_MODE="false"
if [[ "${1:-}" == "--json" ]]; then
JSON_MODE="true"
fi
# Minimal JSON escaping for our use-case
json_escape() {
local s="${1:-}"
s="${s//\\/\\\\}"
s="${s//\"/\\\"}"
s="${s//$'\n'/\\n}"
s="${s//$'\r'/\\r}"
s="${s//$'\t'/\\t}"
printf '%s' "$s"
}
emit_json() {
# Args are already escaped if needed by caller
printf '{%s}\n' "$1"
}
hr() { echo "--------------------------------------------------------------------------------"; }
# OS detection
OS_NAME="unknown"; OS_VERSION="unknown"; OS_ID="unknown"; OS_VERSION_ID="unknown"
if [[ -f /etc/os-release ]]; then
# shellcheck disable=SC1091
. /etc/os-release
OS_NAME="${NAME:-unknown}"
OS_VERSION="${VERSION:-unknown}"
OS_ID="${ID:-unknown}"
OS_VERSION_ID="${VERSION_ID:-unknown}"
fi
# python3 discovery
PY_BIN="$(command -v python3 || true)"
if [[ -z "$PY_BIN" ]]; then
if [[ "$JSON_MODE" == "true" ]]; then
emit_json "\"status\":\"error\",\"reason\":\"python3_not_found\",\"os\":{\"name\":\"$(json_escape "$OS_NAME")\",\"version\":\"$(json_escape "$OS_VERSION")\",\"id\":\"$(json_escape "$OS_ID")\",\"version_id\":\"$(json_escape "$OS_VERSION_ID")\"}"
else
echo "ERROR: python3 not found in PATH."
fi
exit 2
fi
PY_VERSION="$("$PY_BIN" --version 2>&1 || true)"
PY_EXEC="$PY_BIN"
# SELinux import test
IMPORT_OK="false"
IMPORT_ERR=""
IMPORT_OUT="$("$PY_BIN" - <<'PY' 2>&1
try:
import selinux
print("PASS")
except Exception as e:
print("FAIL:", repr(e))
PY
)" || true
if echo "$IMPORT_OUT" | grep -q "^PASS$"; then
IMPORT_OK="true"
else
IMPORT_ERR="$IMPORT_OUT"
fi
# RPM package hints (best-effort)
RPM_FOUND="false"
RPM_LIST=""
RPM_PYSELINUX_FILES=""
if command -v rpm >/dev/null 2>&1; then
# Typical names:
# - EL8/EL9: python3-libselinux
# - EL7: libselinux-python
RPM_LIST="$(rpm -qa | grep -E '(^|-)python3-libselinux|(^|-)libselinux-python' || true)"
if [[ -n "$RPM_LIST" ]]; then RPM_FOUND="true"; fi
# If python3-libselinux installed, list relevant installed paths
if rpm -q python3-libselinux >/dev/null 2>&1; then
RPM_PYSELINUX_FILES="$(rpm -ql python3-libselinux | egrep -i 'selinux.*\.(so|py)$|site-packages|dist-packages' || true)"
elif rpm -q libselinux-python >/dev/null 2>&1; then
RPM_PYSELINUX_FILES="$(rpm -ql libselinux-python | egrep -i 'selinux.*\.(so|py)$|site-packages|dist-packages' || true)"
fi
fi
# sys.path (best-effort)
SYS_PATH="$("$PY_BIN" - <<'PY' 2>/dev/null || true
import sys
print("\n".join(sys.path))
PY
)"
# platform-python probe (common on RHEL-family)
PLATFORM_PY="/usr/libexec/platform-python"
PLATFORM_PY_EXISTS="false"
PLATFORM_PY_VERSION=""
PLATFORM_IMPORT_OK="false"
if [[ -x "$PLATFORM_PY" ]]; then
PLATFORM_PY_EXISTS="true"
PLATFORM_PY_VERSION="$("$PLATFORM_PY" --version 2>&1 || true)"
if "$PLATFORM_PY" -c "import selinux" >/dev/null 2>&1; then
PLATFORM_IMPORT_OK="true"
fi
fi
# Recommendation logic (simple + actionable)
RECOMMENDATION=""
if [[ "$IMPORT_OK" == "true" ]]; then
RECOMMENDATION="PASS: Active python3 can import selinux. Node meets requirement."
else
# Heuristic guidance
if [[ "$OS_ID" =~ (almalinux|rhel|rocky|centos|fedora) ]]; then
# On EL8/EL9 it's common that bindings target platform python, while python3 might be a different minor version.
if [[ "$PLATFORM_PY_EXISTS" == "true" && "$PLATFORM_IMPORT_OK" == "true" ]]; then
RECOMMENDATION=$'FAIL: Active python3 cannot import selinux, but platform-python can.\nFix: Align Yugabyte tooling to use platform/system Python, or align python3 back to the distro Python version (common on EL8/EL9).'
elif [[ "$RPM_FOUND" == "true" ]]; then
RECOMMENDATION=$'FAIL: SELinux binding RPM appears installed, but active python3 cannot import selinux.\nLikely mismatch between your python3 minor version and the RPM-provided bindings. Compare rpm-installed paths vs python sys.path.\nFix: Use distro/platform Python or install SELinux bindings matching your python3.'
else
RECOMMENDATION=$'FAIL: SELinux Python binding package not detected and import fails.\nFix: Install the appropriate SELinux Python bindings for your OS/Python (EL8/EL9 typically python3-libselinux; EL7 typically libselinux-python), then re-test.'
fi
else
RECOMMENDATION=$'FAIL: import selinux failed.\nFix: Install SELinux Python bindings for the active python3, or use a system Python that includes them.'
fi
fi
# Output
if [[ "$JSON_MODE" == "true" ]]; then
# Build JSON by hand to avoid jq dependency
# Arrays rendered as newline-joined strings for portability (easier ingestion).
status="fail"
exit_code=1
if [[ "$IMPORT_OK" == "true" ]]; then status="pass"; exit_code=0; fi
emit_json \
"\"status\":\"$status\",\
\"exit_code\":$exit_code,\
\"os\":{\"name\":\"$(json_escape "$OS_NAME")\",\"version\":\"$(json_escape "$OS_VERSION")\",\"id\":\"$(json_escape "$OS_ID")\",\"version_id\":\"$(json_escape "$OS_VERSION_ID")\"},\
\"python\":{\"path\":\"$(json_escape "$PY_EXEC")\",\"version\":\"$(json_escape "$PY_VERSION")\"},\
\"selinux_import\":{\"ok\":$IMPORT_OK,\"raw\":\"$(json_escape "$IMPORT_OUT")\"},\
\"rpm\":{\"found\":$RPM_FOUND,\"packages\":\"$(json_escape "$RPM_LIST")\",\"files_hint\":\"$(json_escape "$RPM_PYSELINUX_FILES")\"},\
\"python_sys_path\":\"$(json_escape "$SYS_PATH")\",\
\"platform_python\":{\"exists\":$PLATFORM_PY_EXISTS,\"path\":\"$(json_escape "$PLATFORM_PY")\",\"version\":\"$(json_escape "$PLATFORM_PY_VERSION")\",\"selinux_import_ok\":$PLATFORM_IMPORT_OK},\
\"recommendation\":\"$(json_escape "$RECOMMENDATION")\""
# Preserve exit semantics for automation
if [[ "$IMPORT_OK" == "true" ]]; then exit 0; fi
exit 1
fi
# Human output
echo "YugabyteDB Python SELinux Binding Check"
hr
echo "OS:"
echo " NAME=$OS_NAME"
echo " VERSION=$OS_VERSION"
echo " ID=$OS_ID"
echo " VERSION_ID=$OS_VERSION_ID"
hr
echo "Python:"
echo " python3 path: $PY_EXEC"
echo " python3 --version: $PY_VERSION"
hr
echo "SELinux import test (python3 -c 'import selinux'):"
if [[ "$IMPORT_OK" == "true" ]]; then
echo " β
PASS"
else
echo " β FAIL"
echo " $IMPORT_OUT"
fi
hr
echo "SELinux Python RPM packages (best-effort):"
if [[ -n "$RPM_LIST" ]]; then
echo "$RPM_LIST" | sed 's/^/ /'
else
echo " (none detected by name)"
fi
hr
echo "platform-python (if present):"
if [[ "$PLATFORM_PY_EXISTS" == "true" ]]; then
echo " path: $PLATFORM_PY"
echo " version: $PLATFORM_PY_VERSION"
echo " import selinux: $PLATFORM_IMPORT_OK"
else
echo " not found"
fi
hr
echo "Recommendation:"
echo "$RECOMMENDATION"
hr
if [[ "$IMPORT_OK" == "true" ]]; then exit 0; fi
exit
βΆοΈ Install and run the script
chmod +x yb_python_selinux_check.sh
Human-readable mode (default):
./yb_python_selinux_check.sh
JSON mode (automation / CI):
./yb_python_selinux_check.sh --json
Strict mode (package + behavior validation):
./yb_python_selinux_check.sh --strict
Strict + JSON (ideal for preflight gates)
./yb_python_selinux_check.sh --json --strict
π§ͺ Exit codes (stable and automation-friendly)
| Exit Code | Meaning |
|---|---|
0 |
PASS β requirement satisfied |
1 |
FAIL β requirement not met |
2 |
ERROR β python3 missing or runtime failure |
π§Ύ Human readable output example
YugabyteDB Python SELinux Binding Check
--------------------------------------------------------------------------------
OS:
NAME=AlmaLinux
VERSION=9.6 (Sage Margay)
ID=almalinux
VERSION_ID=9.6
--------------------------------------------------------------------------------
Python:
python3 path: /usr/bin/python3
python3 --version: Python 3.11.11
--------------------------------------------------------------------------------
SELinux import test (python3 -c 'import selinux'):
β FAIL
FAIL: ModuleNotFoundError("No module named 'selinux'")
--------------------------------------------------------------------------------
SELinux Python RPM packages (best-effort):
python3-libselinux-3.6-3.el9.x86_64
--------------------------------------------------------------------------------
platform-python (if present):
path: /usr/libexec/platform-python
version: Python 3.9.21
import selinux: true
--------------------------------------------------------------------------------
Recommendation:
FAIL: Active python3 cannot import selinux, but platform-python can.
Fix: Align Yugabyte tooling to use platform/system Python, or align python3 back to the distro Python version (common on EL8/EL9).
--------------------------------------------------------------------------------
π§Ύ JSON output fields Example
{"status":"fail","exit_code":1,"os":{"name":"AlmaLinux","version":"9.6 (Sage Margay)","id":"almalinux","version_id":"9.6"},"python":{"path":"/usr/bin/python3","version":"Python 3.11.11"},"selinux_import":{"ok":false,"raw":"FAIL: ModuleNotFoundError(\"No module named 'selinux'\")"},"rpm":{"found":true,"packages":"python3-libselinux-3.6-3.el9.x86_64","files_hint":"/usr/lib64/python3.9/site-packages/_selinux.cpython-39-x86_64-linux-gnu.so\n/usr/lib64/python3.9/site-packages/selinux\n/usr/lib64/python3.9/site-packages/selinux-3.6-py3.9.egg-info\n/usr/lib64/python3.9/site-packages/selinux-3.6-py3.9.egg-info/PKG-INFO\n/usr/lib64/python3.9/site-packages/selinux-3.6-py3.9.egg-info/SOURCES.txt\n/usr/lib64/python3.9/site-packages/selinux-3.6-py3.9.egg-info/dependency_links.txt\n/usr/lib64/python3.9/site-packages/selinux-3.6-py3.9.egg-info/installed-files.txt\n/usr/lib64/python3.9/site-packages/selinux-3.6-py3.9.egg-info/top_level.txt\n/usr/lib64/python3.9/site-packages/selinux/__init__.py\n/usr/lib64/python3.9/site-packages/selinux/__pycache__\n/usr/lib64/python3.9/site-packages/selinux/__pycache__/__init__.cpython-39.opt-1.pyc\n/usr/lib64/python3.9/site-packages/selinux/__pycache__/__init__.cpython-39.pyc\n/usr/lib64/python3.9/site-packages/selinux/_selinux.cpython-39-x86_64-linux-gnu.so\n/usr/lib64/python3.9/site-packages/selinux/audit2why.cpython-39-x86_64-linux-gnu.so"},"python_sys_path":"\n/usr/lib64/python311.zip\n/usr/lib64/python3.11\n/usr/lib64/python3.11/lib-dynload\n/usr/local/lib64/python3.11/site-packages\n/usr/local/lib/python3.11/site-packages\n/usr/lib64/python3.11/site-packages\n/usr/lib/python3.11/site-packages\n/usr/lib/python3.11/site-packages/setuptools-44.1.1-py3.11.egg","platform_python":{"exists":true,"path":"/usr/libexec/platform-python","version":"Python 3.9.21","selinux_import_ok":true},"recommendation":"FAIL: Active python3 cannot import selinux, but platform-python can.\nFix: Align Yugabyte tooling to use platform/system Python, or align python3 back to the distro Python version (common on EL8/EL9)."}
This is ideal for:
β CI/CD pipelines
β Bulk node validation
β YBA preflight gating
β Compliance reporting
β οΈ About --strict mode
By default, the script is behavior-based:
- If
import selinuxworks β PASS
With --strict, it becomes policy-based:
- PASS only if:
- 1.
import selinuxworks and - 2. A recognized SELinux Python RPM is installed (
python3-libselinuxorlibselinux-python)
- 1.
This is especially useful in regulated environments where βit worksβ isnβt sufficient without package-level proof.
π Common fixes when the check fails
If the script reports a failure, the recommendation section will usually point to one of these actions:
β Use the OS/platform Python (common on EL8/EL9)
β Align
python3back to the distro Pythonβ Install SELinux bindings that match the active Python version
The script intentionally does not auto-fix anything, making it safe to run in production environments.
π The fixe for my check fail
For the Python issue idenitified on my node, the fix is straightforward because alternatives is already configured. Switching the python3 alternative back to Python 3.9 resolves it.
sudo alternatives --set python3 /usr/bin/python3.9
python3 --version
python3 -c "import selinux; print('PASS: selinux import works')"
After running the commands above and re-executing the validation script, the check now reports a clean PASS.
YugabyteDB Python SELinux Binding Check
--------------------------------------------------------------------------------
OS:
NAME=AlmaLinux
VERSION=9.6 (Sage Margay)
ID=almalinux
VERSION_ID=9.6
--------------------------------------------------------------------------------
Python:
python3 path: /usr/bin/python3
python3 --version: Python 3.9.21
--------------------------------------------------------------------------------
SELinux import test (python3 -c 'import selinux'):
β
PASS
--------------------------------------------------------------------------------
SELinux Python RPM packages (best-effort):
python3-libselinux-3.6-3.el9.x86_64
--------------------------------------------------------------------------------
platform-python (if present):
path: /usr/libexec/platform-python
version: Python 3.9.21
import selinux: true
--------------------------------------------------------------------------------
Recommendation:
PASS: Active python3 can import selinux. Node meets requirement.
--------------------------------------------------------------------------------
β Conclusion
The Python SELinux binding requirement is a small detail with a big impact. When itβs missing, or mismatched, it can silently derail YugabyteDB Anywhere provisioning and lead to frustrating, hard-to-diagnose failures.
The key takeaway is simple:
- Itβs not enough for SELinux bindings to be installed. They must match the Python version that YugabyteDB tooling actually uses.
By validating this upfront, especially with JSON or strict mode, you can catch issues early, automate preflight checks, and ensure YugabyteDB deployments are smooth, predictable, and repeatable.
Have Fun!
