<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Kubernetes 上 Java 應(yīng)用的最佳實(shí)踐

          共 13107字,需瀏覽 27分鐘

           ·

          2023-03-07 23:55

          點(diǎn)擊關(guān)注公眾號,Java干貨及時(shí)送達(dá)

          在本文中,您將了解在 Kubernetes 上運(yùn)行 Java 應(yīng)用程序的最佳實(shí)踐。大多數(shù)這些建議也適用于其他語言。但是,我正在考慮 Java 特性范圍內(nèi)的所有規(guī)則,并且還展示了可用于基于 JVM 的應(yīng)用程序的解決方案和工具。當(dāng)使用最流行的 Java 框架(如 Spring Boot 或 Quarkus)時(shí),這些 Kubernetes 建議中的一些是設(shè)計(jì)強(qiáng)制的。我將向您展示如何有效地利用它們來簡化開發(fā)人員的生活。


          1、不要將 Limit 設(shè)置得太低


          我們是否應(yīng)該為 Kubernetes 上的 Java 應(yīng)用設(shè)置 limit ?答案似乎顯而易見。有許多工具可以驗(yàn)證您的 Kubernetes YAML 清單,如果您沒有設(shè)置 CPU 或內(nèi)存 limit ,它們肯定會打印警告。不過,社區(qū)對此也有一些“熱議”。這是一篇有趣的文章,不建議設(shè)置任何 CPU limit 。這是另一篇文章,作為對上一篇文章的對比,他們考慮 CPU limit 。但我們也可以針對內(nèi)存 limit 開始類似的討論。特別是在 Java 應(yīng)用程序的上下文中。
          然而,對于內(nèi)存管理,這個(gè)命題似乎大不相同。讓我們閱讀另一篇文章——這次是關(guān)于內(nèi)存 limit 和 request 的。簡而言之,它建議始終設(shè)置內(nèi)存 limit。此外,限制應(yīng)與 request 相同。在 Java 應(yīng)用程序的上下文中,我們可以使用 -Xmx 、 -XX:MaxMetaspaceSize 或 -XX:ReservedCodeCacheSize 等 JVM 參數(shù)限制內(nèi)存也很重要。無論如何,從 Kubernetes 的角度來看,pod 接收它 request 的資源。Limit 與它無關(guān)。

          這一切讓我得出了今天的第一個(gè)建議—A—不要將你的 limit 設(shè)置得太低。即使您設(shè)置了 CPU limit ,也不應(yīng)該影響您的應(yīng)用程序。例如,您可能知道,即使您的 Java 應(yīng)用程序在正常工作中不會消耗太多 CPU,但它需要大量 CPU 才能快速啟動。對于我在 Kubernetes 上連接 MongoDB 的簡單 Spring Boot 應(yīng)用程序,無限制和甚至 0.5 核之間的差異是顯著的。通常它在 10 秒以下開始:


          將 CPU limit 設(shè)置為 500 millicores ,它開始大約 30 秒:


          當(dāng)然,我們可以找到一些例子。但我們也會在下一節(jié)中討論它們。

          2、首先考慮內(nèi)存使用


          讓我們只關(guān)注內(nèi)存 limit 。如果您在 Kubernetes 上運(yùn)行 Java 應(yīng)用程序,則有兩個(gè)級別的最大使用 limit :容器和 JVM。但是,如果您沒有為 JVM 指定任何設(shè)置,也有一些默認(rèn)值。如果您不設(shè)置 -Xmx 參數(shù),JVM 會將其最大堆大小設(shè)置為可用 RAM 的大約 25%。該值是根據(jù)容器內(nèi)可見的內(nèi)存計(jì)算的。一旦您不在容器級別設(shè)置 limit ,JVM 將看到節(jié)點(diǎn)的整個(gè)內(nèi)存。

          在 Kubernetes 上運(yùn)行應(yīng)用程序之前,您至少應(yīng)該測量它在預(yù)期負(fù)載下消耗了多少內(nèi)存。幸運(yùn)的是,有一些工具可以優(yōu)化在容器中運(yùn)行的 Java 應(yīng)用程序的內(nèi)存配置。例如,Paketo Buildpacks 帶有內(nèi)置內(nèi)存計(jì)算器,它使用公式 Heap = 總?cè)萜鲀?nèi)存 - Non-Heap - Headroom 計(jì)算 JVM 的 -Xmx 參數(shù)。另一方面,非堆值是使用以下公式計(jì)算的:Non-Heap = Direct Memory + Metaspace + Reserved Code Cache + (Thread Stack * Thread Count) 。

          Paketo Buildpacks 目前是構(gòu)建 Spring Boot 應(yīng)用程序的默認(rèn)選項(xiàng)(使用 mvn spring-boot:build-image 命令)。讓我們?yōu)槲覀兊氖纠龖?yīng)用程序嘗試一下。假設(shè)我們將內(nèi)存限制設(shè)置為 512M,它將在 130M 的級別計(jì)算 -Xmx 。


          我的應(yīng)用程序可以嗎?我至少應(yīng)該執(zhí)行一些負(fù)載測試來驗(yàn)證我的應(yīng)用程序在高流量下的性能。但再一次 - 不要將 limit 設(shè)置得太低。例如,對于 1024M 限制, -Xmx 等于 650M。


          如您所見,我們使用 JVM 參數(shù)處理內(nèi)存使用情況。它可以防止我們在第一節(jié)提到的文章中描述的 OOM kills 。因此,將 request 設(shè)置為與 limit 相同的級別并沒有太大意義。我建議將其設(shè)置為比正常使用高一點(diǎn)——比方說多 20%。

          3、適當(dāng)?shù)?liveness 和 readiness 探針


          3.1 介紹


          了解 Kubernetes 中的 liveness 和 readiness 探針之間的區(qū)別至關(guān)重要。如果這兩個(gè)探針都沒有仔細(xì)實(shí)施,它們可能會降低服務(wù)的整體運(yùn)行,例如導(dǎo)致不必要的重啟。第三種類型的探針,啟動探針,是 Kubernetes 中一個(gè)相對較新的特性。它允許我們避免在 liveness 或 readiness 探針上設(shè)置 initialDelaySeconds ,因此如果您的應(yīng)用程序啟動需要很長時(shí)間,它特別有用。有關(guān) Kubernetes 探針的一般和最佳實(shí)踐的更多詳細(xì)信息,我可以推薦那篇非常有趣的文章。

          Liveness 探針用于決定是否重啟容器。如果應(yīng)用程序因任何原因不可用,有時(shí)重啟容器是有意義的。另一方面,readiness 探針用于確定容器是否可以處理傳入流量。如果一個(gè) pod 被識別為未就緒,它將被從負(fù)載平衡中移除。readiness 探針失敗不會導(dǎo)致 pod 重啟。Web 應(yīng)用程序最典型的 liveness 或 readiness 探針是通過 HTTP 端點(diǎn)實(shí)現(xiàn)的。
          由于 liveness 探針的后續(xù)失敗會導(dǎo)致 pod 重新啟動,因此它不應(yīng)檢查您的應(yīng)用程序集成的可用性。這些事情應(yīng)該由 readiness 驗(yàn)證。

          3.2 配置詳情


          好消息是,最流行的 Java 框架(如 Spring Boot 或 Quarkus)提供了兩種 Kubernetes 探針的自動配置實(shí)現(xiàn)。他們遵循最佳實(shí)踐,因此我們通常不必了解基礎(chǔ)知識。但是,在 Spring Boot 中,除了包含 Actuator 模塊之外,您還需要使用以下屬性啟用它們:

          management:  endpoint:     health:      probes:        enabled: true

          由于 Spring Boot Actuator 提供了多個(gè)端點(diǎn)(例如 metric、 trace),因此最好將其公開在與默認(rèn)端口不同的端口(通常為 8080 )。當(dāng)然,同樣的規(guī)則也適用于其他流行的 Java 框架。另一方面,一個(gè)好的做法是檢查您的主要應(yīng)用程序端口——尤其是在 readiness 探針中。因?yàn)樗x了我們的應(yīng)用程序是否準(zhǔn)備好處理傳入的請求,所以它也應(yīng)該在主端口上監(jiān)聽。它與 liveness probe 看起來正好相反。如果整個(gè)工作線程池都很忙,我不想重新啟動我的應(yīng)用程序。我只是不想在一段時(shí)間內(nèi)收到傳入流量。

          我們還可以自定義 Kubernetes 探針的其他方面。假設(shè)我們的應(yīng)用程序連接到外部系統(tǒng),但我們沒有在我們的 readiness 探針中驗(yàn)證該集成。它并不重要,不會對我們的運(yùn)營狀態(tài)產(chǎn)生直接影響。這是一個(gè)配置,它允許我們在探針中僅包含選定的集成集 (1),并在主服務(wù)器端口上公開 readiness 情況 (2) 。

          spring:  application:    name: sample-spring-boot-on-kubernetes  data:    mongodb:      host: ${MONGO_URL}      port: 27017      username: ${MONGO_USERNAME}      password: ${MONGO_PASSWORD}      database: ${MONGO_DATABASE}      authentication-database: admin
          management: endpoint.health: show-details: always group: readiness: include: mongo # (1) additional-path: server:/readiness # (2) probes: enabled: true server: port: 8081

          幾乎沒有任何應(yīng)用可以不依賴外部解決方案(如數(shù)據(jù)庫、消息代理或其他應(yīng)用程序)。在配置 readiness 探針時(shí),我們應(yīng)該仔細(xì)考慮到該系統(tǒng)的連接設(shè)置。首先你應(yīng)該考慮外部服務(wù)不可用的情況。你將如何處理?我建議將這些超時(shí)減少到較低的值,如下所示。

          spring:  application:    name: sample-spring-kotlin-microservice  datasource:    url: jdbc:postgresql://postgres:5432/postgres    username: postgres    password: postgres123    hikari:      connection-timeout: 2000      initialization-fail-timeout: 0  jpa:    database-platform: org.hibernate.dialect.PostgreSQLDialect  rabbitmq:    host: rabbitmq    port: 5672    connection-timeout: 2000


          4、選擇合適的 JDK


          如果您已經(jīng)使用 Dockerfile 構(gòu)建了鏡像,那么您可能使用的是來自 Docker Hub 的官方 OpenJDK 基礎(chǔ)鏡像。然而,目前,鏡像網(wǎng)站上的公告稱它已被正式棄用,所有用戶都應(yīng)該找到合適的替代品。我想這可能會讓人很困惑,所以你會在這里找到對原因的詳細(xì)解釋。


          好吧,讓我們考慮一下我們應(yīng)該選擇哪個(gè)備選方案。不同的供應(yīng)商提供多種替代品。如果您正在尋找它們之間的詳細(xì)比較,您應(yīng)該訪問以下站點(diǎn)。17版本推薦使用 Eclipse Temurin。

          另一方面,Jib 或 Cloud Native Buildpacks 等最流行的鏡像構(gòu)建工具會自動為您選擇供應(yīng)商。默認(rèn)情況下,Jib 使用 Eclipse Temurin,而 Paketo Buildpacks 使用 Bellsoft Liberica 實(shí)現(xiàn)。當(dāng)然,您可以輕松地覆蓋這些設(shè)置。我認(rèn)為,例如,如果您在與 JDK 提供程序(如 AWS 和 Amazon Corretto)匹配的環(huán)境中運(yùn)行您的應(yīng)用程序,這可能是有意義的。

          假設(shè)我們使用 Paketo Buildpacks 和 Skaffold 在 Kubernetes 上部署 Java 應(yīng)用程序。為了將默認(rèn)的 Bellsoft Liberica buildpack 替換為另一個(gè),我們只需要在 buildpacks 部分中逐字設(shè)置它。下面是一個(gè)利用 Amazon Corretto buildpack 的示例。

          apiVersion: skaffold/v2beta22kind: Configmetadata:  name: sample-spring-boot-on-kubernetesbuild:  artifacts:    - image: piomin/sample-spring-boot-on-kubernetes      buildpacks:        builder: paketobuildpacks/builder:base        buildpacks:          - paketo-buildpacks/amazon-corretto          - paketo-buildpacks/java        env:          - BP_JVM_VERSION=17

          我們還可以使用不同的 JDK 供應(yīng)商輕松測試我們的應(yīng)用程序的性能。如果您正在尋找此類比較的示例,您可以閱讀我描述此類測試和結(jié)果的文章。我使用幾個(gè)可用的 Paketo Java 構(gòu)建包測量了與 Mongo 數(shù)據(jù)庫交互的 Spring Boot 3 應(yīng)用程序的不同 JDK 性能。

          5、考慮遷移到原生編譯


          原生編譯是 Java 世界中真正的“游戲規(guī)則改變者”。但我敢打賭,你們中沒有多少人使用它——尤其是在生產(chǎn)中。當(dāng)然,在將現(xiàn)有應(yīng)用程序遷移到本機(jī)編譯的過程中存在(現(xiàn)在仍然存在)許多挑戰(zhàn)。GraalVM 在構(gòu)建期間執(zhí)行的靜態(tài)代碼分析可能會導(dǎo)致類似 ClassNotFound 或 MethodNotFound 的錯(cuò)誤。為了克服這些挑戰(zhàn),我們需要提供一些提示讓 GraalVM 了解代碼的動態(tài)元素。這些提示的數(shù)量通常取決于庫的數(shù)量和應(yīng)用程序中使用的語言功能的一般數(shù)量。

          像 Quarkus 或 Micronaut 這樣的 Java 框架試圖通過設(shè)計(jì)解決與原生編譯相關(guān)的挑戰(zhàn)。例如,他們盡可能避免使用反射。Spring Boot 還通過 Spring Native 項(xiàng)目大大改進(jìn)了原生編譯支持。因此,我在這方面的建議是,如果您要創(chuàng)建一個(gè)新的應(yīng)用程序,請按照為本機(jī)編譯做好準(zhǔn)備的方式進(jìn)行準(zhǔn)備。例如,使用 Quarkus,您可以簡單地生成一個(gè) Maven 配置,其中包含用于構(gòu)建原生可執(zhí)行文件的專用配置文件。

          <profiles>  <profile>    <id>native</id>    <activation>      <property>        <name>native</name>      </property>    </activation>    <properties>      <skipITs>false</skipITs>      <quarkus.package.type>native</quarkus.package.type>    </properties>  </profile></profiles>

          添加后,您可以使用以下命令進(jìn)行本機(jī)構(gòu)建:

          $ mvn clean package -Pnative

          然后你可以分析在構(gòu)建過程中是否有任何問題。即使您現(xiàn)在不在生產(chǎn)環(huán)境中運(yùn)行原生應(yīng)用程序(例如您的組織不批準(zhǔn)它),您也應(yīng)該將 GraalVM 編譯作為您接受管道中的一個(gè)步驟。您可以使用最流行的框架輕松地為您的應(yīng)用程序構(gòu)建 Java 原生鏡像。例如,使用 Spring Boot,您只需在 Maven pom.xml 中提供以下配置,如下所示:

          <plugin>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-maven-plugin</artifactId>  <executions>    <execution>      <goals>        <goal>build-info</goal>        <goal>build-image</goal>      </goals>    </execution>  </executions>  <configuration>    <image>      <builder>paketobuildpacks/builder:tiny</builder>      <env>        <BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>        <BP_NATIVE_IMAGE_BUILD_ARGUMENTS>          --allow-incomplete-classpath        </BP_NATIVE_IMAGE_BUILD_ARGUMENTS>      </env>    </image>  </configuration></plugin>


          6、正確配置日志記錄


          在編寫 Java 應(yīng)用程序時(shí),日志記錄可能不是您首先考慮的事情。然而,在全局范圍內(nèi),它變得非常重要,因?yàn)槲覀冃枰軌蚴占⒋鎯?shù)據(jù),并最終快速搜索和呈現(xiàn)特定條目。最佳做法是將應(yīng)用程序日志寫入標(biāo)準(zhǔn)輸出 (stdout) 和標(biāo)準(zhǔn)錯(cuò)誤 (stderr) 流。
          Fluentd 是一種流行的開源日志聚合器,它允許您從 Kubernetes 集群收集日志、處理它們,然后將它們發(fā)送到您選擇的數(shù)據(jù)存儲后端。它與 Kubernetes 部署無縫集成。Fluentd 嘗試將數(shù)據(jù)結(jié)構(gòu)化為 JSON 以統(tǒng)一不同來源和目的地的日志記錄。假設(shè)那樣,最好的方法可能是以這種格式準(zhǔn)備日志。使用 JSON 格式,我們還可以輕松地包含用于標(biāo)記日志的附加字段,然后使用各種條件在可視化工具中輕松搜索它們。

          為了將我們的日志格式化為 Fluentd 可讀的 JSON,我們可以在 Maven 依賴項(xiàng)中包含 Logstash Logback 編碼器庫。

          <dependency>   <groupId>net.logstash.logback</groupId>   <artifactId>logstash-logback-encoder</artifactId>   <version>7.2</version></dependency>

          然后我們只需要在文件 logback-spring.xml 中為我們的 Spring Boot 應(yīng)用程序設(shè)置一個(gè)默認(rèn)的控制臺日志 Appender 。

          <configuration>    <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">        <encoder class="net.logstash.logback.encoder.LogstashEncoder"/>    </appender>    <logger name="jsonLogger" additivity="false" level="DEBUG">        <appender-ref ref="consoleAppender"/>    </logger>    <root level="INFO">        <appender-ref ref="consoleAppender"/>    </root></configuration>

          我們是否應(yīng)該避免使用額外的日志 appenders ,而只是將日志打印到標(biāo)準(zhǔn)輸出?根據(jù)我的經(jīng)驗(yàn),答案是——不。您仍然可以使用其他機(jī)制來發(fā)送日志。特別是如果您使用不止一種工具來收集組織中的日志——例如 Kubernetes 上的內(nèi)部堆棧和外部的全局堆棧。就個(gè)人而言,我正在使用一種工具來幫助我解決性能問題,例如消息代理作為代理。在 Spring Boot 中,我們可以輕松地使用 RabbitMQ。只需包括以下 starter:

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

          然后你需要在 logback-spring.xml 中提供一個(gè)類似的 appender 配置:

          <?xml version="1.0" encoding="UTF-8"?><configuration>
          <springProperty name="destination" source="app.amqp.url" />
          <appender name="AMQP" class="org.springframework.amqp.rabbit.logback.AmqpAppender"> <layout> <pattern>{ "time": "%date{ISO8601}", "thread": "%thread", "level": "%level", "class": "%logger{36}", "message": "%message"} </pattern> </layout>
          <addresses>${destination}</addresses> <applicationId>api-service</applicationId> <routingKeyPattern>logs</routingKeyPattern> <declareExchange>true</declareExchange> <exchangeName>ex_logstash</exchangeName>
          </appender>
          <root level="INFO"> <appender-ref ref="AMQP" /> </root>
          </configuration>


          7、創(chuàng)建集成測試


          好的,我知道——它與 Kubernetes 沒有直接關(guān)系。但是由于我們使用 Kubernetes 來管理和編排容器,我們還應(yīng)該對容器進(jìn)行集成測試。幸運(yùn)的是,使用 Java 框架,我們可以大大簡化該過程。例如,Quarkus 允許我們用 @QuarkusIntegrationTest 注釋測試。結(jié)合 Quarkus 容器構(gòu)建功能,它是一個(gè)非常強(qiáng)大的解決方案。我們可以針對包含該應(yīng)用程序的已構(gòu)建鏡像運(yùn)行測試。首先,讓我們包含 Quarkus Jib 模塊:

          <dependency>   <groupId>io.quarkus</groupId>   <artifactId>quarkus-container-image-jib</artifactId></dependency>

          然后我們必須通過在 application.properties 文件中將 quarkus.container-image.build 屬性設(shè)置為 true 來啟用容器構(gòu)建。在測試類中,我們可以使用 @TestHTTPResource 和 @TestHTTPEndpoint 注解注入測試服務(wù)器 URL。然后我們使用 RestClientBuilder 創(chuàng)建一個(gè)客戶端并調(diào)用在容器上啟動的服務(wù)。測試類的名字不是偶然的。為了被自動檢測為集成測試,它有 IT 后綴。

          @QuarkusIntegrationTestpublic class EmployeeControllerIT {
          @TestHTTPEndpoint(EmployeeController.class) @TestHTTPResource URL url;
          @Test void add() { EmployeeService service = RestClientBuilder.newBuilder() .baseUrl(url) .build(EmployeeService.class); Employee employee = new Employee(1L, 1L, "Josh Stevens", 23, "Developer"); employee = service.add(employee); assertNotNull(employee.getId()); }
          @Test public void findAll() { EmployeeService service = RestClientBuilder.newBuilder() .baseUrl(url) .build(EmployeeService.class); Set<Employee> employees = service.findAll(); assertTrue(employees.size() >= 3); }
          @Test public void findById() { EmployeeService service = RestClientBuilder.newBuilder() .baseUrl(url) .build(EmployeeService.class); Employee employee = service.findById(1L); assertNotNull(employee.getId()); }}

          您可以在我之前關(guān)于使用 Quarkus 進(jìn)行高級測試的文章中找到有關(guān)該過程的更多詳細(xì)信息。最終效果如下圖所示。當(dāng)我們在構(gòu)建期間使用 mvn clean verify 命令運(yùn)行測試時(shí),我們的測試在構(gòu)建容器鏡像后執(zhí)行。


          該 Quarkus 功能基于 Testcontainers 框架。我們還可以將 Testcontainer 與 Spring Boot 一起使用。這是 Spring REST 應(yīng)用程序及其與 PostgreSQL 數(shù)據(jù)庫集成的示例測試。

          @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)@Testcontainers@TestMethodOrder(MethodOrderer.OrderAnnotation.class)public class PersonControllerTests {
          @Autowired TestRestTemplate restTemplate;
          @Container static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15.1") .withExposedPorts(5432);
          @DynamicPropertySource static void registerMySQLProperties(DynamicPropertyRegistry registry) { registry.add("spring.datasource.url", postgres::getJdbcUrl); registry.add("spring.datasource.username", postgres::getUsername); registry.add("spring.datasource.password", postgres::getPassword); }
          @Test @Order(1) void add() { Person person = Instancio.of(Person.class) .ignore(Select.field("id")) .create(); person = restTemplate.postForObject("/persons", person, Person.class); Assertions.assertNotNull(person); Assertions.assertNotNull(person.getId()); }
          @Test @Order(2) void updateAndGet() { final Integer id = 1; Person person = Instancio.of(Person.class) .set(Select.field("id"), id) .create(); restTemplate.put("/persons", person); Person updated = restTemplate.getForObject("/persons/{id}", Person.class, id); Assertions.assertNotNull(updated); Assertions.assertNotNull(updated.getId()); Assertions.assertEquals(id, updated.getId()); }
          }


          8、最后的想法


          我希望這篇文章能幫助您在 Kubernetes 上運(yùn)行 Java 應(yīng)用程序時(shí)避免一些常見的陷阱。將其視為我在類似文章中找到的其他人的建議以及我在該領(lǐng)域的個(gè)人經(jīng)驗(yàn)的總結(jié)。

          轉(zhuǎn)自:云云眾生s / 岱軍

          英文鏈接:piotrminkowski.com/2023/02/13/best-practices-for-java-apps-on-kubernetes/

            

          1、社區(qū)糾紛不斷:程序員何苦為難程序員?

          2、該死的單元測試,寫起來到底有多痛?

          3、互聯(lián)網(wǎng)人為什么學(xué)不會擺爛

          4、為什么國外JetBrains做 IDE 就可以養(yǎng)活自己,國內(nèi)不行?區(qū)別在哪?

          5、相比高人氣的Rust、Go,為何 Java、C 在工具層面進(jìn)展緩慢?

          6、讓程序員早點(diǎn)下班的《技術(shù)寫作指南》

          點(diǎn)

          點(diǎn)

          點(diǎn)點(diǎn)

          點(diǎn)在看

          瀏覽 50
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  AV亚洲天堂网 | 国产一级黄色片视频 | 操熟女网站 | xxxx中文 | 操逼操逼操逼操逼操逼 |