问题背景

最近在 CentOS 7 上编译安装 Python 3.9,按照官方推荐的方式执行:

./configure --prefix=/usr/local/python3 --enable-optimizations
make -j$(nproc)
sudo make install

结果编译到一半,突然报错:

Could not import runpy module
Traceback (most recent call last):
  File "/opt/Python-3.10.0/Lib/runpy.py", line 15, in <module>
    import importlib.util
  File "/opt/Python-3.10.0/Lib/importlib/util.py", line 14, in <module>
    from contextlib import contextmanager
  File "/opt/Python-3.10.0/Lib/contextlib.py", line 4, in <module>
    import _collections_abc
SystemError: <built-in function compile> returned NULL without setting an exception
generate-posix-vars failed
make: *** [pybuilddir.txt] Error 1

折腾了半天,最终发现 --enable-optimizations 是罪魁祸首!删掉这个参数后,编译顺利通过。


为什么 --enable-optimizations 会导致编译失败?

--enable-optimizations 是 Python 官方推荐的编译选项,用于启用 PGO(Profile-Guided Optimization)优化,让 Python 运行更快。但它在某些旧系统(如 CentOS 7)上可能会引发问题,原因包括:

  1. GCC 版本过低
    CentOS 7 默认的 GCC 4.8.5 对 PGO 支持不完善,而 Python 3.9+ 的优化编译需要更高版本 GCC(建议 ≥ 8)。
  2. 依赖库不兼容
    PGO 优化会额外编译测试用例,如果系统缺少某些依赖(如 zlib-devellibffi-devel),可能导致 runpy 等核心模块编译失败。
  3. 内存不足
    PGO 优化会占用更多内存,如果服务器内存较小(< 2GB),可能导致编译崩溃。

正确编译 Python 3.9 的步骤

1. 安装编译依赖

sudo yum install -y gcc make openssl-devel bzip2-devel libffi-devel zlib-devel \
ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel xz-devel

2. 下载 Python 3.9 源码

wget https://www.python.org/ftp/python/3.9.16/Python-3.9.16.tgz
tar xzf Python-3.9.16.tgz
cd Python-3.9.16

