首页 > 编程语言 >python部署至k8s解决方案

python部署至k8s解决方案

时间:2024-05-16 21:42:35浏览次数:23  
标签:__ python 解决方案 self cache namespace url ._ k8s

前言

最近做了一个全文检索的项目,项目之前的架子是别人搭建的,部署方式是docker-compose,到后期这个同事基本上不参与了,后面发布测试的时候,我们觉得这种方式不适合测试环境和线上发版(当然也可能是我们不熟悉,有点不专业了),于是就在他开发的基础上,做了一些调整:

  • 修改Dockerfile:把依赖打进基础镜像中,他之前的基础镜像是ubuntu,然后安装一堆依赖
  • 修改配置方式:我们改成apollo获取,apollo的地址作为环境变量,通过k8s配置。改的时候才发现es的配置竟然是写死的

下面给各位小伙伴分享下具体的优化过程。

解决过程

构建镜像

之前的基础镜像是这样的,很臃肿,也很繁琐:

优化之后我们分为两步,第一步先打基础镜像,Dockerfile是这样写的:

# 基于pyhon基础镜像
FROM python:3.10-slim

RUN pip install uvicorn -i https://mirrors.aliyun.com/pypi/simple --trusted-host mirrors.aliyun.com --no-cache-dir
RUN pip install fastapi -i https://mirrors.aliyun.com/pypi/simple --trusted-host mirrors.aliyun.com --no-cache-dir

# The following command must be executed in service container, not when in building image!
RUN pip install elasticsearch==6.3 -i https://mirrors.aliyun.com/pypi/simple --trusted-host mirrors.aliyun.com --no-cache-dir
RUN pip install pandas -i https://mirrors.aliyun.com/pypi/simple --trusted-host mirrors.aliyun.com --no-cache-dir
RUN pip install uuid -i https://mirrors.aliyun.com/pypi/simple --trusted-host mirrors.aliyun.com --no-cache-dir
RUN pip install joblib -i https://mirrors.aliyun.com/pypi/simple --trusted-host mirrors.aliyun.com --no-cache-dir

这里依赖的是python-slim,这个版本的好处是体积小,如果不加slim打出来有好几GB,其余的都是项目的依赖。然后把这个打成一个基础镜像,在我们项目中引用这个镜像:

FROM syske.hubor.cn/common/semantic_search_base:1.0.2
# 创建code文件夹
RUN mkdir /semantic_search
# 将run.sh脚本复制到code文件夹下
COPY ./run.sh /semantic_search
# 将arduino-index.py脚本复制到code文件夹下
COPY ./index.py /semantic_search
COPY ./mylog.py /semantic_search
COPY ./retrieve.py /semantic_search
COPY ./config.py /semantic_search
COPY ./api_search.py /semantic_search

COPY ./searcher /semantic_search/searcher

COPY ./apollo /semantic_search/apollo

# 设置code文件夹为工作目录
WORKDIR /semantic_search

RUN cd /semantic_search

EXPOSE 8989
# 执行启动命令
CMD ["/bin/bash", "run.sh"]

我们的run.sh中写了项目的启动命令:

uvicorn api_search:app --port 8989 --host 0.0.0.0 --workers 2

至此,项目的镜像算是构建完了,然后开始接入apollo的改造

引入Apollo

首先,我们需要引入Apollo的客户端,这里我图方便直接从其他项目复制了,这个客户端代码很简单,就是通过requests来调用apollo的接口获取配置信息,具体我没有做深入研究,我只把apollo的地址和namespace透传进来,方便调用。

import json
import logging
import sys
import threading
import time
import requests

