栏目分类:
子分类:
返回
终身学习网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
终身学习网 > IT > 前沿技术 > 云计算 > Docker/k8s

Airflow2.2.3 + Celery + MySQL 8构建一个健壮的分布式调度集群

Docker/k8s 更新时间:发布时间: 百科书网 趣学号



前面聊了Airflow基础架构??,以及又讲了如何在容器化内部署Airflow??,今天我们就再来看看如何通过Airflow和celery构建一个健壮的分布式调度集群。

1集群环境

同样是在Ubuntu 20.04.3 LTS机器上安装Airflow集群,这次我们准备三台同等配置服务器,进行测试,前篇文章??[1]中,我们已经在Bigdata1服务器上安装了airflow的所有组件,没看过的可以点击链接先看下之前的文章,现在只需要在其他两个节点安装worker组件即可。

  Bigdata1(A) Bigdata2(B) Bigdata3(C)
Webserver    
Scheduler    
Worker

在上篇文章中的docker-compose.yml中没有对部署文件以及数据目录进行的分离,这样在后期管理的时候不太方便,因此我们可以把服务停止后,将数据库以及数据目录与部署文件分开

部署文件:docker-compose.yaml/.env 存放在/apps/airflow目录下 MySQL以及配置文件: 放在/data/mysql airflow数据目录: 放在/data/airflow

这样拆分开就方便后期的统一管理了。

2部署worker服务

前期准备

  1. mkdir /data/airflow/{dags,plugins} -pv mkdir -pv /apps/airflow 
  2. mkdir -pv /logs/airflow 