3. 配置编译选项(关键!不要加 --enable-optimizations

./configure --prefix=/usr/local/python3  # 不加 --enable-optimizations

4. 编译并安装

make -j$(nproc)        # 并行编译加速
sudo make install      # 安装到 /usr/local/python3

5. 验证安装

/usr/local/python3/bin/python3 --version
# 应输出: Python 3.9.16

如果仍想启用 PGO 优化怎么办?

如果确实需要优化性能,可以尝试以下方法:

方案 1:升级 GCC

sudo yum install centos-release-scl
sudo yum install devtoolset-9
scl enable devtoolset-9 bash
gcc --version  # 确认版本 ≥ 9

然后重新编译:

./configure --prefix=/usr/local/python3 --enable-optimizations
make -j$(nproc)

方案 2:改用 --enable-shared

如果 PGO 仍然失败,可以改用动态链接库优化:

./configure --prefix=/usr/local/python3 --enable-shared
make -j$(nproc)

总结

编译选项适用场景风险
--prefix=/usr/local/python3默认编译,兼容性最好✅ 推荐
--enable-shared支持动态链接库⚠️ 需设置 LD_LIBRARY_PATH
--enable-optimizations高性能优化(PGO)❌ 旧系统易失败

最终建议

  • 生产环境 → 直接去掉 --enable-optimizations,优先保证稳定性。
  • 开发环境 → 可尝试升级 GCC 后再启用 PGO。

希望这篇踩坑记录能帮你少走弯路!如果有其他问题,欢迎留言讨论。 🚀

问题描述

最近在服务器上安装了轻量级 Kubernetes 发行版 K3s,并部署了一个简单的 Nginx 服务,希望通过 http://服务器IP:80 访问。然而,浏览器始终返回 404 Not Found,而 curl localhost:80 却能正常返回 Nginx 的欢迎页面。

经过排查,发现 K3s 默认安装的 Traefik 组件劫持了 80 端口,导致 Nginx 虽然监听 80 端口,但外部请求被 Traefik 拦截,最终返回 404。

问题分析

1. 为什么 Nginx 的 80 端口“失效”?

  • ss -tulnp 显示 Nginx 在监听 80 端口,但外部请求被 Traefik 拦截。
  • Traefik 是 K3s 默认的 Ingress 控制器,它会自动绑定 80 和 443 端口,接管所有 HTTP/HTTPS 流量。
  • Nginx 虽然运行,但 Traefik 优先处理请求,由于没有匹配的 Ingress 规则,返回 404。

2. Traefik 的工作原理

Traefik 是一个 云原生反向代理和 Ingress 控制器,主要功能:

  1. 自动发现 Kubernetes 服务,并根据 Ingress 规则路由流量。
  2. 默认监听 80(HTTP)和 443(HTTPS),拦截所有进入集群的 Web 请求。
  3. 动态配置路由,无需重启即可生效。

在 K3s 中,Traefik 会:

  • 自动创建 LoadBalancer 类型的 Service,绑定 80/443 端口。
  • 如果没有配置 Ingress 规则,访问任何路径都会返回 404

解决方案

方法 1:禁用 Traefik(推荐)

如果不需要 Traefik,可以在安装 K3s 时直接禁用:

curl -sfL https://get.k3s.io | sh -s - --disable traefik

这样 K3s 不会安装 Traefik,80 端口由 Nginx 直接接管。

方法 2:配置 Traefik 路由(适合需要 Ingress 的场景)

如果仍想使用 Traefik,可以创建 Ingress 规则,将流量正确路由到 Nginx:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-ingress
spec:
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: nginx-service  # 替换为你的 Nginx Service 名称
            port:
              number: 80

然后应用配置:

kubectl apply -f ingress.yaml

最终解决

由于我的需求只是运行一个简单的 Nginx,不需要 Kubernetes Ingress 功能,因此选择 卸载并重装 K3s,禁用 Traefik

# 卸载 K3s
/usr/local/bin/k3s-uninstall.sh

# 重新安装,禁用 Traefik
curl -sfL https://get.k3s.io | sh -s - --disable traefik

安装完成后,Nginx 成功接管 80 端口,浏览器访问正常!

总结

  • K3s 默认安装 Traefik,它会劫持 80/443 端口,导致 Nginx 无法直接接收外部请求。
  • 解决方案

    • 如果不需要 Ingress,安装时加 --disable traefik
    • 如果需要 Traefik,必须配置 Ingress 规则,否则会返回 404。
  • K3s 适合轻量级 Kubernetes,但需注意默认组件的影响

希望这篇博文能帮你解决类似问题!🚀

/**

  • HTTP客户端工具类
  • 特性:
    1. 连接池管理优化
    1. 空闲连接自动清理
    1. 自动重试机制
    1. 更安全的TLS配置
    1. 资源自动释放
      */

public class HttpClientUtil {

private static final Logger logger = LoggerFactory.getLogger(HttpClientUtil.class);

public static final String METHOD_POST = "POST";
public static final String METHOD_GET = "GET";
public static final String DEFAULT_CHARSET = "UTF-8";
public static final String DEFAULT_CONTENT_TYPE = "application/json;charset=UTF-8";

// 超时配置
public static final int DEFAULT_CONNECT_TIMEOUT = 10000;         // 10秒连接超时
public static final int DEFAULT_READ_TIMEOUT = 30000;            // 30秒读取超时
public static final int DEFAULT_CONNECT_REQUEST_TIMEOUT = 10000; // 10秒请求超时

// 连接池配置
private static final int MAX_TOTAL = 200;         // 最大连接数
private static final int MAX_PER_ROUTE = 100;     // 每路由最大连接数
private static final int VALIDATE_AFTER_INACTIVITY = 5000; // 5秒空闲后验证
private static final int EVICT_IDLE_CONNECTION_TIME = 30; // 30秒空闲连接清理
private static final int CONNECTION_TTL = 60;     // 连接存活时间(秒)
private static final int MAX_RETRIES = 3;         // 最大重试次数

private static final RequestConfig requestConfig;
private static final PoolingHttpClientConnectionManager connectionManager;
private static final CloseableHttpClient httpClient;
private static final CloseableHttpClient httpsClient;

static {
    // 初始化SSLContext
    SSLContext sslContext = initSSLContext();

    // 配置请求参数
    requestConfig = RequestConfig.custom()
            .setSocketTimeout(DEFAULT_READ_TIMEOUT)
            .setConnectTimeout(DEFAULT_CONNECT_TIMEOUT)
            .setConnectionRequestTimeout(DEFAULT_CONNECT_REQUEST_TIMEOUT)
            .build();

    // 配置连接池
    Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
            .register("http", PlainConnectionSocketFactory.getSocketFactory())
            .register("https", new SSLConnectionSocketFactory(
                    sslContext,
                    new String[]{"TLSv1.2", "TLSv1.3"}, // 安全协议
                    null,
                    NoopHostnameVerifier.INSTANCE))
            .build();

    connectionManager = new PoolingHttpClientConnectionManager(
            socketFactoryRegistry,
            null,
            null,
            null,
            VALIDATE_AFTER_INACTIVITY,
            TimeUnit.MILLISECONDS);

    connectionManager.setMaxTotal(MAX_TOTAL);
    connectionManager.setDefaultMaxPerRoute(MAX_PER_ROUTE);

    // 创建HttpClient
    HttpClientBuilder builder = HttpClients.custom()
            .setDefaultRequestConfig(requestConfig)
            .setConnectionManager(connectionManager)
            .evictExpiredConnections()
            .evictIdleConnections(EVICT_IDLE_CONNECTION_TIME, TimeUnit.SECONDS)
            .setConnectionTimeToLive(CONNECTION_TTL, TimeUnit.SECONDS)
            .disableAutomaticRetries()
            .setRetryHandler((exception, executionCount, context) -> {
                if (executionCount > MAX_RETRIES) {
                    return false;
                }
                if (exception instanceof org.apache.http.NoHttpResponseException) {
                    return true;
                }
                return false;
            });

    httpClient = builder.build();
    httpsClient = builder.build();
}

private static SSLContext initSSLContext() {
    try {
        return new SSLContextBuilder()
                .loadTrustMaterial(null, (X509Certificate[] chain, String authType) -> true)
                .build();
    } catch (KeyStoreException | NoSuchAlgorithmException | KeyManagementException ex) {
        logger.error("Failed to initialize SSL context", ex);
        throw new RuntimeException("Failed to initialize SSL context", ex);
    }
}

private HttpClientUtil() {
    // 私有构造器防止实例化
}

/**
 * 执行GET请求
 * @param url 请求URL
 * @param headers 请求头
 * @return 响应内容
 */
public static String get(String url, Map<String, String> headers) {
    HttpGet request = new HttpGet(url);
    try {
        wrapHeader(request, headers);
        return isHttps(url) ? execute(request, httpsClient) : execute(request, httpClient);
    } catch (Exception e) {
        logger.error("GET request failed: {}", e.getMessage(), e);
        throw new RuntimeException("HTTP GET request failed", e);
    } finally {
        request.releaseConnection();
    }
}

/**
 * 执行POST请求(JSON body)
 * @param url 请求URL
 * @param body 请求体(JSON)
 * @param headers 请求头
 * @return 响应内容
 */
public static String postBody(String url, String body, Map<String, String> headers) {
    HttpPost request = new HttpPost(url);
    try {
        wrapHeader(request, headers);
        wrapStringEntity(request, body);
        return isHttps(url) ? execute(request, httpsClient) : execute(request, httpClient);
    } catch (Exception e) {
        logger.error("POST request failed: {}", e.getMessage(), e);
        throw new RuntimeException("HTTP POST request failed", e);
    } finally {
        request.releaseConnection();
    }
}

/**
 * 执行POST请求(form表单)
 * @param url 请求URL
 * @param params 表单参数
 * @param headers 请求头
 * @return 响应内容
 */
public static String postForm(String url, Map<String, String> params, Map<String, String> headers) {
    HttpPost request = new HttpPost(url);
    try {
        wrapHeader(request, headers);
        wrapFormEntity(request, params);
        return isHttps(url) ? execute(request, httpsClient) : execute(request, httpClient);
    } catch (Exception e) {
        logger.error("POST form request failed: {}", e.getMessage(), e);
        throw new RuntimeException("HTTP POST form request failed", e);
    } finally {
        request.releaseConnection();
    }
}

/**
 * 清理连接池中的无效连接
 */
public static void cleanup() {
    connectionManager.closeExpiredConnections();
    connectionManager.closeIdleConnections(0, TimeUnit.SECONDS);
    logger.debug("HTTP connection pool cleaned up");
}

private static String execute(HttpRequestBase request, CloseableHttpClient httpClient) {
    String respJson = null;
    int retryCount = 0;

    while (retryCount <= MAX_RETRIES) {
        try (CloseableHttpResponse response = httpClient.execute(request)) {
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                HttpEntity httpEntity = response.getEntity();
                respJson = EntityUtils.toString(httpEntity, DEFAULT_CHARSET);
                EntityUtils.consume(httpEntity);
                break;
            }
        } catch (IOException e) {
            retryCount++;
            if (retryCount > MAX_RETRIES) {
                logger.error("Request failed after {} retries: {}", MAX_RETRIES, e.getMessage());
                throw new RuntimeException("HTTP request failed after retries", e);
            }

            try {
                // 指数退避
                Thread.sleep((long) Math.pow(2, retryCount) * 100);
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
                throw new RuntimeException("Interrupted during retry", ie);
            }

            // 清理无效连接
            cleanup();
        }
    }
    return respJson;
}

private static void wrapHeader(HttpRequestBase request, Map<String, String> headers) {
    if (headers != null) {
        for (Map.Entry<String, String> entry : headers.entrySet()) {
            request.addHeader(entry.getKey(), entry.getValue());
        }
    }
}

private static void wrapStringEntity(HttpPost request, String body) {
    if (body != null) {
        StringEntity entity = new StringEntity(body, DEFAULT_CHARSET);
        entity.setContentEncoding(DEFAULT_CHARSET);
        entity.setContentType(DEFAULT_CONTENT_TYPE);
        request.setEntity(entity);
    }
}

private static void wrapFormEntity(HttpPost request, Map<String, String> params) throws UnsupportedEncodingException {
    if (params != null) {
        List<NameValuePair> nvps = new ArrayList<>();
        for (Map.Entry<String, String> entry : params.entrySet()) {
            nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
        }
        request.setEntity(new UrlEncodedFormEntity(nvps, DEFAULT_CHARSET));
    }
}

private static boolean isHttps(String url) {
    return StringUtils.startsWithIgnoreCase(url, "https:");
}

}