class ApolloClient(object):
    def __init__(self, app_id, cluster='default', config_server_url='http://localhost:8080', interval=60, ip=None):
        self.config_server_url = config_server_url
        self.appId = app_id
        self.cluster = cluster
        self.timeout = 60
        self.interval = interval
        self.init_ip(ip)
        self._stopping = False
        self._cache = {}
        self._notification_map = {'application': -1}

    def init_ip(self, ip):
        if ip:
            self.ip = ip
        else:
            import socket
            try:
                s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                s.connect(('8.8.8.8', 53))
                ip = s.getsockname()[0]
            finally:
                s.close()
            self.ip = ip

    def get_value(self, key, default_val=None, namespace='application', auto_fetch_on_cache_miss=False):
        if namespace not in self._notification_map:
            self._notification_map[namespace] = -1
            logging.getLogger(__name__).info("Add namespace '%s' to local notification map", namespace)

        if namespace not in self._cache:
            self._cache[namespace] = {}
            logging.getLogger(__name__).info("Add namespace '%s' to local cache", namespace)
            self._long_poll()
        if key in self._cache[namespace]:
            return self._cache[namespace][key]
        else:
            if auto_fetch_on_cache_miss:
                return self._cached_http_get(key, default_val, namespace)
            else:
                return default_val

    def start(self):
        if len(self._cache) == 0:
            self._long_poll()
        t = threading.Thread(target=self._listener)
        t.start()

    def stop(self):
        self._stopping = True
        logging.getLogger(__name__).info("Stopping listener...")

    def _cached_http_get(self, key, default_val, namespace='application'):
        url = '{}/configfiles/json/{}/{}/{}?ip={}'.format(self.config_server_url, self.appId, self.cluster, namespace, self.ip)
        r = requests.get(url)
        if r.ok:
            data = r.json()
            self._cache[namespace] = data
            logging.getLogger(__name__).info('Updated local cache for namespace %s', namespace)
        else:
            data = self._cache[namespace]

        if key in data:
            return data[key]
        else:
            return default_val

    def _uncached_http_get(self, namespace='application'):
        url = '{}/configs/{}/{}/{}?ip={}'.format(self.config_server_url, self.appId, self.cluster, namespace, self.ip)
        r = requests.get(url)
        if r.status_code == 200:
            data = r.json()
            self._cache[namespace] = data['configurations']
            logging.getLogger(__name__).info('Updated local cache for namespace %s release key %s: %s',
                                             namespace, data['releaseKey'],
                                             repr(self._cache[namespace]))
    def _long_poll(self):
        url = '{}/notifications/v2'.format(self.config_server_url)
        notifications = []
        for key in self._notification_map:
            notification_id = self._notification_map[key]
            notifications.append({
                'namespaceName': key,
                'notificationId': notification_id
            })

        r = requests.get(url=url, params={
            'appId': self.appId,
            'cluster': self.cluster,
            'notifications': json.dumps(notifications, ensure_ascii=False)
        }, timeout=self.timeout)

        logging.getLogger(__name__).debug('Long polling returns %d: url=%s', r.status_code, r.request.url)

        if r.status_code == 304:
            # no change, loop
            logging.getLogger(__name__).debug('No change, loop...')
            return

        if r.status_code == 200:
            data = r.json()
            for entry in data:
                ns = entry['namespaceName']
                nid = entry['notificationId']
                logging.getLogger(__name__).info("%s has changes: notificationId=%d", ns, nid)
                self._uncached_http_get(ns)
                self._notification_map[ns] = nid
        else:
            logging.getLogger(__name__).warn('Sleep...')
            time.sleep(self.timeout)

    def _listener(self):
        logging.getLogger(__name__).info('Entering listener loop...')
        while not self._stopping:
            self._long_poll()
            time.sleep(self.interval)
        logging.getLogger(__name__).info("Listener stopped!")

之后在原有配置代码中使用apollo客户端:

from .pyapollo import ApolloClient


class ApolloData(object):
    
    def __init__(self, config_server_url= "https://apollo.coolcollege.cn:8080", namespace = 'application'): 
                
        if namespace == None:
            namespace = 'application'
        app_id = "semantic-search"
        client = ApolloClient(app_id=app_id, config_server_url=config_server_url, cluster='default', interval=10)
        #如果是关联空间的值,必须使用namespace ,指定空间名称
        # get config from apollo
        self.es_host = client.get_value("ES.HOST", namespace=namespace)
        self.es_pwd = client.get_value("ES.PASS", namespace=namespace)
        self.es_user = client.get_value("ES.USER", namespace=namespace)
        
        
    def get_es_host(self):
        return self.es_host
        
    def get_es_user(self):
        return self.es_user
        
    def get_es_pwd(self):
        return self.es_pwd
        

这两个文件在同一个目录下,在具体使用配置的地方,获取apollo的环境变量,这样我们就可以获取apollo的配置了:

apollo_meta_config_server_url = os.getenv("config_server_url")
namespace = os.getenv("namespace")
apolloData = ApolloData(config_server_url=apollo_meta_config_server_url, namespace=namespace)
es_host = apolloData.get_es_host()

至此,配置代码层面的改造业也完成了,下面看下k8s的操作。

k8s设置环境变量

直接在环境变量那里增加对应的配置,并指定环境变量的值,这样项目运行时就可以通过os.getenv()方法来获取对应的配置

至此,整个流程搞完了。

结语