worker的部署文件:

  1. --- version: '3' 
  2. x-airflow-common:   &airflow-common 
  3.   # In order to add custom dependencies or upgrade provider packages you can use your extended image.   # Comment the image line, place your Dockerfile in the directory where you placed the docker-compose.yaml 
  4.   # and uncomment the "build" line below, Then run `docker-compose build` to build the images.   image: ${AIRFLOW_IMAGE_NAME:-apache/airflow:2.2.3} 
  5.   # build: .   environment: 
  6.     &airflow-common-env     AIRFLOW__CORE__EXECUTOR: CeleryExecutor 
  7.     AIRFLOW__CORE__SQL_ALCHEMY_CONN: mysql+mysqldb://airflow:aaaa@$${MYSQL_HOST}:3306/airflow #修改MySQL对应的账号和密码     AIRFLOW__CELERY__RESULT_BACKEND: db+mysql://airflow:aaaa@$${MYSQL_HOST}:3306/airflow #修改MySQL对应的账号和密码 
  8.     AIRFLOW__CELERY__BROKER_URL: redis://:xxxx@$${REDIS_HOST}:7480/0 #修改Redis的密码     AIRFLOW__CORE__FERNET_KEY: '' 
  9.     AIRFLOW__CORE__DAGS_ARE_PAUSED_AT_CREATION: 'true'     AIRFLOW__CORE__LOAD_EXAMPLES: 'true' 
  10.     AIRFLOW__API__AUTH_BACKEND: 'airflow.api.auth.backend.basic_auth'     _PIP_ADDITIONAL_REQUIREMENTS: ${_PIP_ADDITIONAL_REQUIREMENTS:-} 
  11.   volumes:     - /data/airflow/dags:/opt/airflow/dags 
  12.     - /logs/airflow:/opt/airflow/logs     - /data/airflow/plugins:/opt/airflow/plugins 
  13.     - /data/airflow/airflow.cfg:/opt/airflow/airflow.cfg   user: "${AIRFLOW_UID:-50000}:0" 
  14.  services: 
  15.   airflow-worker:     <<: *airflow-common 
  16.     command: celery worker     healthcheck: 
  17.       test:         - "CMD-SHELL" 
  18.         - 'celery --app airflow.executors.celery_executor.app inspect ping -d "celery@$${HOSTNAME}"'       interval: 10s 
  19.       timeout: 10s       retries: 5 
  20.     environment:       <<: *airflow-common-env 
  21.       # Required to handle warm shutdown of the celery workers properly       # See https://airflow.apache.org/docs/docker-stack/entrypoint.html#signal-propagation 
  22.       DUMB_INIT_SETSID: "0"     restart: always 
  23.     hostname: bigdata-20-194 # 此处设置容器的主机名,便于在flower中查看是哪个worker     depends_on: 
  24.       airflow-init:         condition: service_completed_successfully 
  25.    airflow-init: 
  26.     <<: *airflow-common     entrypoint: /bin/bash 
  27.     # yamllint disable rule:line-length     command: 
  28.       - -c       - | 
  29.         function ver() {           printf "%04d%04d%04d%04d" $${1//./ } 
  30.         }         airflow_version=$$(gosu airflow airflow version) 
  31.         airflow_version_comparable=$$(ver $${airflow_version})         min_airflow_version=2.2.0 
  32.         min_airflow_version_comparable=$$(ver $${min_airflow_version})         if (( airflow_version_comparable < min_airflow_version_comparable )); then 
  33.           echo           echo -e "33[1;31mERROR!!!: Too old Airflow version $${airflow_version}!e[0m" 
  34.           echo "The minimum Airflow version supported: $${min_airflow_version}. Only use this or higher!"           echo 
  35.           exit 1         fi 
  36.         if [[ -z "${AIRFLOW_UID}" ]]; then           echo 
  37.           echo -e "33[1;33mWARNING!!!: AIRFLOW_UID not set!e[0m"           echo "If you are on Linux, you SHOULD follow the instructions below to set " 
  38.           echo "AIRFLOW_UID environment variable, otherwise files will be owned by root."           echo "For other operating systems you can get rid of the warning with manually created .env file:" 
  39.           echo "    See: https://airflow.apache.org/docs/apache-airflow/stable/start/docker.html#setting-the-right-airflow-user"           echo 
  40.         fi         one_meg=1048576 
  41.         mem_available=$$(($$(getconf _PHYS_PAGES) * $$(getconf PAGE_SIZE) / one_meg))         cpus_available=$$(grep -cE 'cpu[0-9]+' /proc/stat) 
  42.         disk_available=$$(df / | tail -1 | awk '{print $$4}')         warning_resources="false" 
  43.         if (( mem_available < 4000 )) ; then           echo 
  44.           echo -e "33[1;33mWARNING!!!: Not enough memory available for Docker.e[0m"           echo "At least 4GB of memory required. You have $$(numfmt --to iec $$((mem_available * one_meg)))" 
  45.           echo           warning_resources="true" 
  46.         fi         if (( cpus_available < 2 )); then 
  47.           echo           echo -e "33[1;33mWARNING!!!: Not enough CPUS available for Docker.e[0m" 
  48.           echo "At least 2 CPUs recommended. You have $${cpus_available}"           echo 
  49.           warning_resources="true"         fi 
  50.         if (( disk_available < one_meg * 10 )); then           echo 
  51.           echo -e "33[1;33mWARNING!!!: Not enough Disk space available for Docker.e[0m"           echo "At least 10 GBs recommended. You have $$(numfmt --to iec $$((disk_available * 1024 )))" 
  52.           echo           warning_resources="true" 
  53.         fi         if [[ $${warning_resources} == "true" ]]; then 
  54.           echo           echo -e "33[1;33mWARNING!!!: You have not enough resources to run Airflow (see above)!e[0m" 
  55.           echo "Please follow the instructions to increase amount of resources available:"           echo "   https://airflow.apache.org/docs/apache-airflow/stable/start/docker.html#before-you-begin" 
  56.           echo         fi 
  57.         mkdir -p /sources/logs /sources/dags /sources/plugins         chown -R "${AIRFLOW_UID}:0" /sources/{logs,dags,plugins} 
  58.         exec /entrypoint airflow version     # yamllint enable rule:line-length 
  59.     environment:       <<: *airflow-common-env 
  60.       _AIRFLOW_DB_UPGRADE: 'true'       _AIRFLOW_WWW_USER_CREATE: 'true' 
  61.       _AIRFLOW_WWW_USER_USERNAME: ${_AIRFLOW_WWW_USER_USERNAME:-airflow}       _AIRFLOW_WWW_USER_PASSWORD: ${_AIRFLOW_WWW_USER_PASSWORD:-airflow} 
  62.     user: "0:0"     volumes: 
  63.       - .:/sources  
  64.   airflow-cli:     <<: *airflow-common 
  65.     profiles:       - debug 
  66.     environment:       <<: *airflow-common-env 
  67.       CONNECTION_CHECK_MAX_COUNT: "0"     # Workaround for entrypoint issue. See: https://github.com/apache/airflow/issues/16252 
  68.     command:       - bash 
  69.       - -c       - airflow 

初始化检测,检查环境是否满足:

  1. cd /apps/ariflow/ echo -e "AIRFLOW_UID=$(id -u)" > .env # 注意,此处一定要保证AIRFLOW_UID是普通用户的UID,且保证此用户有创建这些持久化目录的权限 
  2. docker-compose up airflow-init 

如果数据库已经存在,初始化检测不影响已有的数据库,接下来就运行airflow-worker服务

  1. docker-compose up -d 



接下来,按照同样的方式在bigdata3节点上安装airflow-worker服务就可以了。部署完成之后,就可以通过flower查看broker的状态:



3持久化配置文件

大多情况下,使用airflow多worker节点的集群,我们就需要持久化airflow的配置文件,并且将airflow同步到所有的节点上,因此这里需要修改一下docker-compose.yaml中x-airflow-common的volumes,将airflow.cfg通过挂载卷的形式挂载到容器中,配置文件可以在容器中拷贝一份出来,然后在修改;

前期使用的时候,我们需要将docker-compose文件中的一些环境变量的值写入到airflow.cfg文件中,例如以下信息:

  1. [core] dags_folder = /opt/airflow/dags 
  2. hostname_callable = socket.getfqdn default_timezone = Asia/Shanghai # 修改时区 
  3. executor = CeleryExecutor sql_alchemy_conn = mysql+mysqldb://airflow:aaaa@$${MYSQL_HOST}:3306/airflow 
  4. sql_engine_encoding = utf-8 sql_alchemy_pool_enabled = True 
  5. sql_alchemy_pool_size = 5 sql_alchemy_max_overflow = 10 
  6. sql_alchemy_pool_recycle = 1800 sql_alchemy_pool_pre_ping = True 
  7. sql_alchemy_schema = parallelism = 32 
  8. max_active_tasks_per_dag = 16 dags_are_paused_at_creation = True 
  9. max_active_runs_per_dag = 16 load_examples = True 
  10. load_default_connections = True plugins_folder = /opt/airflow/plugins 
  11. execute_tasks_new_python_interpreter = False fernet_key = 
  12. donot_pickle = True dagbag_import_timeout = 30.0 
  13. dagbag_import_error_tracebacks = True dagbag_import_error_traceback_depth = 2 
  14. dag_file_processor_timeout = 50 task_runner = StandardTaskRunner 
  15. default_impersonation = security = 
  16. unit_test_mode = False enable_xcom_pickling = False 
  17. killed_task_cleanup_time = 60 dag_run_conf_overrides_params = True 
  18. dag_discovery_safe_mode = True default_task_retries = 0 
  19. default_task_weight_rule = downstream min_serialized_dag_update_interval = 30 
  20. min_serialized_dag_fetch_interval = 10 max_num_rendered_ti_fields_per_task = 30 
  21. check_slas = True xcom_backend = airflow.models.xcom.baseXCom 
  22. lazy_load_plugins = True lazy_discover_providers = True 
  23. max_db_retries = 3 hide_sensitive_var_conn_fields = True 
  24. sensitive_var_conn_names = default_pool_task_slot_count = 128 
  25. [logging] base_log_folder = /opt/airflow/logs 
  26. remote_logging = False remote_log_conn_id = 
  27. google_key_path = remote_base_log_folder = 
  28. encrypt_s3_logs = False logging_level = INFO 
  29. fab_logging_level = WARNING logging_config_class = 
  30. colored_console_log = True colored_log_format = [%%(blue)s%%(asctime)s%%(reset)s] {%%(blue)s%%(filename)s:%%(reset)s%%(lineno)d} %%(log_color)s%%(levelname)s%%(reset)s - %%(log_color)s%%(message)s%%(reset)s 
  31. colored_formatter_class = airflow.utils.log.colored_log.CustomTTYColoredFormatter log_format = [%%(asctime)s] {%%(filename)s:%%(lineno)d} %%(levelname)s - %%(message)s 
  32. simple_log_format = %%(asctime)s %%(levelname)s - %%(message)s task_log_prefix_template = 
  33. log_filename_template = {{ ti.dag_id }}/{{ ti.task_id }}/{{ ts }}/{{ try_number }}.log log_processor_filename_template = {{ filename }}.log 
  34. dag_processor_manager_log_location = /opt/airflow/logs/dag_processor_manager/dag_processor_manager.log task_log_reader = task 
  35. extra_logger_names = worker_log_server_port = 8793 
  36. [metrics] statsd_on = False 
  37. statsd_host = localhost statsd_port = 8125 
  38. statsd_prefix = airflow statsd_allow_list = 
  39. stat_name_handler = statsd_datadog_enabled = False 
  40. statsd_datadog_tags = [secrets] 
  41. backend = backend_kwargs = 
  42. [cli] api_client = airflow.api.client.local_client 
  43. endpoint_url = http://localhost:8080 [debug] 
  44. fail_fast = False [api] 
  45. enable_experimental_api = False auth_backend = airflow.api.auth.backend.deny_all 
  46. maximum_page_limit = 100 fallback_page_limit = 100 
  47. google_oauth2_audience = google_key_path = 
  48. access_control_allow_headers = access_control_allow_methods = 
  49. access_control_allow_origins = [lineage] 
  50. backend = [atlas] 
  51. sasl_enabled = False host = 
  52. port = 21000 username = 
  53. password = [operators] 
  54. default_owner = airflow default_cpus = 1 
  55. default_ram = 512 default_disk = 512 
  56. default_gpus = 0 default_queue = default 
  57. allow_illegal_arguments = False [hive] 
  58. default_hive_mapred_queue = [webserver] 
  59. base_url = https://devopsman.cn/airflow #自定义airflow域名 default_ui_timezone = Asia/Shanghai # 设置默认的时区 
  60. web_server_host = 0.0.0.0 web_server_port = 8080 
  61. web_server_ssl_cert = web_server_ssl_key = 
  62. web_server_master_timeout = 120 web_server_worker_timeout = 120 
  63. worker_refresh_batch_size = 1 worker_refresh_interval = 6000 
  64. reload_on_plugin_change = False secret_key = emEfndkf3QWZ5zVLE1kVMg== 
  65. workers = 4 worker_class = sync 
  66. access_logfile = - error_logfile = - 
  67. access_logformat = expose_config = False 
  68. expose_hostname = True expose_stacktrace = True 
  69. dag_default_view = tree dag_orientation = LR 
  70. log_fetch_timeout_sec = 5 log_fetch_delay_sec = 2 
  71. log_auto_tailing_offset = 30 log_animation_speed = 1000 
  72. hide_paused_dags_by_default = False page_size = 100 
  73. navbar_color = #fff default_dag_run_display_number = 25 
  74. enable_proxy_fix = False proxy_fix_x_for = 1 
  75. proxy_fix_x_proto = 1 proxy_fix_x_host = 1 
  76. proxy_fix_x_port = 1 proxy_fix_x_prefix = 1 
  77. cookie_secure = False cookie_samesite = Lax 
  78. default_wrap = False x_frame_enabled = True 
  79. show_recent_stats_for_completed_runs = True update_fab_perms = True 
  80. session_lifetime_minutes = 43200 auto_refresh_interval = 3 
  81. [email] email_backend = airflow.utils.email.send_email_smtp 
  82. email_conn_id = smtp_default default_email_on_retry = True 
  83. default_email_on_failure = True [smtp] # 邮箱配置 
  84. smtp_host = localhost smtp_starttls = True 
  85. smtp_ssl = False smtp_port = 25 
  86. smtp_mail_from = airflow@example.com smtp_timeout = 30 
  87. smtp_retry_limit = 5 [sentry] 
  88. sentry_on = false sentry_dsn = 
  89. [celery_kubernetes_executor] kubernetes_queue = kubernetes 
  90. [celery] celery_app_name = airflow.executors.celery_executor 
  91. worker_concurrency = 16 worker_umask = 0o077 
  92. broker_url = redis://:xxxx@$${REDIS_HOST}:7480/0 result_backend = db+mysql://airflow:aaaa@$${MYSQL_HOST}:3306/airflow 
  93. flower_host = 0.0.0.0 flower_url_prefix = 
  94. flower_port = 5555 flower_basic_auth = 
  95. sync_parallelism = 0 celery_config_options = airflow.config_templates.default_celery.DEFAULT_CELERY_CONFIG 
  96. ssl_active = False ssl_key = 
  97. ssl_cert = ssl_cacert = 
  98. pool = prefork operation_timeout = 1.0 
  99. task_track_started = True task_adoption_timeout = 600 
  100. task_publish_max_retries = 3 worker_precheck = False 
  101. [celery_broker_transport_options] [dask] 
  102. cluster_address = 127.0.0.1:8786 tls_ca = 
  103. tls_cert = tls_key = 
  104. [scheduler] job_heartbeat_sec = 5 
  105. scheduler_heartbeat_sec = 5 num_runs = -1 
  106. scheduler_idle_sleep_time = 1 min_file_process_interval = 30 
  107. dag_dir_list_interval = 300 print_stats_interval = 30 
  108. pool_metrics_interval = 5.0 scheduler_health_check_threshold = 30 
  109. orphaned_tasks_check_interval = 300.0 child_process_log_directory = /opt/airflow/logs/scheduler 
  110. scheduler_zombie_task_threshold = 300 catchup_by_default = True 
  111. max_tis_per_query = 512 use_row_level_locking = True 
  112. max_dagruns_to_create_per_loop = 10 max_dagruns_per_loop_to_schedule = 20 
  113. schedule_after_task_execution = True parsing_processes = 2 
  114. file_parsing_sort_mode = modified_time use_job_schedule = True 
  115. allow_trigger_in_future = False dependency_detector = airflow.serialization.serialized_objects.DependencyDetector 
  116. trigger_timeout_check_interval = 15 [triggerer] 
  117. default_capacity = 1000 [kerberos] 
  118. ccache = /tmp/airflow_krb5_ccache principal = airflow 
  119. reinit_frequency = 3600 kinit_path = kinit 
  120. keytab = airflow.keytab forwardable = True 
  121. include_ip = True [github_enterprise] 
  122. api_rev = v3 [elasticsearch] 
  123. host = log_id_template = {dag_id}-{task_id}-{execution_date}-{try_number} 
  124. end_of_log_mark = end_of_log frontend = 
  125. write_stdout = False json_format = False 
  126. json_fields = asctime, filename, lineno, levelname, message host_field = host 
  127. offset_field = offset [elasticsearch_configs] 
  128. use_ssl = False verify_certs = True 
  129. [kubernetes] pod_template_file = 
  130. worker_container_repository = worker_container_tag = 
  131. namespace = default delete_worker_pods = True 
  132. delete_worker_pods_on_failure = False worker_pods_creation_batch_size = 1 
  133. multi_namespace_mode = False in_cluster = True 
  134. kube_client_request_args = delete_option_kwargs = 
  135. enable_tcp_keepalive = True tcp_keep_idle = 120 
  136. tcp_keep_intvl = 30 tcp_keep_cnt = 6 
  137. verify_ssl = True worker_pods_pending_timeout = 300 
  138. worker_pods_pending_timeout_check_interval = 120 worker_pods_queued_check_interval = 60 
  139. worker_pods_pending_timeout_batch_size = 100 [smart_sensor] 
  140. use_smart_sensor = False shard_code_upper_limit = 10000 
  141. shards = 5 sensors_enabled = NamedHivePartitionSensor 

修改完成之后,重启一下服务。

  1. docker-compose restart 
4数据同步

因为airflow使用了三个worker节点,每个节点修改配置,其他节点都要同步,同时DAGS目录以及plugins目录也需要实时进行同步,在scheduler将信息调度到某个节点后,如果找不到对应的DAGS文件,就会报错,因此我们使用lsyncd进行数据实时同步:

  1. apt-get install lsyncd -y 

配置节点之间通过公钥连接

  1. ssh-keygen -t rsa -C "airflow-sync" -b 4096 #生成一对名为airflow-sync的密钥 for ip in 100 200;do ssh-copy-id -i ~/.ssh/airflow-sync.pub ${USERNAME}@192.168.0.$ip -P12022;done  

然后我们就可以通过私钥访问了其它节点了。

编辑同步的配置文件,lsyncd配置的更多参数学习,可以直达官方文档[2]

  1. settings {     logfile = "/var/log/lsyncd.log", # 日志文件 
  2.     statusFile = "/var/log/lsyncd.status", # 同步状态信息     pidfile = "/var/run/lsyncd.pid", 
  3.     statusInterval = 1,     nodaemon = false, # 守护进程 
  4.     inotifyMode  = "CloseWrite",     maxProcesses = 1, 
  5.     maxDelays = 1, } 
  6. sync {     default.rsync, 
  7.     source = "/data/airflow",     target = "192.168.0.100:/data/airflow", 
  8.      rsync = { 
  9.        binary = "/usr/bin/rsync",        compress = false, 
  10.        archive = true,        owner = true, 
  11.        perms = true,        --delete =  true, 
  12.        whole_file = false,        rsh = "/usr/bin/ssh -p 12022 -l suoper -o StrictHostKeyChecking=no -i /home/username/.ssh/airflow-rsync" 
  13.     }, } 
  14. sync {     default.rsync, 
  15.     source = "/data/airflow",     target = "192.168.0.200:/data/airflow", 
  16.      rsync = { 
  17.        binary = "/usr/bin/rsync",        compress = false, 
  18.        archive = true,        owner = true, 
  19.        perms = true,        --delete =  true, 
  20.        whole_file = false,        rsh = "/usr/bin/ssh -p 12022 -l suoper -o StrictHostKeyChecking=no -i /home/username/.ssh/airflow-rsync" 
  21.     }, } 

以上的参数是什么意思,可以访问官网查看,此处是通过rsync的rsh定义ssh命令,能够解决使用了私钥,自定义端口等安全措施的场景,当然你也可以使用配置无密访问,然后使用default.rsync或者default.rsyncssh等进行配置。

配置lsyncd的服务托管

  1. cat << EOF > /etc/systemd/system/lsyncd.service [Unit] 
  2. Description=lsyncd ConditionFileIsExecutable=/usr/bin/lsyncd 
  3.  After=network-online.target 
  4. Wants=network-online.target  
  5. [Service] StartLimitBurst=10 
  6. ExecStart=/usr/bin/lsyncd /etc/lsyncd.conf Restart=on-failure 
  7. RestartSec=120 EnvironmentFile=-/etc/sysconfig/aliyun 
  8. KillMode=process [Install] 
  9. WantedBy=multi-user.target EOF 
  10.  systemctl daemon-reload 
  11. systemctl enable --now lsyncd.service #启动服务并配置开启自启 

这样就完成了数据(dags,plugins,airflow.cfg)的同步问题,后期使用CICD场景的时候,便可以直接将dag文件上传到Bigdata1节点上即可,其他两个节点就会自动同步了。如果出现问题,可以通过查看日志进行debug

  1. lsyncd -log all /etc/lsyncd.conf tail -f /var/log/lsyncd.log 



5反向代理[3]

如果你需要将airflow放在反向代理之后,如https://lab.mycompany.com/myorg/airflow/你可以通过一下配置完成:

在airflow.cfg中配置base_url

  1. base_url = http://my_host/myorg/airflow enable_proxy_fix = True 

nginx的配置

  1. server {   listen 80; 
  2.   server_name lab.mycompany.com;  
  3.   location /myorg/airflow/ {       proxy_pass http://localhost:8080; 
  4.       proxy_set_header Host $http_host;       proxy_redirect off; 
  5.       proxy_http_version 1.1;       proxy_set_header Upgrade $http_upgrade; 
  6.       proxy_set_header Connection "upgrade";   } 

到这里就基本上完成的airflow分布式调度集群的安装了.看下具体效果如下。



看到这里说明你也正在使用或对Airflow感兴趣,顺便送你一个学习Airflow资料;

https://livebook.manning.com/book/data-pipelines-with-apache-airflow/chapter-12/1

参考资料

[1]Airflow 2.2.3 + MySQL8.0.27: https://mp.weixin.qq.com/s/VncpyXcTtlvnDkFrsAZ5lQ

[2]lsyncd config file: https://lsyncd.github.io/lsyncd/manual/config/file/

[3]airflow-behind-proxy: https://airflow.apache.org/docs/apache-airflow/stable/howto/run-behind-proxy.html

 

转载请注明:文章转载自 www.051e.com
本文地址:http://www.051e.com/it/796698.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 ©2023-2025 051e.com

ICP备案号:京ICP备12030808号