GitOps 最佳实践(下)| 基于 Amazon EKS 构建 CI/CD 流水线

Kubernetes
Amazon Elastic Kubernetes Service (EKS)
Amazon CodeCommit
Amazon CodePipeline
0
0
了解了 [GitOps 的概念以及 CI/CD 流水线的架构](https://dev.amazoncloud.cn/column/article/64256901add53077e881c79d),完成了[构建 GitOps 风格的 CI/CD 流水线的前两部分](https://dev.amazoncloud.cn/column/article/642ea6f6a199f057d9f145f9),恭喜开发者们!我们一起在 GitOps 最佳实践的道路上已经实现了大半。接下来,我们一起看看构建 CI/CD 流水线最佳实践的后两个部分: 1. 通过 IaC 部署云基础架构 2. 在 [Amazon EKS](https://aws.amazon.com/cn/eks/?trk=cndc-detail) 集群上部署 Flux CD 3. **利用 Flux CD 部署 GitOps 工作流** 4. **利用 GitOps 工作流实现基于镜像的自动部署** # 利用 Flux CD 部署 GitOps 工作流 对于 GitOps CI/CD 实践下的 EKS 和运行其上的工作负载来说,EKS 集群和工作负载的配置修改和状态变更是源自于 Git 中代码的变化(通过 git push 或者 pull request 触发并完成最终交付,GitOps 推荐使用 pull request),而不再是传统的 CI/CD 流水线中由 CI 引擎所发起的使用 kubectl create/apply 或 helm install/upgrade 直接操作集群实现最终交付。因此我们通过 GitOps 的方式,去简化传统的 CI/CD 流水线,构建更为高效和简洁的 GitOps 方式的 CI/CD 流水线。 > 最佳实践 > > Flux 周期性拉取代码库中的应用配置和部署文件,比较集群当前的应用负载运行状态是否和代码库中的文件所描述的期望一致,当发现二者有差异时,Flux 会自动将差异同步至 EKS 集群,确保工作负载始终按照期望状态运行。 我们通过具体的实践演示,来展示一个具体应用 sock shop 如何在以 GitOps 方式构建的 CI/CD 流水线上实现应用的持续集成和持续交付。 ## 1 Sock Shop 应用介绍 我们使用 sock shop 的在线商店面向用户的部分作为案例的举例应用。它旨在帮助演示和测试微服务和云原生技术。它使用 Spring Boot、Go kit 和 Node 构建,并打包在 Docker 容器中。作为"微服务标准演示",将提供: - 微服务最佳实践 (包括错误示例) - 提供跨平台部署能力 - 展示持续集成/部署的优势 - 展示 DevOps 与微服务的互补 - 为各种编排平台提供“真实”的可测试应用程序 参考架构如下: ![fc9d7b4edbe964c76631e7d2f7cdfca9.png](https://dev-media.amazoncloud.cn/9e199858605e40749f346257a646b12e_fc9d7b4edbe964c76631e7d2f7cdfca9.png "fc9d7b4edbe964c76631e7d2f7cdfca9.png") ## 2 配置管理工具 Kustomize 除了 GitOps 工作流的搭建,我们还需要了解 K8s 中应用的管理方式,传统的基于资源清单(yaml)的管理随着系统复杂度和环境复杂度的提升越来约难以维护。多个业务应用,多个环境(开发,测试,预发,生产),大量的 yaml 资源清单需要维护和管理。虽然 Helm 可以解决部分痛点,比如:统一管理分散的资源文件,应用分发、升级、回滚等。但是 Helm 面对不同环境之间微小的差异处理比较复杂,需要学习复杂的 DSL(模板语法)语法,上手成本较高。所以基于声明式的配置管理工具 Kustomize 应运而生。Kustomize 帮助团队管理不同环境或不同团队的应用的大量 Kubernetes YAML 资源,帮助团队以轻量化的方式管理不同环境的微小差异,使得资源配置可以复用,减少 copy and change 的工作量,同时也很大程度上降低了配置出错率。整个应用配置过程不需要额外学习模板语法。 **Kustomize 通过以下几种方式解决了上述问题:** - kustomize 通过 Base & Overlays 方式方式维护不同环境的应用配置 - kustomize 使用 patch 方式复用 Base 配置,并在 Overlay 描述与 Base 应用配置的差异部分来实现资源复用 - kustomize 管理的都是 Kubernetes 原生 YAML 文件,不需要学习额外的 DSL 语法 ![99842a1a4aa8dced9d31c5f38594fdc9.png](https://dev-media.amazoncloud.cn/65a86019ff2a41179a87f792e9a1c9ff_99842a1a4aa8dced9d31c5f38594fdc9.png "99842a1a4aa8dced9d31c5f38594fdc9.png") 根据官网的描述: kustomize 成为 kubernetes 原生的配置管理,以无模板方式来定制应用的配置。 kustomize 使用 k8s 原生概念帮助创建并复用资源配置(YAML),允许用户以一个应用描述文件 (YAML 文件)为基础(Base YAML),然后通过 Overlay 的方式生成最终部署应用所需的描述文件。 ## 3 微服务多集群配置 了解配置管理工具 Kustomize 后,我们通过 Kustomization(base、overlays)实现多集群部署改造。 在微服务项目中创建两个目录,base 目录存放完整的资源配置 (YAML) 文件,overlays 目录中存放不同环境或者集群的差异配置: ![e3a8e1a1f38cb5d555faa152f8864881.png](https://dev-media.amazoncloud.cn/67b18161b3d749508b85169e4a45203a_e3a8e1a1f38cb5d555faa152f8864881.png "e3a8e1a1f38cb5d555faa152f8864881.png") 例如,本例中微服务的完整配置文件是:complete-demo.yaml,我们把它复制到 base 目录下: ```js cp deploy/kubernetes/complete-demo.yaml deploy/kubernetes/base/complete-demo.yaml ``` 左滑查看更多 然后通过 kustomization.yaml 引用该文件: ```js # deploy/kubernetes/base/kustomization.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - ./complete-demo.yaml ``` 左滑查看更多 对于开发环境 development,如果有差异化的需求,比如服务端口、副本数等需要修改,只需要把差异配置到 overlays/development/kustomization.yaml 文件,而不用复制并修改原有的 complete-demo.yaml。 > 最佳实践 > > flux 在服务部署时会自动根据环境把 base 配置与 overlays 配置合并。我们推荐在 overlays 下同时定义 development,test,prod 多套环境的差异配置。对多环境集群的支持并没有采用多仓库/多分支的策略,而是用的使用不同路径来管理不同的集群。 这也是 Flux 推荐的策略,可以减少代码维护和合并的难度。 ## 4 利用 GitOps 工作流部署微服务 完成微服务的多集群支持后,我们需要让 flux 感知到微服务的配置变更,于是需要把微服务仓库(microservices-repo)所在的 CodeCommit 地址注册到 flux 的仓库(flux-repo)中。 ![24eed93568f90d7e8abf1039f1f13e4a.png](https://dev-media.amazoncloud.cn/59122d4055a64e1dbe37589605b78b69_24eed93568f90d7e8abf1039f1f13e4a.png "24eed93568f90d7e8abf1039f1f13e4a.png") ### 添加微服务仓库地址 我们回到 flux-repo 项目,在应用层 /apps 目录下: ![56c2139068916088dedec59d078b7eff.png](https://dev-media.amazoncloud.cn/dfa74b1f86df45079ab5bf5232f52c2c_56c2139068916088dedec59d078b7eff.png "56c2139068916088dedec59d078b7eff.png") 打开 apps/base/sock-shop/tenant.yaml 文件,把 __MICRO_SERVICES_REPO__ 替换成微服务的地址:https://git-codecommit.xxx.amazonaws.com/v1/repos/microservices-repo: ![5536e351f004e15f877249872017977f.png](https://dev-media.amazoncloud.cn/29b5a1d563ef45e3aadd4e9003239e48_5536e351f004e15f877249872017977f.png "5536e351f004e15f877249872017977f.png") ### 添加 CodeCommit 凭证 找到 “2.3.2 准备 Amazon CodeCommit 凭证” 的账号和密码。执行命令,请先将数据的值转化为 base64 编码: ![f163115b1fc7d591b6c568345f3ed22e.png](https://dev-media.amazoncloud.cn/9b5ac6def199418188a0e45fc28e0797_f163115b1fc7d591b6c568345f3ed22e.png "f163115b1fc7d591b6c568345f3ed22e.png") 然后打开文件 base/sock-shop/basic-access-auth.yaml,用上面生的 base64 编码替换 __BASE64_USERNAME__ 和 __BASE64_PASSWORD__。 ### 开始部署 由于我们把微服务的 Git 地址添加到了 flux 的配置仓库,所以 flux 会自动扫描微服务的配置变更。当我们提交代码后,flux 发现集群中没有部署微服务,与 Git 仓库定义不一致,于是 flux 会自动把微服务部署到集群。 提交代码后,执行命令 flux get kustomizations -watch,等待 flux 更新,当所有 kustomizations 的 READY 状态都为 True 时说明部署完成: ![a4d49136bdad0638d189ef755361a75a.png](https://dev-media.amazoncloud.cn/a1d727b129884a76af464edbc3dd2fe0_a4d49136bdad0638d189ef755361a75a.png "a4d49136bdad0638d189ef755361a75a.png") 查询命名空间 sock-shop 的 pod 和 service,显示如下: ![9f998abfa5a35360502566fde2d6e3ee.png](https://dev-media.amazoncloud.cn/e082d2f58a77421d8bd90d3ee0cce8ce_9f998abfa5a35360502566fde2d6e3ee.png "9f998abfa5a35360502566fde2d6e3ee.png") 访问 Amazon Load Balancer 的 DNS 名称: ![7ed017074edfcf7094a248d8ebd20fa3.png](https://dev-media.amazoncloud.cn/bc84236faeff4ac68aa6fec4a19e5668_7ed017074edfcf7094a248d8ebd20fa3.png "7ed017074edfcf7094a248d8ebd20fa3.png") ## 5 小结 在这里我们详细介绍了一个微服务业务应用:sock shop 在线商店,并且完成了该服务的多集群配置。我们还基于 Flux 搭建了标准的 GitOps 工作流,当配置文件发生变更时,会自动把变更同步到目标集群,以此完成了微服务部署到 EKS 集群。同时,我们介绍了一个实用的 K8s 配置管理工具 Kustomize,我们使用它的特性完成了应用的资源文件管理: - 微服务业务应用介绍 - 配置管理工具 Kustomize 介绍,并通过 Kustomize(base、overlays)实现微服务多集群部署改造 - 搭建 GitOps 工作流,并部署微服务 # 利用 GitOps 工作流实现基于镜像的自动部署 我们选择 sock shop 的其中一个前端微服务 front-end 作为例子,演示 GitOps 工作流实现的代码变更-镜像构建-自定义发布的详细过程。 ## 1 定义 CodePipeline 的 CI 流程 front-end 是一个 Node.js 的纯前端服务,支持 Docker 镜像打包。在前端项目的源码中添加 buildspec.yml 文件,来定义 CodePipeline 中执行的 CI 流程: ```js version: 0.2 phases: pre_build: commands: - echo Logging in to Amazon ECR... - aws --version - AWS_ACCOUNT_ID=`echo \$REPOSITORY_URI|cut -d"." -f1` - AWS_DEFAULT_REGION=`echo \$REPOSITORY_URI|cut -d"." -f4` - echo \$AWS_ACCOUNT_ID \$AWS_DEFAULT_REGION - aws ecr get-login-password --region \$AWS_DEFAULT_REGION | docker login --username AWS --password-stdin \$REPOSITORY_HOST - COMMIT_HASH=\$(echo \$CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7) - IMAGE_TAG=main-\$COMMIT_HASH-\$CODEBUILD_BUILD_NUMBER - echo \$IMAGE_TAG build: commands: - echo Build started on `date` - echo Building the Docker image... - docker build -t \$REPOSITORY_URI:latest . - docker tag \$REPOSITORY_URI:latest \$REPOSITORY_URI:\$IMAGE_TAG post_build: commands: - echo Build completed on `date` - echo Pushing the Docker images... - docker push \$REPOSITORY_URI:latest - docker push \$REPOSITORY_URI:\$IMAGE_TAG ``` 左滑查看更多 > 最佳实践 > > 我们在 CodePipeline 中使用了 CodeBuild 执行 CI 步骤,buildspec.yml 文是 CodeBuild 这一步需要的文件。 该 CI 流程会在 front-end 代码变更时,自动构建镜像,并上传到 ECR 的仓库 weaveworksdemos/front-end。其中镜像的 tag 格式为—[branch]-[commit]-[build number]: ![8aea8e409b50be348fc552f8a3c50d74.png](https://dev-media.amazoncloud.cn/4bd79b4d83d14610a26d833d79b174d7_8aea8e409b50be348fc552f8a3c50d74.png "8aea8e409b50be348fc552f8a3c50d74.png") ## 2 镜像自动更新 在开发测试等可持续集成的敏捷环境中,在构建新服务镜像且发布后,通过人工或脚本更新 GitOps 代码仓库显得过于繁琐。Flux 自身提供了完善且强大的 Git 仓库镜像自动升级功能。镜像自动更新需要确保 Flux 在安装配置时已启用镜像自动更新组件。如未启用,可重复 Flux bootstrap 时加上 --components-extra=image-reflector-controller,image-automation-controller 参数来启用。 要想达到基于镜像的自动更新,我们需要做以下操作: - 注册 front-end 微服务的镜像仓库,该步骤的目的是让 Flux 定期扫描前端项目对应的 ECR 镜像仓库。 - 配置镜像仓库访问凭证,需要给 Flux 访问 ECR 镜像仓库的凭证,它才能有权限读取镜像信息。 - 设置镜像更新策略,大多数情况我们不希望所有版本的镜像变更都触发一次 CD,我们的期望是指定分支(main)的代码变更才触发 CD。这时候需要特殊的更新策略来实现该需求。 接下来我们逐个完成以上的操作。 ### Flux 定位镜像 在项目 microservices-repo 中,我们在 DEV 环境将使用 Kustomization overlays 将 front-end 微服务替换为定制化更新的版本。修改文件 deploy/kubernetes/overlays/development/kustomization.yaml。(注意替换__ACCOUNT_ID__成自己的 ACCOUNT_ID): ```js apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - ../../base images: - name: weaveworksdemos/front-end newName: __ACCOUNT_ID__.dkr.ecr.us-west-2.amazonaws.com/weaveworksdemos/front-end # {"\$imagepolicy": "sock-shop:sock-shop-front-end:name"} newTag: latest # {"\$imagepolicy": "sock-shop:sock-shop-front-end:tag"} ``` 左滑查看更多 *注意:其中的注释 $imagepolicy 是必须的,它们的作用是用来定位的。Flux 发现镜像的版本变更后,需要根据该注释定位并修改文件内容。* ### 注册 front-end 微服务的镜像仓库 在项目 flux-repo 中,新建文件 apps/overlays/development/sock-shop/registry.yaml,注意替换__ACCOUNT_ID__成自己的 ACCOUNT_ID: ```js --- apiVersion: image.toolkit.fluxcd.io/v1beta1 kind: ImageRepository metadata: name: sock-shop-front-end namespace: sock-shop spec: image: __ACCOUNT_ID__.dkr.ecr.xxxx.amazonaws.com/weaveworksdemos/front-end interval: 1m0s ``` 左滑查看更多 ### 配置镜像仓库访问凭证 有两种方法可用于 Flux 中的镜像仓库凭证 : - 自动身份验证机制(image-reflector-controller 自己检索凭证,仅适用于三大云提供商:Amazon ECR、GCP GCR、Azure ACR) - 通过 CronJob 定期刷新凭证(通过 Secret 存储在集群) > 最佳实践 > > 我们使用的 Amazon ECR,选择自动身份验证机制,修改 clusters/dev-cluster/flux-system/kustomization.yaml,通过 patch 机制添加--aws-autologin-for-ecr 参数。 ```js apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - gotk-components.yaml - gotk-sync.yaml patches: - patch: |- - op: add path: /spec/template/spec/containers/0/args/- value: --aws-autologin-for-ecr target: version: v1 group: apps kind: Deployment name: image-reflector-controller namespace: flux-system ``` 左滑查看更多 ### 设置镜像更新策略 新增文件 gitops/apps/overlays/development/sock-shop/policy.yaml。如下规则将匹配 master-d480788-1, master-d480788-2, master-d480788-3 等镜像版本: ```js --- apiVersion: image.toolkit.fluxcd.io/v1beta1 kind: ImagePolicy metadata: name: sock-shop-front-end spec: imageRepositoryRef: name: sock-shop-front-end filterTags: pattern: '^main-[a-fA-F0-9]+-(?P<buidnumber>.*)' extract: '\$buidnumber' policy: numerical: order: asc ``` 左滑查看更多 新增文件 gitops/apps/overlays/development/sock-shop/image-automation.yaml。Flux 自动镜像配置会指定应用配置的 Git 仓库,包括分支、路径等信息: ```js --- apiVersion: image.toolkit.fluxcd.io/v1beta1 kind: ImageUpdateAutomation metadata: name: sock-shop-front-end spec: git: checkout: ref: branch: main commit: author: email: fluxcdbot@users.noreply.github.com name: fluxcdbot messageTemplate: '{{range .Updated.Images}}{{println .}}{{end}}' push: branch: main interval: 5m0s sourceRef: kind: GitRepository name: sock-shop-tenant namespace: sock-shop update: path: ./deploy/kubernetes/overlays/development strategy: Setters ``` 左滑查看更多 ## 3 发布并验证 我们通过修改 front-end 源码,来验证镜像自动更新的全流程。 ### 更新微服务 front-end 代码 修改前端页面的页脚,修改文件—front-end/public/footer.html: ![f2039fbd548963da476b1e196d991e8b.png](https://dev-media.amazoncloud.cn/edffce4047ac4f2189d1d6dc981a528a_f2039fbd548963da476b1e196d991e8b.png "f2039fbd548963da476b1e196d991e8b.png") 提交变更: ![8729f1295fbbb248090dae6a8375723e.png](https://dev-media.amazoncloud.cn/ee17943a7cfa4964b7e165153e948ec7_8729f1295fbbb248090dae6a8375723e.png "8729f1295fbbb248090dae6a8375723e.png") ### 检查 CodePipeline front-end 的代码变更会自动触发 CodePipeline 运行: ![8fd9295141c66687041a1a93c170ed6b.png](https://dev-media.amazoncloud.cn/9d6df09a25ba43d89c05545aadf3c629_8fd9295141c66687041a1a93c170ed6b.png "8fd9295141c66687041a1a93c170ed6b.png") ### 确认 ECR 镜像版本更新 等待 CodePipeline 成功完成后,登录 Amazon ECR 控制台,查询 weaveworksdemos/front-end 镜像版本: ![b10cd6874e2d4a088badff479d650ab5.png](https://dev-media.amazoncloud.cn/45c2c526d5c54ed9a3eedd7477c02cf0_b10cd6874e2d4a088badff479d650ab5.png "b10cd6874e2d4a088badff479d650ab5.png") ### 验证 Flux 镜像信息 通过 flux 查询,ImageRepository 和 ImagePolicy 是否成功检索到最新版本。返回结果应该可以看到已经最新版本 master-1f49071-24: ![d887638b5e27454caf29da455f6b4772.png](https://dev-media.amazoncloud.cn/8bde01f4691741ca8871e4947b2726e6_d887638b5e27454caf29da455f6b4772.png "d887638b5e27454caf29da455f6b4772.png") ### 微服务源码自动更新 flux 自动更新 front-end 的镜像版本。可以看到最新一次 commit 提交人是 fluxcdbot,并且成功修改镜像 tag 为最新版本—master-1f49071-24: ![5b2965327ec66450b2cfd8c448800b2a.png](https://dev-media.amazoncloud.cn/8a5180f64eef48d5863c819be485f110_5b2965327ec66450b2cfd8c448800b2a.png "5b2965327ec66450b2cfd8c448800b2a.png") ### 查询 pod 镜像的版本 用命令 kubectl get pod -n sock-shop | grep front-end 查询 pod 名称,通过以下代码查询 pod 详情,确认镜像版本已经更新: ```js kubectl describe pod/front-end-759476784b-9r2rt -n sock-shop | grep Image ``` 左滑查看更多 更新显示如下: ![f9d7dbc3c0041edde7f26dd8c1ff1d31.png](https://dev-media.amazoncloud.cn/a98aae0ba17c492ba419dfc6a1bfc9b8_f9d7dbc3c0041edde7f26dd8c1ff1d31.png "f9d7dbc3c0041edde7f26dd8c1ff1d31.png") ### 确认静态页面已经是最新版本 ![b08268f1fb3ccd27755bcfb163f6c20c.png](https://dev-media.amazoncloud.cn/a30cea0efc2e4dac813a557a2ef551e1_b08268f1fb3ccd27755bcfb163f6c20c.png "b08268f1fb3ccd27755bcfb163f6c20c.png") ## 4 小结 以上是我们对基于镜像的自动部署全过程的详细介绍。简单来说,我们利用了 Flux 对镜像仓库的持续监听能力,当发现镜像版本变更时自动修改 Git 仓库中的镜像配置,衔接上一小节的标准的 GitOps 工作流完成自动部署: - 通过 CodePipeline 实现 CI 流程,完成前端代码的持续集成 - 通过注释 Flux 定位并修改业务配置文件内容 - 配置 Flux 镜像更新策略,让 Flux 监听指定版本的镜像,完成自动部署 在 GitOps 系列内容中,我们介绍使用 GitOps 工具 FluxCD 实现了管理云环境下的 [Amazon EKS](https://aws.amazon.com/cn/eks/?trk=cndc-detail) 集群的微服务自动发布,实践了 GitOps 流水线的最佳实践。 GitOps 是一种持续交付的方式,包含了一系列的最佳实践,在构建 CI/CD 的工具层面并没有严格限制,只要符合 GitOps 的一些基本原则(Principles of GitOps)即可, 希望大家从中获得了一些启发去构建自己的 GitOps 技术堆栈。 同时,面对复杂的企业场景,还有一些方面还可以持续的优化,例如: 1. 面对关键的线上生产系统,如何安全增量的灰度发布? 2. Sealed Secrets 引入了额外的私钥管理需求,在云计算环境如何改善 GitOps 密钥的管理? 3. 如何将云平台的资源 IaC 同 Kubernetes 内资源 GitOps 协同管理? 4. 如何更加高效的开发 Kubernetes manifests(YAML)? 请持续关注 Build On Cloud 微信公众号,了解更多面向开发者的技术分享和云开发动态!欢迎开发者与我们一起探讨这些问题,可以通过微信留言与我们分享你的经验或看法。 ### 往期推荐 [Generative AI 新世界](https://mp.weixin.qq.com/s/aIDvCOA98n2SeGWVHzqj1w?trk=cndc-detail) [机器学习洞察](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=Mzg5Mzg1NDc2NQ==&action=getalbum&album_id=2674381074877399043&scene=21#wechat_redirect?trk=cndc-detail) [开发者生态](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=Mzg5Mzg1NDc2NQ==&action=getalbum&album_id=2779048236715360257&scene=21#wechat_redirect?trk=cndc-detail) ### 文章作者 ![Betty200.png](https://dev-media.amazoncloud.cn/c5e4dc68d43341aba0176b37d1f37f85_Betty200.png "Betty200.png") **郑予彬** 亚马逊云科技资深开发者布道师 20 年 ICT 行业和数字化转型实践积累,专注于亚马逊云科技云原生、云安全技术领域。18 年架构师经验,致力于为金融、教育、制造以及世界 500 强企业用户提供数据中心建设以及软件定义数据中心等解决方案的咨询及技术落地。 ![阙铭飞200.jpg](https://dev-media.amazoncloud.cn/53956f26d35f4ad9a423ea82a7bdc2f5_%E9%98%99%E9%93%AD%E9%A3%9E200.jpg "阙铭飞200.jpg") **阙铭飞**     亚马逊云科技大中华区解决方案研发中心解决方案架构师 任职亚马逊云科技大中华区解决方案研发中心-解决方案架构师,负责解决方案研发工作。到目前为止有 10 年的工作经验,主要涉及大数据、DevOps、容器化等相关工作。 ![04d37d55edf000706bcc29c5935e9b6c.jpg](https://dev-media.amazoncloud.cn/25dc1a4236bf4f81be125d24d70f7356_04d37d55edf000706bcc29c5935e9b6c.jpg "04d37d55edf000706bcc29c5935e9b6c.jpg")
0
目录
关闭