微服务部署实践
分类:电脑系统

四、结语

    总算是把三个微服务项目布置运营起来了,大概是用了至少的 Docker-compose 模板文件,所以如故有很多地点能够健全的,例如说 MySQL 密码未有加密管理、服务未有做健检、集群方面还没怎么思量(用 Docker Swarm 达成)等等......路深远其修远兮,吾将上下而求索。共勉!

云部署

三、编排文件 docker-compose.yml

    微服务项目要配备起来,主借使靠 docker-compose.yml 文件举行编写制定,规定服务中间的关联以及前后相继运转顺序,然后把几十二个一鳞半爪的微服务当成一个完全来统一管理。

    首先,困扰本人的是互联网难点。做过支付的都明白,要在品种中钦定(Spring 在 applicationContext.xml)数据库地址和 Zookeeper 地址,那么小编怎么通晓容器的 ip 地址是稍微呢?先来打探下 Docker 的网络格局?

    Docker 的默许互联网安顿是 "bridge",当 Docker 运营时,会自行在主机上创制叁个 docker0 虚拟网桥,实际上是 Linux 的七个bridge,能够知晓为四个软件调换机。Docker 会随机分配二个地面未占用的私人商品房网段(在 奇骏FC一九二〇 中定义)中的一个地方给 docker0 接口,它会在挂载到它的网口之间开展中间转播。当创制八个 Docker 容器的时候,同期会创立了一对 veth pair 接口。那对接口一端在容器内,即 eth0;另一端在地面并被挂载到 docker0 网桥,名称以 veth 最早(比如vethAQI2QT)。通过这种办法,主机能够跟容器通讯,容器之间也得以互相通讯。

     也正是说,每一趟容器运维以后的 ip 地址是不固定的,那该怎么做吧?当然能够写死 IP 地址,规定局域网网段,给各样服务编排 IP 地址;当然也能够把network_mode="host",统一用宿主机的互联网地址。当然!那几个都不是最棒的办法:

version: '3.7'
#服务列表
services:
  #基础组件 zookeeper  
  zookeeper:
    image: zookeeper
    restart: always
    ports:
      - 4181:2181
  #基础组件 MySQL
  db:
    image: mysql:5.7.17
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --init-connect='SET NAMES utf8mb4;'
    ports:
     - "3636:3306"
    volumes:
     - /var/mysqldb:/var/lib/mysql
     - /docker/mysql/my.cnf:/etc/mysql/mysql.conf.d/mysqld.cnf
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: password
  #消费者服务1 admin
  admin:
    image: "admin:2.3.1"
    ports:
     - "7575:8080"
    depends_on:
     - zookeeper
    restart: always
    environment:
      zookeeper.host: zookeeper://zookeeper:2181
  #提供者服务1 system
  system:
    image: "system:2.3.1"
    depends_on:
     - db
     - zookeeper
    restart: always
    environment:
      zookeeper.host: zookeeper://zookeeper:2181
      mysql.address: db:3306

    看到了呢?IP 地址直接由 服务名 钦命就足以了。别的, Docker 中装置的情状变量,竟然能被 applicationContext.xml 中读取,笔者也是蛮诧异的!(在代码和 Docker 中都配备了mysql.address 的话,以 Docker 中安装的见效)。

    然后 docker-compose up -d 运行微服务项目就能够了~~

    容器安顿的一个尺码:尽量不要在容器内部做文件的改造,要修改的内容用数据卷的方式映射到宿主机上,比方上面的MySQL配置文件和数据仓库。

图片 1

    在 Docker 上配置 MySQL 遭逢了多少个难点,轻松罗列下:

1、Navicat 连接的时候: Client does not support authentication protocol requested by server ?

解决:进入 MySQL 容器,运行

ALTER user 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'password';

2、Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggre 的问题?