其实,以上内容在看的时候,感觉没有特别难,包括我现在看也是这样的感觉,但最开始我没有任何思路,一边找方案,一边各种尝试,用了各种apollo的客户端,踩了很多坑,折腾了好久,最终发现都不行,过程也是很艰辛的,当然最后几经尝试,问题终于一个一个被解决,当这条路跑通的时候,那会也是真的开心,虽然那天加班了,但是解决问题的感觉还是很爽的,这可能就是我觉得工作最有趣的地方~

所以说,问题本身既是挑战,也是快乐,希望你也能享受解决问题的过程,找到自己的快乐源泉

标签:__,python,解决方案,self,cache,namespace,url,._,k8s
From: https://www.cnblogs.com/caoleiCoding/p/18196798

相关文章

  • Python基础01
    Python基础01学习视频https://www.bilibili.com/video/BV1qW4y1a7fU软件pycharm通过快捷键快速设置字体大小常用快捷键ctrl+alt+s:打开软件设置ctrl+d:复制当前行代码shift+alt+上\下:将当前行代码上移或下移crtl+shift+f10:运行当前代......
  • 解决Python执行命令时路径空格引发的困扰
    哈喽,大家好,我是木头左!在编程实践中,常常需要通过Python执行系统命令,这包括调用外部程序、脚本或是其他命令行工具。然而,一个看似简单却常被忽视的细节——文件路径中的空格,可能会导致程序意外崩溃或行为异常。本篇文章将深入探讨当路径中包含空格时,如何在Python中正确执行命令,以......
  • Python查询PostgreSQL数据库
    哈喽,大家好,我是木头左!Python与PostgreSQL的连接需要了解如何在Python中连接到PostgreSQL数据库。这通常涉及到使用一个库,如psycopg2,它是Python中用于PostgreSQL的最流行的适配器。安装psycopg2非常简单,可以通过pip进行安装:pipinstallpsycopg2安装完成后,可以使用以下代码......
  • python 面向对象(进阶篇)
    python面向对象(进阶篇) 上一篇《Python面向对象(初级篇)》文章介绍了面向对象基本知识:面向对象是一种编程方式,此编程方式的实现是基于对 类 和 对象 的使用类是一个模板,模板中包装了多个“函数”供使用(可以讲多函数中公用的变量封装到对象中)对象,根据模板创建的实例(即......
  • 实战项目-基于K8s平台进行wordpress建站
    (240516更新)基本信息系统:Debian12.05k8s版本:1.30环境:虚拟机序号IP地址域名主机名1192.168.100.12k8s-master.$yourname.comk8s-master2192.168.100.15k8s-node1.yourname.comk8s-node13192.168.100.16k8s-node2.yourname.comk8s-node24192.168......
  • 利用python脚本批量读取当前目录下所有excle表格中特定的单元格内容
    利用python脚本批量读取当前目录下所有excle表格中特定的单元格内容importosfromopenpyxlimportload_workbook#设置要读取的单元格地址cell_address='N18'#遍历当前目录下的所有文件forfilenameinos.listdir('.'):iffilename.endswith(......
  • 利用python脚本批量替换当前目录下所有excle表格中特定的单元格内容
    利用python脚本批量替换当前目录下所有excle表格中特定的单元格内容#导入os模块,用于文件和目录操作importos#导入openpyxl库中的load_workbook函数,用于加载Excel文件fromopenpyxlimportload_workbook#定义一个函数replace_cell_content,用于替换Exc......
  • python打包在32位无法运行问题
    真不想吐槽现在的技术越高级越烂的一批尤其是开发工具win1064位python64位开发pyinsataller打包后不能在32位上运行别折腾重新安装python32位测试安装python3.12.232位竟然不能安装pandas(见鬼去吧)重新安装python3.8.10提示不能用在xp上,也可以接受了.再安装依赖包,没......
  • 上百页html生成pdf解决方案(bookjs-easy)简洁完整版(包含接收服务端返回路径参数)
    依靠1:客户端插件 bookjs-easy(点击直接跳转官网)2:服务端插件screenshot-api-server实测105页的pdf,生成耗时40s左右,文件大小16MB项目需求:生成一个上百页的pdf,这个pdf包含表格、折线图、图片等,且横竖幅页面交叉 bookjs-easy官网的文档对于第一次看的人来说并不友好(建议第......
  • docker构建python镜像
    执行步骤如下:1、使用Dockerfile创建有 接口自动化框架执行环境 的python镜像2、根据python镜像,编写shell脚本创建容器3、拉取git(接口自动化框架)上的代码,使用python执行接口自动化框架4、执行完成之后自动销毁容器使用Dockfile创建python镜像前置:需创建目录py38dockerfi......