原因:MySQL 5.7.5及以上功效注重检查实验作用。若是启用了ONLY_FULL_GROUP_BY SQL格局(暗中认可意况下),MySQL将不容采取列表,HAVING条件或OPAJERODER BY列表的查询引用在GROUP BY子句中既未命名的非集合列,也不在成效上依赖于它们。

消除:在MySQL的布局文件中增加:

sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION

3、MySQL 连接参数useSSL=true 和 useSSL=false 的分别?

    提出不用在尚未服务器身份验证的情事下创造SSL连接(同二个 Docker-compose 中是内网意况)。依照 MySQL 5.5.45 +,5.6.26 +和5.7.6+ 必要如若未安装显式选项,则必需暗中认可构造建设SSL连接。为了顺应不应用SSL的依存应用程序。您必要通过安装useSSL = false显式禁止使用SSL,恐怕设置useSSL = true并为服务器证书验证提供信任库。

Dockerfile指令

  • FROM
    指明当前镜像承继的基镜像,编译当前镜像时会自动下载基镜像。

    FROM java:8
    
  • MAINTAINER
    指明当前镜像的撰稿人。

    MAINTAINER linliangsheng
    
  • RUN
    此时此刻镜像上施行Linux命令并转身一变多个新的层,编写翻译时(build)动作。

    RUN /bin/bash -c "echo hello"
    
    RUN ["/bin/bash", "-c", "echo hello"]
    
  • CMD
    运行镜像容器时的默许行为,二个Dockerfile只可以有三个CMD指令,可在运行镜像时使用参数覆盖,运营时(run)动作。

    CMD echo "hello"
    

    参数覆盖写法:

    docker run -d image_name echo "docker-hello"
    
  • EXPOSE
    指明镜像运营时的容器必需监听钦点的端口。

    EXPOSE 8080
    
  • ENV
    设置情状变量。

    ENV name=linliangsheng
    
    ENV name linliangsheng
    
  • ADD
    从当前工作目录复制文件到镜像目录。

    ADD xxx.jar app.jar
    
  • ENTRYPOINT
    让容器像可施行程序同样运营,镜像运维时可选拔参数,运维时(run)动作。

    ENTRYPOINT ["java"]
    

    参数接收写法:

    docker run -d image_name "-jar app.jar"
    

二、服务镜像打包

参照他事他说加以考察资料

Java EE开采的颠覆者-Spring Boot实战.汪云飞.407-424,478-484

     1、汤姆cat 基础条件搭建

    我们系统的各个微服务都布署运营在 汤姆cat 上(听大人说这种方法很差,对于一些不是web工程的,没须要搭建成 web 服务,扩大复杂性,也浪费系统财富),所以本人的主张是:先搭建一套 Tomcat 情状镜像,然后种种微服务都依据那么些情状镜像去构建。所以写了贰个tomcat-env 的镜像,思路如下:

    -- 基于 JDK 的 Tomcat 容器(重要参照官方网址 汤姆cat 镜像的 Dockerfile)。

    -- 在上下文目录寄放项目编写翻译文件,仁同一视命名叫ROOT(不放 war 包的来头是想念调节和测量检验的时候便于,不用改二个文件,就打个war包)。

    -- 删除原来 汤姆cat 容器 webapps 目录下的 ROOT 文件,并将上下文目录中项目标 ROOT 文件夹上盛传容器 webapps 目录下。

    -- 运维服务。

图片 2图片 3

FROM openjdk:8-jre

ENV CATALINA_HOME /usr/local/tomcat
ENV PATH $CATALINA_HOME/bin:$PATH
RUN mkdir -p "$CATALINA_HOME"
WORKDIR $CATALINA_HOME

# let "Tomcat Native" live somewhere isolated
ENV TOMCAT_NATIVE_LIBDIR $CATALINA_HOME/native-jni-lib
ENV LD_LIBRARY_PATH ${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$TOMCAT_NATIVE_LIBDIR

# runtime dependencies for Tomcat Native Libraries
# Tomcat Native 1.2+ requires a newer version of OpenSSL than debian:jessie has available
# > checking OpenSSL library version >= 1.0.2...
# > configure: error: Your version of OpenSSL is not compatible with this version of tcnative
# see http://tomcat.10.x6.nabble.com/VOTE-Release-Apache-Tomcat-8-0-32-tp5046007p5046024.html (and following discussion)
# and https://github.com/docker-library/tomcat/pull/31
ENV OPENSSL_VERSION 1.1.0f-3+deb9u2
RUN set -ex; 
    currentVersion="$(dpkg-query --show --showformat '${Version}n' openssl)"; 
    if dpkg --compare-versions "$currentVersion" '<<' "$OPENSSL_VERSION"; then 
        if ! grep -q stretch /etc/apt/sources.list; then 
# only add stretch if we're not already building from within stretch
            { 
                echo 'deb http://deb.debian.org/debian stretch main'; 
                echo 'deb http://security.debian.org stretch/updates main'; 
                echo 'deb http://deb.debian.org/debian stretch-updates main'; 
            } > /etc/apt/sources.list.d/stretch.list; 
            { 
# add a negative "Pin-Priority" so that we never ever get packages from stretch unless we explicitly request them
                echo 'Package: *'; 
                echo 'Pin: release n=stretch*'; 
                echo 'Pin-Priority: -10'; 
                echo; 
# ... except OpenSSL, which is the reason we're here
                echo 'Package: openssl libssl*'; 
                echo "Pin: version $OPENSSL_VERSION"; 
                echo 'Pin-Priority: 990'; 
            } > /etc/apt/preferences.d/stretch-openssl; 
        fi; 
        apt-get update; 
        apt-get install -y --no-install-recommends openssl="$OPENSSL_VERSION"; 
        rm -rf /var/lib/apt/lists/*; 
    fi

RUN apt-get update && apt-get install -y --no-install-recommends 
        libapr1 
    && rm -rf /var/lib/apt/lists/*

# see https://www.apache.org/dist/tomcat/tomcat-$TOMCAT_MAJOR/KEYS
# see also "update.sh" (https://github.com/docker-library/tomcat/blob/master/update.sh)
ENV GPG_KEYS 05AB33110949707C93A279E3D3EFE6B686867BA6 07E48665A34DCAFAE522E5E6266191C37C037D42 47309207D818FFD8DCD3F83F1931D684307A10A5 541FBE7D8F78B25E055DDEE13C370389288584E7 61B832AC2F1C5A90F0F9B00A1C506407564C17A3 713DA88BE50911535FE716F5208B0AB1D63011C7 79F7026C690BAA50B92CD8B66A3AD3F4F22C4FED 9BA44C2621385CB966EBA586F72C284D731FABEE A27677289986DB50844682F8ACB77FC2E86E29AC A9C5DF4D22E99998D9875A5110C01C5A2F6059E7 DCFD35E0BF8CA7344752DE8B6FB21E8933C60243 F3A04C595DB5B6A5F1ECA43E3B7BBB100D811BBE F7DA48BB64BCB84ECBA7EE6935CD23C10D498E23

ENV TOMCAT_MAJOR 8
ENV TOMCAT_VERSION 8.0.53
ENV TOMCAT_SHA512 cd8a4e48a629a2f2bb4ce6b101ebcce41da52b506064396ec1b2915c0b0d8d82123091242f2929a649bcd8b65ecf6cd1ab9c7d90ac0e261821097ab6fbe22df9

ENV TOMCAT_TGZ_URLS 
# https://issues.apache.org/jira/browse/INFRA-8753?focusedCommentId=14735394#comment-14735394
    https://www.apache.org/dyn/closer.cgi?action=download&filename=tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz 
# if the version is outdated, we might have to pull from the dist/archive :/
    https://www-us.apache.org/dist/tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz 
    https://www.apache.org/dist/tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz 
    https://archive.apache.org/dist/tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz

ENV TOMCAT_ASC_URLS 
    https://www.apache.org/dyn/closer.cgi?action=download&filename=tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz.asc 
# not all the mirrors actually carry the .asc files :'(
    https://www-us.apache.org/dist/tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz.asc 
    https://www.apache.org/dist/tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz.asc 
    https://archive.apache.org/dist/tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz.asc

RUN set -eux; 
    
    savedAptMark="$(apt-mark showmanual)"; 
    apt-get update; 
    
    apt-get install -y --no-install-recommends gnupg dirmngr; 
    
    export GNUPGHOME="$(mktemp -d)"; 
    for key in $GPG_KEYS; do 
        gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$key"; 
    done; 
    
    apt-get install -y --no-install-recommends wget ca-certificates; 
    
    success=; 
    for url in $TOMCAT_TGZ_URLS; do 
        if wget -O tomcat.tar.gz "$url"; then 
            success=1; 
            break; 
        fi; 
    done; 
    [ -n "$success" ]; 
    
    echo "$TOMCAT_SHA512 *tomcat.tar.gz" | sha512sum -c -; 
    
    success=; 
    for url in $TOMCAT_ASC_URLS; do 
        if wget -O tomcat.tar.gz.asc "$url"; then 
            success=1; 
            break; 
        fi; 
    done; 
    [ -n "$success" ]; 
    
    gpg --batch --verify tomcat.tar.gz.asc tomcat.tar.gz; 
    tar -xvf tomcat.tar.gz --strip-components=1; 
    rm bin/*.bat; 
    rm tomcat.tar.gz*; 
    command -v gpgconf && gpgconf --kill all || :; 
    rm -rf "$GNUPGHOME"; 
    
    nativeBuildDir="$(mktemp -d)"; 
    tar -xvf bin/tomcat-native.tar.gz -C "$nativeBuildDir" --strip-components=1; 
    apt-get install -y --no-install-recommends 
        dpkg-dev 
        gcc 
        libapr1-dev 
        libssl-dev 
        make 
        "openjdk-${JAVA_VERSION%%[.~bu-]*}-jdk=$JAVA_DEBIAN_VERSION" 
    ; 
    ( 
        export CATALINA_HOME="$PWD"; 
        cd "$nativeBuildDir/native"; 
        gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)"; 
        ./configure 
            --build="$gnuArch" 
            --libdir="$TOMCAT_NATIVE_LIBDIR" 
            --prefix="$CATALINA_HOME" 
            --with-apr="$(which apr-1-config)" 
            --with-java-home="$(docker-java-home)" 
            --with-ssl=yes; 
        make -j "$(nproc)"; 
        make install; 
    ); 
    rm -rf "$nativeBuildDir"; 
    rm bin/tomcat-native.tar.gz; 
    
# reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies
    apt-mark auto '.*' > /dev/null; 
    [ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; 
    apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; 
    rm -rf /var/lib/apt/lists/*; 
    
# sh removes env vars it doesn't support (ones with periods)
# https://github.com/docker-library/tomcat/issues/77
    find ./bin/ -name '*.sh' -exec sed -ri 's|^#!/bin/sh$|#!/usr/bin/env bash|' '{}' +

# verify Tomcat Native is working properly
RUN set -e 
    && nativeLines="$(catalina.sh configtest 2>&1)" 
    && nativeLines="$(echo "$nativeLines" | grep 'Apache Tomcat Native')" 
    && nativeLines="$(echo "$nativeLines" | sort -u)" 
    && if ! echo "$nativeLines" | grep 'INFO: Loaded APR based Apache Tomcat Native library' >&2; then 
        echo >&2 "$nativeLines"; 
        exit 1; 
    fi

EXPOSE 8080
RUN rm -rf /usr/local/tomcat/webapps/ROOT/
ONBUILD COPY ROOT /usr/local/tomcat/webapps/ROOT/
ONBUILD ENTRYPOINT ["/usr/local/tomcat/bin/catalina.sh","run"]

tomcat-env

看起来很复杂,不要被吓到,其实都是抄的官方网站 汤姆cat 镜像的Dockerfile,然后改成了一些,首假如末端三句:删除容器 ROOT 文件夹,拷贝上下文目录的 ROOT 文件夹到 wenapps 目录下,重启服务。

RUN rm -rf /usr/local/tomcat/webapps/ROOT/
ONBUILD COPY ROOT /usr/local/tomcat/webapps/ROOT/
ONBUILD ENTRYPOINT ["/usr/local/tomcat/bin/catalina.sh","run"]

tips:1、ONBUILD 命令本次镜像不会被实行,唯有以这么些镜像为底蕴镜像的时候才会被试行。

          2、上下文目录指的是 Dockerfile 文件所在的目录。

          3、该镜像已上传来 DockerHub 上:https://hub.docker.com/r/jmcui/tomcat-env/

热部署

pom.xml文件中加多spring-boot-devtools重视就能够兑现页面和代码的热铺排。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
</dependency>

一、前言

    以前我们公司安顿服务,正是豪门都懂的那一套(安装JDK、汤姆cat —> 编写翻译好文件恐怕打war包上传 —> 运行汤姆cat),这种安插格局一向不停了相当久,带来的标题也相当多:

1、繁重的发布职责。微服务一多,就要每种服务都要重启一遍,並且一旦集群的话,这要运行的劳务就越来越多了。

2、处境迁移报错。平日发生的一件事,同样的一套代码,那台服务器上正是能跑起来,换个服务器正是报错了。

3、士气低沉。小商铺尚未正面包车型地铁运行,都以让开荒兼并着做那地点的专门的职业,然后肩负那块的同事怨言比相当多(因为这种发表陈设实在太无趣了)。

    所以领导决定挑起 Docker 作为我们的安排格局,一来能够很好的解决近期项目配置存在的题目,二来为品种注入新鲜血液。

    从上月15号开始接触 Docker,到现在把大家系统的微服务架构开端搭建好,折腾了绵绵,踩了广布袋澳。回看一下小成就,写了那篇博客。为了防止提到败露公司机密,就小而全的做一些简便介绍哈,以下边那张小小的微服务架构图为例,安排一套 Dubbo 微服务。

图片 4

war

  • 打包
mvn package
  • 运行
    将war包丢到支撑war文件的Servlet容器实行。
  • jar 转 war

    • pom.xml文件元帅<packaging>jar</packaging>改为<packaging>war</packaging>
    • 增加ServletInitializer类

      import org.springframework.boot.builder.SpringApplicationBuilder;
      import org.springframework.boot.context.web.SpringBootServletInitializer;
      public class ServletInitializer extends SpringBootServletInitializer {
          @Override
          protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
              return application.source(XxxApplication.class)
          }
      }
      
    • 日增如下信赖,覆盖默许内嵌汤姆cat信赖

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-tomcat</artifactId>
          <scope>provided</scope>
      </dependency>
      

     2、微服务镜像打包

    有了根基情况镜像 tomcat-env,那么打包叁个服务镜像正是一件再轻便然则的事体了:

图片 5

FROM tomcat-env:1.0

    没有错,正是这般轻易,因为我们把具备的劳作都坐落 tomcat-env 中了,其实正是这一个 ONBUILD 命令的效果啊~~ 

Spring Cloud实践

  1. 编写runboot.sh脚本
    位于src/main/docker下:

    sleep 10
    java -Djava.security.egd=file:/dev/./urandom -jar /app/app.jar
    

    依照运行顺序调节sleep时间

  2. 编写Dockerfile
    位于src/main/docker下:

    FROM java:8
    VOLUME /tmp
    RUN mkdir /app
    ADD xxx.jar /app/app.jar
    ADD runboot.sh /app/
    RUN bash -c 'touch /app/app.jar'
    WORKDIR /app
    RUN chmod a+x runboot.sh
    EXPOSE 8888
    CMD /app/runboot.sh
    

    不等的微服务只需修改

    ADD xxx.jar /app/app.jar
    EXPOSE 8888
    
  3. Docker插件
    在pom.xml中增加docker-maven-plugin插件

    <build>
        <plugins>
            <plugin>
                <groupId>com.spotify</groupId>
                <artifactId>docker-maven-plugin</artifactId>
                <configuration>
                    <imageName>${project.name}:${project.version}</imageName>
                    <dockerDirectory>${project.basedir}/src/main/docker</dockerDirectory>
                    <skipDockerBuild>false</skipDockerBuild>
                    <resources>
                        <resource>
                            <directory>${project.build.directory}</directory>
                            <include>${project.build.finalName}.jar</include>
                        </resource>
                    </resources>
                </configuration>
            </plugin>
        </plugins>
    </build>
    
  4. 编写翻译镜像
    docker-maven-plugin私下认可将Docker编译到localhost,纵然是远程Linux服务器,需求配置情状变量DOCKER_HOST,变量值tcp://IP地址:端口号
    施行mvn命令进行镜像编译:

    mvn clean package docker:build -DskipTests
    
  5. Docker Compose
    Docker Compose是用来定义和平运动行多容器应用的工具,使用一个docker-compose.yml来说述多容器定义,使用docker-compose up -d运行总体应用,-d意味着后台运转。
    docker-compose.yml参照他事他说加以考察剧情:

    postgresdb:
      image: busybox
      volumes:
        - /var/lib/postgresql/data
    postgres:
      name: postgres
      image: postgres
      hostname: postgres
      volumes_from:
        - postgresdb
      environment:
        - POSTGRES_USER=postgres
        - POSTGRES_PASSWORD=postgres
    discovery:
      name: discovery
      image: "discovery:1.0.0-SNAPSHOT"
      hostname: discovery
      ports:
        - "8761:8761"
    config:
      name: config
      image: "config:1.0.0-SNAPSHOT"
      hostname: config
      links:
        - discovery
      environment:
        - EUREKA_HOST: discovery
        - EUREKA_PORT: 8761
      ports:
        - "8888:8888"
    person:
      name: person
      image: "person:1.0.0-SNAPSHOT"
      hostname: person
      links:
        - discovery
        - config
        - postgres
      environment:
        - EUREKA_HOST: discovery
        - EUREKA_PORT: 8761
        - SPRING_PROFILES_ACTIVE: docker
      ports:
        - "8082:8082"
    some:
      name: some
      image: "some:1.0.0-SNAPSHOT"
      hostname: some
      links:
        - discovery
        - config
      environment:
        - EUREKA_HOST: discovery
        - EUREKA_PORT: 8761
        - SPRING_PROFILES_ACTIVE: docker
      ports:
        - "8083:8083"
    ui:
      name: ui
      image: "ui:1.0.0-SNAPSHOT"
      hostname: ui
      links:
        - discovery
        - config
        - person
        - some
      environment:
        - EUREKA_HOST: discovery
        - EUREKA_PORT: 8761
        - SPRING_PROFILES_ACTIVE: docker
      ports:
        - "80:80"
    monitor:
      name: monitor
      image: "monitor:1.0.0-SNAPSHOT"
      hostname: monitor
      links:
        - discovery
        - config
        - person
        - some
        - ui
      environment:
        - EUREKA_HOST: discovery
        - EUREKA_PORT: 8761
        - SPRING_PROFILES_ACTIVE: docker
      ports:
        - "8989:8989"
    

Docker实践

本文由威尼斯手机娱乐官网发布于电脑系统,转载请注明出处:微服务部署实践

上一篇:没有了 下一篇:没有了
猜你喜欢
热门排行
精彩图文