diff --git a/astro.config.mjs b/astro.config.mjs index ddb637a..da8183e 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -233,6 +233,185 @@ export default defineConfig({ { label: 'AI 工作负载路径', slug: 'ai-on-tke' }, ], }, + { + label: '面向 Agent · CLI', + collapsed: true, + items: [ + { label: '概述与环境准备', slug: 'cli' }, + { + label: 'tccli on TKE', + collapsed: true, + items: [ + { label: 'TKE tccli 参考', slug: 'cli/tke' }, + { + label: '集群配置', + collapsed: true, + items: [ + { + label: '集群管理', + collapsed: true, + items: [ + { label: '创建集群', slug: 'cli/tke/cluster-config/cluster-management/create' }, + { label: '删除集群', slug: 'cli/tke/cluster-config/cluster-management/delete' }, + { label: '升级集群', slug: 'cli/tke/cluster-config/cluster-management/upgrade' }, + { label: '集群扩缩容', slug: 'cli/tke/cluster-config/cluster-management/scale' }, + { label: '连接集群', slug: 'cli/tke/cluster-config/cluster-management/connect' }, + { label: '集群生命周期', slug: 'cli/tke/cluster-config/cluster-management/lifecycle' }, + { label: '集群管理模式说明', slug: 'cli/tke/cluster-config/cluster-management/management-modes' }, + { label: '更改集群操作系统', slug: 'cli/tke/cluster-config/cluster-management/change-os' }, + { label: '自定义控制面组件参数', slug: 'cli/tke/cluster-config/cluster-management/custom-control-plane' }, + ], + }, + ], + }, + ], + }, + { + label: 'tccli on TCR', + collapsed: true, + items: [ + { label: 'TCR tccli 参考', slug: 'cli/tcr' }, + { + label: '快速入门', + collapsed: true, + items: [ + { label: '企业版快速入门', slug: 'cli/tcr/quickstart/enterprise' }, + { label: '个人版快速入门', slug: 'cli/tcr/quickstart/personal' }, + ], + }, + { + label: '操作指南', + collapsed: true, + items: [ + { + label: '实例', + collapsed: true, + items: [ + { label: '创建企业版实例', slug: 'cli/tcr/ops/instances/create' }, + { label: '销毁退还实例', slug: 'cli/tcr/ops/instances/delete' }, + ], + }, + { + label: '访问配置', + collapsed: true, + items: [ + { + label: '访问凭证', + collapsed: true, + items: [ + { label: '用户级账号管理', slug: 'cli/tcr/ops/access/credentials/user-credentials' }, + { label: '服务级账号管理', slug: 'cli/tcr/ops/access/credentials/service-credentials' }, + ], + }, + { + label: '访问权限', + collapsed: true, + items: [ + { label: '权限管理概述', slug: 'cli/tcr/ops/access/permissions/permission-overview' }, + { label: 'CAM 子账号权限', slug: 'cli/tcr/ops/access/permissions/cam-subaccount' }, + ], + }, + { + label: '访问网络', + collapsed: true, + items: [ + { label: '网络控制概述', slug: 'cli/tcr/ops/access/network/network-overview' }, + { label: '公网访问控制', slug: 'cli/tcr/ops/access/network/public-access' }, + { label: '内网访问控制', slug: 'cli/tcr/ops/access/network/private-access' }, + ], + }, + { label: '自定义域名', slug: 'cli/tcr/ops/access/domain/custom-domain' }, + ], + }, + { + label: '镜像创建', + collapsed: true, + items: [ + { label: '管理命名空间', slug: 'cli/tcr/ops/image-creation/namespace' }, + { label: '管理镜像仓库', slug: 'cli/tcr/ops/image-creation/repository' }, + ], + }, + { + label: '镜像分发', + collapsed: true, + items: [ + { label: '同实例多地域复制', slug: 'cli/tcr/ops/image-distribution/cross-region-replication' }, + { label: '跨实例同步镜像', slug: 'cli/tcr/ops/image-distribution/cross-instance-sync' }, + { label: '按需加载容器镜像', slug: 'cli/tcr/ops/image-distribution/accelerated-image' }, + ], + }, + { + label: '镜像安全', + collapsed: true, + items: [ + { label: '容器镜像安全扫描', slug: 'cli/tcr/ops/image-security/vulnerability-scan' }, + { label: '镜像版本不可变', slug: 'cli/tcr/ops/image-security/immutable-tags' }, + { label: '高危镜像部署阻断', slug: 'cli/tcr/ops/image-security/deployment-block' }, + { label: '容器镜像签名', slug: 'cli/tcr/ops/image-security/image-signing' }, + ], + }, + { + label: '镜像清理', + collapsed: true, + items: [ + { label: '清理 COS 存储空间', slug: 'cli/tcr/ops/image-cleanup/cos-cleanup' }, + { label: '自动删除镜像版本', slug: 'cli/tcr/ops/image-cleanup/auto-delete' }, + ], + }, + { + label: 'OCI 制品管理', + collapsed: true, + items: [ + { label: 'OCI 制品管理概述', slug: 'cli/tcr/ops/oci-artifacts/overview' }, + { label: '托管 Helm Chart', slug: 'cli/tcr/ops/oci-artifacts/helm-chart' }, + ], + }, + { + label: 'DevOps', + collapsed: true, + items: [ + { label: '触发器(Webhook)', slug: 'cli/tcr/ops/devops/webhook' }, + ], + }, + { + label: '个人版操作指南', + collapsed: true, + items: [ + { label: '更新登录密码', slug: 'cli/tcr/ops/personal-edition/update-password' }, + { label: '设置镜像清理', slug: 'cli/tcr/ops/personal-edition/image-cleanup' }, + { label: 'CAM API 列表', slug: 'cli/tcr/ops/personal-edition/cam-api-list' }, + { label: '授权方案示例', slug: 'cli/tcr/ops/personal-edition/auth-examples' }, + { label: 'API 变更指南', slug: 'cli/tcr/ops/personal-edition/api-migration' }, + ], + }, + ], + }, + { + label: '实践教程', + collapsed: true, + items: [ + { label: 'TKE Serverless 拉取 TCR 镜像', slug: 'cli/tcr/practices/tke-serverless-pull' }, + { label: 'TKE 插件内网免密拉取', slug: 'cli/tcr/practices/tke-plugin-pull' }, + { label: '从 Harbor 同步镜像', slug: 'cli/tcr/practices/harbor-migration' }, + { label: '混合云多平台同步', slug: 'cli/tcr/practices/hybrid-cloud-sync' }, + { label: '全球多地域同步', slug: 'cli/tcr/practices/global-replication' }, + { label: '自定义域名 + 云联网', slug: 'cli/tcr/practices/custom-domain-ccn' }, + { label: '实例后端存储切换', slug: 'cli/tcr/practices/storage-switch' }, + { label: '个人版迁移至企业版', slug: 'cli/tcr/practices/personal-migration' }, + { label: '个人版域名访问企业版', slug: 'cli/tcr/practices/personal-domain-access' }, + ], + }, + ], + }, + { + label: 'tkectl (产品 CLI)', + collapsed: true, + items: [ + { label: '即将发布', slug: 'cli/tkectl' }, + ], + }, + ], + }, { label: 'Contribute', collapsed: true, diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f7f7ead --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6225 @@ +{ + "name": "tke-workshop", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "tke-workshop", + "version": "1.0.0", + "dependencies": { + "@astrojs/starlight": "0.40.0", + "astro": "6.4.6", + "sharp": "^0.34.0" + }, + "devDependencies": { + "typescript": "^5.8.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@astrojs/compiler": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-4.0.0.tgz", + "integrity": "sha512-eouss7G8ygdZqHuke033VMcVw5HTZUu+PXd/h06DGDUg/jt5btPYPqh66ENWw/mU78rBrf/oeC4oqoBwMtDMNA==", + "license": "MIT" + }, + "node_modules/@astrojs/internal-helpers": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.10.0.tgz", + "integrity": "sha512-Ry2R3VPeIN4uPCSA4xQc+e+vsJXkalKpEbDc07hV+a/o5Bs2N/s/uDcPJH/05L19DKh9tAy7e6JM3YZ6Cxfezw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.4", + "@types/mdast": "^4.0.4", + "js-yaml": "^4.1.1", + "picomatch": "^4.0.4", + "retext-smartypants": "^6.2.0", + "shiki": "^4.0.2", + "smol-toml": "^1.6.0", + "unified": "^11.0.5" + } + }, + "node_modules/@astrojs/markdown-remark": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-7.2.0.tgz", + "integrity": "sha512-+YxmVQu1Bd+MFfSzjq1rOJvD9+nIOJzz5YIIhdIH01RrxRkKbyKoEgyIqP3yv51MhzMDgd79QaPv+kCVPT8vHw==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.10.0", + "@astrojs/prism": "4.0.2", + "github-slugger": "^2.0.0", + "hast-util-from-html": "^2.0.3", + "hast-util-to-text": "^4.0.2", + "mdast-util-definitions": "^6.0.0", + "rehype-raw": "^7.0.0", + "rehype-stringify": "^10.0.1", + "remark-gfm": "^4.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "remark-smartypants": "^3.0.2", + "unified": "^11.0.5", + "unist-util-remove-position": "^5.0.0", + "unist-util-visit": "^5.1.0", + "unist-util-visit-parents": "^6.0.2", + "vfile": "^6.0.3" + } + }, + "node_modules/@astrojs/mdx": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-6.0.3.tgz", + "integrity": "sha512-+4P3ZvwsRAqAbBgY+uZMewFo3ficlIBPZfu/Luk+v4ia/ZOuFhpsw7r+7672uT2Fc1UPdp7yW0eU5egvSq0wbw==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.10.0", + "@astrojs/markdown-remark": "7.2.0", + "@mdx-js/mdx": "^3.1.1", + "acorn": "^8.16.0", + "es-module-lexer": "^2.0.0", + "estree-util-visit": "^2.0.0", + "hast-util-to-html": "^9.0.5", + "piccolore": "^0.1.3", + "rehype-raw": "^7.0.0", + "remark-gfm": "^4.0.1", + "remark-smartypants": "^3.0.2", + "source-map": "^0.7.6", + "unist-util-visit": "^5.1.0", + "vfile": "^6.0.3" + }, + "engines": { + "node": ">=22.12.0" + }, + "peerDependencies": { + "@astrojs/markdown-satteri": "0.3.0", + "astro": "^6.4.0" + }, + "peerDependenciesMeta": { + "@astrojs/markdown-satteri": { + "optional": true + } + } + }, + "node_modules/@astrojs/prism": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-4.0.2.tgz", + "integrity": "sha512-KTivpmnz6lDsC6o9H4+DNm2SrE/GHzw8cNAvEJwAvUT+eoaEnn/4NtbDNfRRaxaJHdp15gf+tfHAWiXR4wB3BA==", + "license": "MIT", + "dependencies": { + "prismjs": "^1.30.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@astrojs/sitemap": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.7.3.tgz", + "integrity": "sha512-f8euLVsyeAmAkSm/1M2Kb8sL8byQmfgbvBNaHFItCheTj/IpiJYSEWVcqDHZ/yEHxiS7+w87mQkzwZaPHmk5GA==", + "license": "MIT", + "dependencies": { + "sitemap": "^9.0.0", + "stream-replace-string": "^2.0.0", + "zod": "^4.3.6" + } + }, + "node_modules/@astrojs/starlight": { + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/@astrojs/starlight/-/starlight-0.40.0.tgz", + "integrity": "sha512-H1NBIXx4Xw6YzKMsoMkazYxFgnTTj6pD4IReUGWj1fqw82AOAgj+WnZLpTDWRExf3b9ZM7Popbl583i4IvDNVQ==", + "license": "MIT", + "dependencies": { + "@astrojs/markdown-remark": "^7.2.0", + "@astrojs/mdx": "^6.0.2", + "@astrojs/sitemap": "^3.7.2", + "@pagefind/default-ui": "^1.3.0", + "@types/hast": "^3.0.4", + "@types/js-yaml": "^4.0.9", + "@types/mdast": "^4.0.4", + "astro-expressive-code": "^0.43.1", + "bcp-47": "^2.1.0", + "hast-util-from-html": "^2.0.3", + "hast-util-select": "^6.0.4", + "hast-util-to-string": "^3.0.1", + "hastscript": "^9.0.1", + "i18next": "^26.0.7", + "js-yaml": "^4.1.1", + "klona": "^2.0.6", + "magic-string": "^0.30.21", + "mdast-util-directive": "^3.1.0", + "mdast-util-to-markdown": "^2.1.2", + "mdast-util-to-string": "^4.0.0", + "pagefind": "^1.5.2", + "rehype": "^13.0.2", + "rehype-format": "^5.0.1", + "remark-directive": "^4.0.0", + "ultrahtml": "^1.6.0", + "unified": "^11.0.5", + "unist-util-visit": "^5.1.0", + "vfile": "^6.0.3" + }, + "peerDependencies": { + "@astrojs/markdown-satteri": "^0.2.0", + "astro": "^6.4.5" + }, + "peerDependenciesMeta": { + "@astrojs/markdown-satteri": { + "optional": true + } + } + }, + "node_modules/@astrojs/telemetry": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.2.tgz", + "integrity": "sha512-j8DNruA8ors99Al39RYZPJK4DC1bKkoNm93mAMuBhY9TCNC4R8n1q7ovFnJ5qhGh5Lsh7pa1gpQVpYpsJPeTHQ==", + "license": "MIT", + "dependencies": { + "ci-info": "^4.4.0", + "dset": "^3.1.4", + "is-docker": "^4.0.0", + "is-wsl": "^3.1.1", + "which-pm-runs": "^1.1.0" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@capsizecss/unpack": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-4.0.1.tgz", + "integrity": "sha512-CuNiSqg7+e1cO/GjffyMOm5Tt2jUF9CWHHnvQ/UkqvtkGfHdgwEC0wpmq7fkN3gxwpRnrAN0WzO3vREKmNolMQ==", + "license": "MIT", + "dependencies": { + "fontkitten": "^1.0.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@clack/core": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.4.2.tgz", + "integrity": "sha512-0Ty/1Gfm+Kb07sXcuESjyKfwEhSy4Ns1AgeEisHb/bDY5fWme0tTeTkU14T1Gmcs17YIjB/teiDe4uaCghbYqQ==", + "license": "MIT", + "dependencies": { + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, + "node_modules/@clack/prompts": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.6.0.tgz", + "integrity": "sha512-EYlRokl8szrP9Z25qT5aepMdBjzBvHF9ZEhzIiUBc9guz/T31EqRgvD0QSgZcpE93xiwrr+OkB4nz0BZyF6fSA==", + "license": "MIT", + "dependencies": { + "@clack/core": "1.4.2", + "fast-string-width": "^3.0.2", + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz", + "integrity": "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@expressive-code/core": { + "version": "0.43.1", + "resolved": "https://registry.npmjs.org/@expressive-code/core/-/core-0.43.1.tgz", + "integrity": "sha512-H4rUJXKyS6y2q9Ig9bIp3dFhWhkZQIeH/jRGl3DROlslrGvfD4OC9qzmvKEFExm+/DtdvvHMQ8/Olmrcfxp+wQ==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^4.0.4", + "hast-util-select": "^6.0.2", + "hast-util-to-html": "^9.0.1", + "hast-util-to-text": "^4.0.1", + "hastscript": "^9.0.0", + "postcss": "^8.4.38", + "postcss-nested": "^6.0.1", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.1" + } + }, + "node_modules/@expressive-code/plugin-frames": { + "version": "0.43.1", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-frames/-/plugin-frames-0.43.1.tgz", + "integrity": "sha512-tENfLw2UDeq5h749tTLvUtQYvgjIiQc6W7PBCR5xQ4yuE/QftManKJfUQjwJo6RRsAimVQDN4alhFTJ3aq1Khg==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.43.1" + } + }, + "node_modules/@expressive-code/plugin-shiki": { + "version": "0.43.1", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-shiki/-/plugin-shiki-0.43.1.tgz", + "integrity": "sha512-NdceinYEROXODNgB/ix+7oCdIg+nGyok+E+p2lU9YlWd1xKshXdXpmmptKfkuU27MJ5jjnfhMCI78YYBGi9GtQ==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.43.1", + "shiki": "^4.0.2" + } + }, + "node_modules/@expressive-code/plugin-text-markers": { + "version": "0.43.1", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-text-markers/-/plugin-text-markers-0.43.1.tgz", + "integrity": "sha512-JWf8wdbZSNoGY4TFv3lmt3/NNDaCP7iYL6rRYD05g8YYjKL62hKUHLl5+B47+v0+bqbuMhXDN7qz2wywFUvMkg==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.43.1" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@mdx-js/mdx": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz", + "integrity": "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdx": "^2.0.0", + "acorn": "^8.0.0", + "collapse-white-space": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-util-scope": "^1.0.0", + "estree-walker": "^3.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "markdown-extensions": "^2.0.0", + "recma-build-jsx": "^1.0.0", + "recma-jsx": "^1.0.0", + "recma-stringify": "^1.0.0", + "rehype-recma": "^1.0.0", + "remark-mdx": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "source-map": "^0.7.0", + "unified": "^11.0.0", + "unist-util-position-from-estree": "^2.0.0", + "unist-util-stringify-position": "^4.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@oslojs/encoding": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", + "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", + "license": "MIT" + }, + "node_modules/@pagefind/darwin-arm64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.5.2.tgz", + "integrity": "sha512-MXpI+7HsAdPkvJ0gk9xj9g541BCqBZOBbdwj9g6lB5LCj6kSV6nqDSjzcAJwvOsfu0fjwvC8hQU+ecfhp+MpiQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/darwin-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.5.2.tgz", + "integrity": "sha512-IojxFWMEJe0RQ7PQ3KXQsPIImNsbpPYpoZ+QUDrL8fAl/O27IX+LVLs74/UzEZy5uA2LD8Nz1AiwKr72vrkZQw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/default-ui": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/default-ui/-/default-ui-1.5.2.tgz", + "integrity": "sha512-pm1LMnQg8N2B3n2TnjKlhaFihpz6zTiA4HiGQ6/slKO/+8K9CAU5kcjdSSPgpuk1PMuuN4hxLipUIifnrkl3Sg==", + "license": "MIT" + }, + "node_modules/@pagefind/freebsd-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/freebsd-x64/-/freebsd-x64-1.5.2.tgz", + "integrity": "sha512-7EVzo9+0w+2cbe671BtMj10UlNo83I+HrLVLfRxO731svHRJKUfJ/mo05gU14pe9PCfpKNQT8FS3Xc/oDN6pOA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@pagefind/linux-arm64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.5.2.tgz", + "integrity": "sha512-Ovt9+K35sqzn8H3ZMXGwls4TD/wMJuvRtShHIsmUQREmaxjrDEX7gHckRCrwYJ4XE1H1p6HkLz3wukrAnsfXQw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/linux-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.5.2.tgz", + "integrity": "sha512-V+tFqHKXhQKq/WqPBD67AFy7scn1/aZID00ws4fSDd+1daSi5UHR9VVlRrOUYKxn3VuFQYRD7lYXdZK1WED1YA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/windows-arm64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/windows-arm64/-/windows-arm64-1.5.2.tgz", + "integrity": "sha512-hN9Nh90fNW61nNRCW9ZyQrAj/mD0eRvmJ8NlTUzkbuW8kIzGJUi3cxjFkEcMZ5h/8FsKWD/VcouZl4yo1F7B6g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@pagefind/windows-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.5.2.tgz", + "integrity": "sha512-Fa2Iyw7kaDRzGMfNYNUXNW2zbL5FQVDgSOcbDHdzBrDEdpqOqg8TcZ68F22ol6NJ9IGzvUdmeyZypLW5dyhqsg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/pluginutils": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.4.0.tgz", + "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.2.0.tgz", + "integrity": "sha512-Hc87Ab1Ld/vEbZRCbwx344I5v+4RU8CVToUTRkqXL1+TjbuOp9U5Xa0M23V4GEWHxVn+yO5otb+HkQVm3ptWQQ==", + "license": "MIT", + "dependencies": { + "@shikijs/primitive": "4.2.0", + "@shikijs/types": "4.2.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.2.0.tgz", + "integrity": "sha512-fjETeq1k5ffyXqRgS6+3hpvqseLalp1kjNfRbXpUgWR8FpZ1CmQfiNHovc5lncYjt/Vg5JK/WJEmLahjwMa0og==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.2.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.6" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.2.0.tgz", + "integrity": "sha512-hTorK1dffPkpbMUk6Z+828PgRo7d07HbnizoP0hNPFjhxMHctj0Px/qoHeGMYafc6ju+u9iMldN4JbVzNQM++g==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.2.0", + "@shikijs/vscode-textmate": "^10.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/langs": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.2.0.tgz", + "integrity": "sha512-bwrVRlJ0wUhZxAbVdvBbv2TTC9yLsh4C/IO5Ofz0T8MQntgDvyVnkbjw9vi50r1kx7RCIJdnJnjZAwmAsXFLZQ==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/primitive": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.2.0.tgz", + "integrity": "sha512-NOq+DtUkVBJtZMVXL5A0vI0Xk8nvDYaXetFHSJFlOqjDZIVhIPRYFdGkSoElDqNuegikcc3A76SNUa8dTqtAYA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.2.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/themes": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.2.0.tgz", + "integrity": "sha512-RX8IHYeLv8Cu2W6ruc3RxUqWn0IYCqSrMBzi/uRGAmfyDNOnNO5BF/Px7o97n4XTpmFTo5GbRaazuOWj+2ak2w==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/types": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.2.0.tgz", + "integrity": "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdx": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.14.tgz", + "integrity": "sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/nlcst": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-2.0.3.tgz", + "integrity": "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/node": { + "version": "24.13.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz", + "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", + "license": "ISC" + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-iterate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz", + "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/astring": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "license": "MIT", + "bin": { + "astring": "bin/astring" + } + }, + "node_modules/astro": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/astro/-/astro-6.4.6.tgz", + "integrity": "sha512-48OBTBKR9ctbf+DQxpOuxGl8ebfn59zTuNQMBzptmG/Mi/H8IdfMSbJgGuX1I/4U6g9yazG1p4BHlf4+2hWU4Q==", + "license": "MIT", + "dependencies": { + "@astrojs/compiler": "^4.0.0", + "@astrojs/internal-helpers": "0.10.0", + "@astrojs/markdown-remark": "7.2.0", + "@astrojs/telemetry": "3.3.2", + "@capsizecss/unpack": "^4.0.0", + "@clack/prompts": "^1.1.0", + "@oslojs/encoding": "^1.1.0", + "@rollup/pluginutils": "^5.3.0", + "aria-query": "^5.3.2", + "axobject-query": "^4.1.0", + "ci-info": "^4.4.0", + "clsx": "^2.1.1", + "common-ancestor-path": "^2.0.0", + "cookie": "^1.1.1", + "devalue": "^5.8.1", + "diff": "^8.0.3", + "dset": "^3.1.4", + "es-module-lexer": "^2.0.0", + "esbuild": "^0.27.3", + "flattie": "^1.1.1", + "fontace": "~0.4.1", + "get-tsconfig": "5.0.0-beta.4", + "github-slugger": "^2.0.0", + "html-escaper": "3.0.3", + "http-cache-semantics": "^4.2.0", + "js-yaml": "^4.1.1", + "jsonc-parser": "^3.3.1", + "magic-string": "^0.30.21", + "magicast": "^0.5.2", + "mrmime": "^2.0.1", + "neotraverse": "^0.6.18", + "obug": "^2.1.1", + "p-limit": "^7.3.0", + "p-queue": "^9.1.0", + "package-manager-detector": "^1.6.0", + "piccolore": "^0.1.3", + "picomatch": "^4.0.4", + "rehype": "^13.0.2", + "semver": "^7.7.4", + "shiki": "^4.0.2", + "smol-toml": "^1.6.0", + "svgo": "^4.0.1", + "tinyclip": "^0.1.12", + "tinyexec": "^1.0.4", + "tinyglobby": "^0.2.15", + "ultrahtml": "^1.6.0", + "unifont": "~0.7.4", + "unist-util-visit": "^5.1.0", + "unstorage": "^1.17.5", + "vfile": "^6.0.3", + "vite": "^7.3.2", + "vitefu": "^1.1.2", + "xxhash-wasm": "^1.1.0", + "yargs-parser": "^22.0.0", + "zod": "^4.3.6" + }, + "bin": { + "astro": "bin/astro.mjs" + }, + "engines": { + "node": ">=22.12.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/astrodotbuild" + }, + "optionalDependencies": { + "sharp": "^0.34.0" + } + }, + "node_modules/astro-expressive-code": { + "version": "0.43.1", + "resolved": "https://registry.npmjs.org/astro-expressive-code/-/astro-expressive-code-0.43.1.tgz", + "integrity": "sha512-xddgwQxFRwpnnAnU7kSfrO82SsOAq7sQrYpXxVcrN9k/0aqNlTH2+mLrOMm1wXm6jdFKepst3hd8/qWojwuunw==", + "license": "MIT", + "dependencies": { + "rehype-expressive-code": "^0.43.1" + }, + "peerDependencies": { + "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.0.tgz", + "integrity": "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47-match": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", + "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/collapse-white-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", + "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/common-ancestor-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz", + "integrity": "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">= 18" + } + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cookie-es": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.3.tgz", + "integrity": "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==", + "license": "MIT" + }, + "node_modules/crossws": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", + "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", + "license": "MIT", + "dependencies": { + "uncrypto": "^0.1.3" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-selector-parser": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.3.0.tgz", + "integrity": "sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "license": "CC0-1.0" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.1.tgz", + "integrity": "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==", + "license": "MIT" + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/direction": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz", + "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==", + "license": "MIT", + "bin": { + "direction": "cli.js" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "license": "MIT" + }, + "node_modules/esast-util-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", + "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esast-util-from-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", + "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "acorn": "^8.0.0", + "esast-util-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-util-attach-comments": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", + "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-build-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", + "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-walker": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-scope": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", + "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-to-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", + "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "astring": "^1.8.0", + "source-map": "^0.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/expressive-code": { + "version": "0.43.1", + "resolved": "https://registry.npmjs.org/expressive-code/-/expressive-code-0.43.1.tgz", + "integrity": "sha512-JdOzanoU825iNvslmk6Kg8Ro61eSHmDK2Zz7BynOxObVrpIXZNzrIZOwQO2uDQcGsjSYShL/8vTrXgeWYnq3NA==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.43.1", + "@expressive-code/plugin-frames": "^0.43.1", + "@expressive-code/plugin-shiki": "^0.43.1", + "@expressive-code/plugin-text-markers": "^0.43.1" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/flattie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", + "integrity": "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/fontace": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/fontace/-/fontace-0.4.1.tgz", + "integrity": "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==", + "license": "MIT", + "dependencies": { + "fontkitten": "^1.0.2" + } + }, + "node_modules/fontkitten": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fontkitten/-/fontkitten-1.0.3.tgz", + "integrity": "sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==", + "license": "MIT", + "dependencies": { + "tiny-inflate": "^1.0.3" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "5.0.0-beta.4", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-5.0.0-beta.4.tgz", + "integrity": "sha512-7nF7C9fIPFEMHgEMEfgIlO9wDdZ8CyHw27rWciFZfHvHDReIiPhsYuzPRXsfvBCqFy1l8RRyyWV7QLM+ZhUJsQ==", + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "engines": { + "node": ">=20.20.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "license": "ISC" + }, + "node_modules/h3": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.11.tgz", + "integrity": "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==", + "license": "MIT", + "dependencies": { + "cookie-es": "^1.2.3", + "crossws": "^0.3.5", + "defu": "^6.1.6", + "destr": "^2.0.5", + "iron-webcrypto": "^1.2.1", + "node-mock-http": "^1.0.4", + "radix3": "^1.1.2", + "ufo": "^1.6.3", + "uncrypto": "^0.1.3" + } + }, + "node_modules/hast-util-embedded": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz", + "integrity": "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-format": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hast-util-format/-/hast-util-format-1.1.0.tgz", + "integrity": "sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-minify-whitespace": "^1.0.0", + "hast-util-phrasing": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "html-whitespace-sensitive-tag-names": "^3.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-has-property": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", + "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-body-ok-link": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.1.tgz", + "integrity": "sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-minify-whitespace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz", + "integrity": "sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-is-body-ok-link": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-select": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.4.tgz", + "integrity": "sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "bcp-47-match": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "css-selector-parser": "^3.0.0", + "devlop": "^1.0.0", + "direction": "^2.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "nth-check": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-estree": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", + "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-attach-comments": "^3.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz", + "integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-string": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", + "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/html-whitespace-sensitive-tag-names": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-whitespace-sensitive-tag-names/-/html-whitespace-sensitive-tag-names-3.0.1.tgz", + "integrity": "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause" + }, + "node_modules/i18next": { + "version": "26.3.1", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.3.1.tgz", + "integrity": "sha512-txQqd5EULsqEh9OJqRH15aCaOuy/nLJyhw5EHCSKLKJE1aBbb3Zve2+uQIxgWhPm1QqUQoWyQBm2kfmmIrzkcQ==", + "funding": [ + { + "type": "individual", + "url": "https://www.locize.com/i18next" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + }, + { + "type": "individual", + "url": "https://www.locize.com" + } + ], + "license": "MIT", + "peerDependencies": { + "typescript": "^5 || ^6" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/iron-webcrypto": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", + "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/brc-dd" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-4.0.0.tgz", + "integrity": "sha512-LHE+wROyG/Y/0ZnbktRCoTix2c1RhgWaZraMZ8o1Q7zCh0VSrICJQO5oqIIISrcSBtrXv0o233w1IYwsWCjTzA==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-yaml": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "license": "MIT" + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz", + "integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "node_modules/markdown-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", + "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-definitions": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz", + "integrity": "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-directive": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", + "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "license": "CC0-1.0" + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-4.0.0.tgz", + "integrity": "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-expression": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", + "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", + "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-md": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", + "license": "MIT", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^3.0.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs-esm": "^3.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", + "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-events-to-acorn": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", + "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/neotraverse": { + "version": "0.6.18", + "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz", + "integrity": "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/nlcst-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz", + "integrity": "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "license": "MIT" + }, + "node_modules/node-mock-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.4.tgz", + "integrity": "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/obug": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz", + "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/ofetch": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz", + "integrity": "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==", + "license": "MIT", + "dependencies": { + "destr": "^2.0.5", + "node-fetch-native": "^1.6.7", + "ufo": "^1.6.1" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "license": "MIT" + }, + "node_modules/oniguruma-parser": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz", + "integrity": "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==", + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.6.tgz", + "integrity": "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.2", + "regex": "^6.1.0", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/p-limit": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.3.0.tgz", + "integrity": "sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.2.1" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.0.tgz", + "integrity": "sha512-7NED7xhQ74Ngp4JP/2e0VZHp7vSWfJfqeiR92jPgxsz6m0Se4P03YoTKa9dDXyZ3r6P616gUXttrB6nnHYKang==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.4", + "p-timeout": "^7.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", + "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "license": "MIT" + }, + "node_modules/pagefind": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.5.2.tgz", + "integrity": "sha512-XTUaK0hXMCu2jszWE584JGQT7y284TmMV9l/HX3rnG5uo3rHI/uHU56XTyyyPFjeWEBxECbAi0CaFDJOONtG0Q==", + "license": "MIT", + "bin": { + "pagefind": "lib/runner/bin.cjs" + }, + "optionalDependencies": { + "@pagefind/darwin-arm64": "1.5.2", + "@pagefind/darwin-x64": "1.5.2", + "@pagefind/freebsd-x64": "1.5.2", + "@pagefind/linux-arm64": "1.5.2", + "@pagefind/linux-x64": "1.5.2", + "@pagefind/windows-arm64": "1.5.2", + "@pagefind/windows-x64": "1.5.2" + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/parse-latin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz", + "integrity": "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "@types/unist": "^3.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-modify-children": "^4.0.0", + "unist-util-visit-children": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/piccolore": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz", + "integrity": "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==", + "license": "ISC" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/property-information": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/radix3": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", + "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/recma-build-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", + "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-build-jsx": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-jsx": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.1.tgz", + "integrity": "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==", + "license": "MIT", + "dependencies": { + "acorn-jsx": "^5.0.0", + "estree-util-to-js": "^2.0.0", + "recma-parse": "^1.0.0", + "recma-stringify": "^1.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/recma-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", + "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "esast-util-from-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", + "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-to-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT" + }, + "node_modules/rehype": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/rehype/-/rehype-13.0.2.tgz", + "integrity": "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "rehype-parse": "^9.0.0", + "rehype-stringify": "^10.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-expressive-code": { + "version": "0.43.1", + "resolved": "https://registry.npmjs.org/rehype-expressive-code/-/rehype-expressive-code-0.43.1.tgz", + "integrity": "sha512-CUOGQVlUcSMSXZgpcq9xL6B+dZqnI3w1R6EZj932XpGgj2Hmy7H6oMqa9W/Z7X2HOILWLWhqu1b9kuYcD+nd6w==", + "license": "MIT", + "dependencies": { + "expressive-code": "^0.43.1" + } + }, + "node_modules/rehype-format": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/rehype-format/-/rehype-format-5.0.1.tgz", + "integrity": "sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-format": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-parse": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", + "integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-html": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-recma": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", + "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "hast-util-to-estree": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-stringify": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz", + "integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-html": "^9.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-directive": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-4.0.0.tgz", + "integrity": "sha512-7sxn4RfF1o3izevPV1DheyGDD6X4c9hrGpfdUpm7uC++dqrnJxIZVkk7CoKqcLm0VUMAuOol7Mno3m6g8cfMuA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-directive": "^3.0.0", + "micromark-extension-directive": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.1.tgz", + "integrity": "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==", + "license": "MIT", + "dependencies": { + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-smartypants": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-3.0.2.tgz", + "integrity": "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==", + "license": "MIT", + "dependencies": { + "retext": "^9.0.0", + "retext-smartypants": "^6.0.0", + "unified": "^11.0.4", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/retext": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz", + "integrity": "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "retext-latin": "^4.0.0", + "retext-stringify": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-latin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-latin/-/retext-latin-4.0.0.tgz", + "integrity": "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "parse-latin": "^7.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-6.2.0.tgz", + "integrity": "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-stringify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-stringify/-/retext-stringify-4.0.0.tgz", + "integrity": "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/shiki": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.2.0.tgz", + "integrity": "sha512-hjNax6o/ylDy9lefQEaSDtzaT3iVNtZ3WmpQnbuQNoG4xvnSKf2kSKbihZVO4JRG1TTMejs7CmNRYlWgAL66pQ==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "4.2.0", + "@shikijs/engine-javascript": "4.2.0", + "@shikijs/engine-oniguruma": "4.2.0", + "@shikijs/langs": "4.2.0", + "@shikijs/themes": "4.2.0", + "@shikijs/types": "4.2.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/sitemap": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-9.0.1.tgz", + "integrity": "sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ==", + "license": "MIT", + "dependencies": { + "@types/node": "^24.9.2", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.4.1" + }, + "bin": { + "sitemap": "dist/esm/cli.js" + }, + "engines": { + "node": ">=20.19.5", + "npm": ">=10.8.2" + } + }, + "node_modules/smol-toml": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.7.0.tgz", + "integrity": "sha512-aqVvWoyO21L23mb+drl4RmMXbf6N7FdHjAhTRA9ZBL7apWBgfWC16KjrASI+1p9GAroljyMHj6fK67i0UiTNvQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stream-replace-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stream-replace-string/-/stream-replace-string-2.0.0.tgz", + "integrity": "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==", + "license": "MIT" + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/svgo": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz", + "integrity": "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==", + "license": "MIT", + "dependencies": { + "commander": "^11.1.0", + "css-select": "^5.1.0", + "css-tree": "^3.0.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.1.1", + "sax": "^1.5.0" + }, + "bin": { + "svgo": "bin/svgo.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, + "node_modules/tinyclip": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/tinyclip/-/tinyclip-0.1.15.tgz", + "integrity": "sha512-uo33abH+Ays0xYaDysoBt494Hb3hsEczMpcC0MwFl773pazORx4fmvKhclhR1wonUbB6vvpRsvVMwnhfqeMc+A==", + "license": "MIT", + "engines": { + "node": "^16.14.0 || >= 17.3.0" + } + }, + "node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "license": "MIT" + }, + "node_modules/ultrahtml": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.6.0.tgz", + "integrity": "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==", + "license": "MIT" + }, + "node_modules/uncrypto": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "license": "MIT" + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unifont": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.7.4.tgz", + "integrity": "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg==", + "license": "MIT", + "dependencies": { + "css-tree": "^3.1.0", + "ofetch": "^1.5.1", + "ohash": "^2.0.11" + } + }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-modify-children": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-4.0.0.tgz", + "integrity": "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "array-iterate": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-children": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-3.0.0.tgz", + "integrity": "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unstorage": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.5.tgz", + "integrity": "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^5.0.0", + "destr": "^2.0.5", + "h3": "^1.15.10", + "lru-cache": "^11.2.7", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.3" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6 || ^7 || ^8", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1 || ^2 || ^3", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.5.tgz", + "integrity": "sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", + "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/which-pm-runs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", + "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/xxhash-wasm": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", + "integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==", + "license": "MIT" + }, + "node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/src/content/docs/cli/index.md b/src/content/docs/cli/index.md new file mode 100644 index 0000000..e954e3a --- /dev/null +++ b/src/content/docs/cli/index.md @@ -0,0 +1,78 @@ +--- +title: "面向 Agent · CLI 操作指南" +description: "通过 tccli 和产品 CLI (tkectl/tcrctl) 以 Agent-Native 方式操作腾讯云 TKE 和 TCR。" +--- + +本模块提供 TKE 和 TCR 的 **Agent-Native CLI 操作文档**。每篇文档面向两类读者: + +- **人类工程师** — 精确的 `tccli` 命令示例、参数表、真实输出 +- **LLM Agent** — 结构化 JSON 输出、稳定退出码、`--dry-run` 验证 + +## 两层命令模型 + +| 层 | 命令 | 说明 | +|----|------|------| +| **L0 — API 直通** | `tccli {tke\|tcr} {Action}` | 覆盖全部 386 个接口,零遗漏 | +| **L1 — 产品 CLI** | `tkectl` / `tcrctl` | 任务导向,带状态轮询和 teaching errors | + +当前阶段:L0 (tccli) 文档已完成并验证,L1 (产品 CLI) 为 **P1 占位页**。 + +## 环境准备 + +以下步骤只需执行一次,适用于所有 CLI 操作页。 + +### 安装 tccli + +```bash +pip install tccli +tccli --version +``` + +### 配置凭证与地域 + +```bash +tccli configure set secretId secretKey region ap-guangzhou +``` + +或交互式: + +```bash +tccli configure +``` + +### 凭证优先级 + +同一设置在多处出现时,`tccli` 按以下优先级解析: + +1. **命令行参数** (最高) +2. **`~/.tccli/default.configure`** (配置文件) +3. **环境变量** (`TENCENTCLOUD_SECRET_ID`, `TENCENTCLOUD_SECRET_KEY`, `TENCENTCLOUD_REGION`, …) + +### VPC 前置检查 + +许多 TKE 创建流程需要已有 VPC 和子网,操作前先确认: + +```bash +tccli vpc DescribeVpcs --region ap-guangzhou --output json +tccli vpc DescribeSubnets --region ap-guangzhou --output json +``` + +### CAM 和计费 + +- 完成实名认证,授予 TKE/CVM/VPC CAM 角色 +- 确认配额与计费:[TKE 计费说明](https://cloud.tencent.com/document/product/457/9082) +- TCR 计费参考:[TCR 计费概述](https://cloud.tencent.com/document/product/1141/40540) + +## tccli 命令契约 + +所有任务页遵循以下契约: + +- **JSON bridge** 模式:写命令使用 `--cli-input-json file://examples/...` 传入模板 +- **waiter 模式**:异步操作通过 `Describe*` + 状态字段轮询等待完成 +- **`--generate-cli-skeleton`**:按需查看完整参数树(参考用,非运行时依赖) + +## 开始使用 + +- **[tccli on TKE](./tke/)** — TKE tccli 操作(即将发布) +- **[tccli on TCR](./tcr/)** — TCR tccli 操作 +- **tkectl** — 产品 CLI,统一 TKE+TCR 的任务级命令(即将发布) diff --git a/src/content/docs/cli/tcr/index.md b/src/content/docs/cli/tcr/index.md new file mode 100644 index 0000000..8e6a2b2 --- /dev/null +++ b/src/content/docs/cli/tcr/index.md @@ -0,0 +1,87 @@ +--- +title: "TCR · tccli 操作参考" +description: "TCR (容器镜像服务) tccli 操作契约:JSON bridge、waiter 模式、参数骨架与常用命令模式。" +--- + +> P0 done · 对照 [TCR 官方文档](https://cloud.tencent.com/document/product/1141) · tccli ≥ 3.1.107.1 · API 2019-09-24 + +TCR (Tencent Container Registry) 任务页使用以下 tccli 契约。环境准备见 [CLI 操作指南概览](../)。 + +## Read actions (Describe* / Get*) + +使用单行命令加 `--output json`: + +```bash +tccli tcr DescribeInstances --region ap-guangzhou --output json +``` + +命名空间和仓库查询: + +```bash +tccli tcr DescribeNamespaces --RegistryId --region ap-guangzhou --output json +tccli tcr DescribeRepositories --RegistryId --NamespaceName --region ap-guangzhou --output json +``` + +## 复杂写操作:JSON bridge + +创建/修改操作(如 `CreateInstance`)使用 **JSON bridge** 模式: + +1. 通过 `--cli-input-json file://examples/...` 传入精选最小模板 +2. 使用 `DescribeInstances` 或 `DescribeInstanceStatus` 轮询至目标状态 + +```bash +tccli tcr CreateInstance --cli-input-json file://examples/CreateTcrInstance.min.json --region ap-guangzhou --output json +``` + +## 个人版操作 + +TCR 个人版使用独立的 `Personal` 后缀 Action,无需 `RegistryId`: + +```bash +tccli tcr CreateNamespacePersonal --Namespace --region ap-guangzhou --output json +tccli tcr CreateRepositoryPersonal --RepoName "/" --region ap-guangzhou --output json +``` + +## 查看完整参数骨架 + +```bash +tccli tcr CreateInstance --generate-cli-skeleton --output json > /tmp/CreateInstance.skeleton.json +``` + +任务页上优先使用 `examples/*.min.json`;从骨架中只取需要的字段。 + +## 已覆盖的接口 + +| 分类 | 接口数 | 状态 | +|------|:---:|:---:| +| TKE 接口 | 271 | P0 done | +| TCR 接口 | 115 | P0 done | +| **合计** | **386** | **100%** | + +## 快速导航 + +### 快速入门 +- [企业版快速入门](./quickstart/enterprise/) +- [个人版快速入门](./quickstart/personal/) + +### 操作指南 +- **实例**: [创建企业版实例](./ops/instances/create/) · [销毁退还实例](./ops/instances/delete/) +- **访问配置**: [用户级账号管理](./ops/access/credentials/user-credentials/) · [服务级账号管理](./ops/access/credentials/service-credentials/) · [公网访问控制](./ops/access/network/public-access/) · [内网访问控制](./ops/access/network/private-access/) +- **镜像创建**: [管理命名空间](./ops/image-creation/namespace/) · [管理镜像仓库](./ops/image-creation/repository/) +- **镜像分发**: [同实例多地域复制](./ops/image-distribution/cross-region-replication/) · [跨实例同步](./ops/image-distribution/cross-instance-sync/) · [按需加载](./ops/image-distribution/accelerated-image/) +- **镜像安全**: [安全扫描](./ops/image-security/vulnerability-scan/) · [版本不可变](./ops/image-security/immutable-tags/) · [部署阻断](./ops/image-security/deployment-block/) · [镜像签名](./ops/image-security/image-signing/) +- **镜像清理**: [清理 COS](./ops/image-cleanup/cos-cleanup/) · [自动删除版本](./ops/image-cleanup/auto-delete/) +- **OCI 制品**: [制品概述](./ops/oci-artifacts/overview/) · [托管 Helm Chart](./ops/oci-artifacts/helm-chart/) +- **DevOps**: [触发器 Webhook](./ops/devops/webhook/) +- **个人版**: [更新密码](./ops/personal-edition/update-password/) · [CAM API 列表](./ops/personal-edition/cam-api-list/) · [授权方案](./ops/personal-edition/auth-examples/) · [镜像清理](./ops/personal-edition/image-cleanup/) + +### 实践教程 +- [Harbor 迁移](./practices/harbor-migration/) +- [TKE Serverless 拉取 TCR 镜像](./practices/tke-serverless-pull/) +- [TKE 插件内网免密拉取](./practices/tke-plugin-pull/) +- [混合云多平台同步](./practices/hybrid-cloud-sync/) +- [全球多地域同步](./practices/global-replication/) +- [自定义域名 + 云联网](./practices/custom-domain-ccn/) +- [实例后端存储切换](./practices/storage-switch/) +- [个人版迁移至企业版](./practices/personal-migration/) +- [个人版域名访问企业版](./practices/personal-domain-access/) diff --git a/src/content/docs/cli/tcr/ops/access/credentials/service-credentials-tcrctl.md b/src/content/docs/cli/tcr/ops/access/credentials/service-credentials-tcrctl.md new file mode 100644 index 0000000..bd26640 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/credentials/service-credentials-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "服务级账号管理(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[服务级账号管理](https://cloud.tencent.com/document/product/1141/89137) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/access/credentials/service-credentials.md b/src/content/docs/cli/tcr/ops/access/credentials/service-credentials.md new file mode 100644 index 0000000..109b4e3 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/credentials/service-credentials.md @@ -0,0 +1,338 @@ +--- +title: "管理服务级账号" +description: "· page_id `89137`" +--- + +> 对照官方:[服务级账号管理](https://cloud.tencent.com/document/product/1141/89137) · page_id `89137` + +## 概述 + +管理 TCR 企业版实例的**服务级账号**(Service Account)。服务级账号是一种命名空间级别的机器人账号,不与腾讯云子账号绑定,适用于 CI/CD 流水线、Kubernetes 集群 ImagePullSecret 等自动化场景。 + +核心特性: + +- **细粒度权限**:通过 `Permissions` 数组精确控制账号可执行的操作(push、pull、create 等)及可访问的命名空间。 +- **时效性**:通过 `Duration`(天数)或 `ExpiresAt`(毫秒时间戳)设定过期时间,到期后自动失效。两者二选一必填。 +- **自动前缀**:创建指定的 `Name` 后,系统自动添加 `tcr$` 前缀作为最终用户名(如 `Name=my-sa` -> `tcr$my-sa`)。 +- **密码一次性返回**:`Password` 仅在创建时返回,后续无法查询,需立即保存。 + +> **注意**:服务级账号权限模型独立于 CAM。拥有 `tcr:CreateServiceAccount` 等 API 权限的子账号可为服务账号配置超出自身权限的命名空间操作权限,存在越权风险。请严格限制服务级账号功能的授权范围。 + +## 前置条件 + +- [环境准备](../../index.md) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域 + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tcr:CreateServiceAccount, tcr:DescribeServiceAccounts, +# tcr:ModifyServiceAccount, tcr:DeleteServiceAccount +# 验证:执行 DescribeServiceAccounts 确认权限 +tccli tcr DescribeServiceAccounts \ + --RegistryId '' \ + --region '' \ + --output json +# expected: exit 0,返回服务级账号列表(可为空) +``` + +### 资源检查 + +```bash +# 4. 确认实例存在且状态为 Running +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region '' \ + --output json +# expected: Registries 列表中实例 Status 为 "Running" + +# 5. 确认目标命名空间存在 +tccli tcr DescribeNamespaces \ + --RegistryId '' \ + --region '' \ + --output json +# expected: 返回命名空间列表,含目标命名空间 +``` + +| # | 条件 | 验证方式 | +|---|------|---------| +| 1 | 企业版实例已创建且状态为 `Running` | `tccli tcr DescribeInstances --Registryids '[""]' --region ` | +| 2 | 目标命名空间已创建 | `tccli tcr DescribeNamespaces --RegistryId --region ` | +| 3 | `tccli` 已安装并配置凭证 | `tccli tcr DescribeServiceAccounts --RegistryId --region ` | + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 查看服务级账号列表 | `DescribeServiceAccounts` | 是 | +| 新建服务级账号 | `CreateServiceAccount` | 否 | +| 修改服务级账号(权限/描述/有效期) | `ModifyServiceAccount` | 否 | +| 删除服务级账号 | `DeleteServiceAccount` | 否 | + +## 关键字段说明 + +以下说明 `CreateServiceAccount` 的主要参数。完整参数定义见 `tccli tcr CreateServiceAccount --generate-cli-skeleton`。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | 实例 ID,格式 `tcr-xxxxxxxx` | 非法格式 → `InvalidParameter`;实例不存在 → `ResourceNotFound` | +| `Name` | String | 是 | 账号名称,系统自动添加 `tcr$` 前缀。实例内唯一 | 同名冲突 → `InvalidParameter` | +| `Permissions` | Array | 是 | 权限策略数组,元素结构 `{"Resource": "", "Actions": ["tcr:PushRepository", ...]}` | 空数组 → `custom account's permission can not be empty` | +| `Permissions[].Resource` | String | 是 | 命名空间名称(非 `namespace/repo` 路径,无需通配符) | 填仓库全路径 → `custom account for resource(...) is illegal` | +| `Permissions[].Actions` | Array | 是 | TCR 自定义 Action 名:`tcr:PushRepository`、`tcr:PullRepository`、`tcr:CreateRepository`、`tcr:CreateHelmChart`、`tcr:DescribeHelmChart` 等,至少一个 | 空数组或不支持 Action → `not support action` | +| `Description` | String | 否 | 账号描述,建议填写用途便于管理 | 无直接后果,不填增加后期审计成本 | +| `Duration` | Integer | 条件必填 | 有效期(天),从当前时间起算。与 `ExpiresAt` 二选一必填 | 两者都不填 → `MissingParameter` | +| `ExpiresAt` | Integer | 条件必填 | 过期时间(Unix 毫秒时间戳)。`Duration` 优先级高于 `ExpiresAt` | 同上 | +| `Disable` | Boolean | 否 | `true` 创建即禁用,默认 `false` | 创建即禁用需手动 Enable 后才能使用 | + +## 操作步骤 + +### 步骤 1:查看已有服务级账号 + +```bash +tccli tcr DescribeServiceAccounts \ + --RegistryId '' \ + --region '' \ + --output json +# expected: exit 0,ServiceAccounts 列表 +``` + +**预期输出**: + +```json +{ + "ServiceAccounts": [], + "TotalCount": 0, + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +当前实例无已有服务级账号。`DescribeServiceAccounts` 为只读操作,可随时安全执行。 + +如需按名称过滤,使用 `--Filters` 参数: + +```bash +tccli tcr DescribeServiceAccounts \ + --RegistryId '' \ + --Filters '[{"Name":"ServiceAccountName","Values":[""]}]' \ + --region '' \ + --output json +# expected: exit 0,匹配的 ServiceAccounts +``` + +### 步骤 2:创建服务级账号 + +#### 选择依据 + +*为什么选这个值而不是其他:* + +- **Actions**:使用 `tcr:PushRepository`、`tcr:PullRepository`、`tcr:CreateRepository`(TCR 自定义 Action 格式,带 `tcr:` 前缀)。这是服务级账号权限体系的专有命名格式,非 CAM IAM Action 名。 +- **Resource**:填命名空间名称(如 ``),非仓库全路径。无需通配符,一个 Permission 条目对应一个命名空间。 +- **Duration vs ExpiresAt**:推荐使用 `Duration`(天数),从当前时间起算,语义清晰不易出错。长期 CI/CD 场景建议设 365 天(1 年)或更长。 +- **Permissions 不可为空**:API 对空权限数组执行硬校验,必须提供至少一个权限条目。 + +#### 最小创建(仅必填字段,长期有效) + +```bash +tccli tcr CreateServiceAccount \ + --RegistryId '' \ + --Name '' \ + --Permissions '[{"Resource":"","Actions":["tcr:PushRepository","tcr:PullRepository","tcr:CreateRepository"]}]' \ + --Duration 87600 \ + --region '' \ + --output json +# expected: exit 0,返回 Name(含 tcr$ 前缀)和 Password +``` + +**预期输出**: + +```json +{ + "Name": "tcr$", + "Password": "", + "ExpiresAt": 1740000000000, + "CreateTime": "2026-06-18T12:00:00+08:00", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +| 返回字段 | 说明 | +|---------|------| +| `Name` | 实际用户名,含 `tcr$` 前缀(如 `tcr$my-sa`),用于 `docker login` | +| `Password` | 访问密码,**仅在创建时返回一次**,需立即保存 | +| `ExpiresAt` | 过期时间(毫秒时间戳) | +| `CreateTime` | 创建时间 | + +#### 增强配置(加描述、精确有效期) + +```bash +tccli tcr CreateServiceAccount \ + --RegistryId '' \ + --Name '' \ + --Permissions '[{"Resource":"","Actions":["tcr:PushRepository","tcr:PullRepository","tcr:CreateRepository"]}]' \ + --Duration 365 \ + --Description '' \ + --region '' \ + --output json +# expected: exit 0,返回 Name 和 Password +``` + +| 占位符 | 说明 | 获取方式 | +|--------|------|---------| +| `` | TCR 实例 ID,格式 `tcr-xxxxxxxx` | `tccli tcr DescribeInstances` | +| `` | 服务级账号名称(不含 `tcr$` 前缀) | 自定义 | +| `` | 目标命名空间名称 | `tccli tcr DescribeNamespaces --RegistryId ` | +| `` | 实例所在地域,如 `ap-guangzhou` | `tccli tcr DescribeInstances` | +| `` | 账号用途描述 | 自定义 | + +> **重要**:`Password` 值仅在创建时返回一次。`DescribeServiceAccounts` 不返回密码,遗失后无法找回,只能删除后重新创建。 + +### 步骤 3:修改服务级账号 + +修改已有服务级账号的权限、描述或有效期: + +```bash +tccli tcr ModifyServiceAccount \ + --RegistryId '' \ + --Name '' \ + --Permissions '[{"Resource":"","Actions":["tcr:PushRepository","tcr:PullRepository"]}]' \ + --region '' \ + --output json +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +> `ModifyServiceAccount` 的 `Permissions` 参数会**全量替换**原有权限,而非追加。修改前建议先通过 `DescribeServiceAccounts` 确认当前权限配置。 + +### 步骤 4:删除服务级账号 + +```bash +tccli tcr DeleteServiceAccount \ + --RegistryId '' \ + --Name '' \ + --region '' \ + --output json +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +| 占位符 | 说明 | +|--------|------| +| `` | 创建时的原始名称(**不含** `tcr$` 前缀) | + +> 删除操作不可逆。`Name` 为创建时传入的原始名称(不含 `tcr$` 前缀)。生产环境建议先确认无 CI/CD 流水线依赖后再删除。 + +## 验证 + +### 控制面(tccli) + +| # | 验证项 | 命令 | 期望结果 | +|---|--------|------|---------| +| 1 | 服务级账号已创建 | `tccli tcr DescribeServiceAccounts --RegistryId '' --region ''` | `ServiceAccounts` 列表中包含新建账号,`Name` 含 `tcr$` 前缀 | +| 2 | 权限策略正确 | 同上,检查 `Permissions[].Actions` | 与创建时指定的 Actions 一致 | +| 3 | 有效期正确 | 同上,检查 `ExpiresAt` | 时间戳与创建时的 Duration 对应 | +| 4 | 修改已生效 | 同上(修改后执行) | `Permissions` 或 `Description` 已更新 | +| 5 | 账号已删除 | 同上(删除后执行) | 该账号不再出现在 `ServiceAccounts` 列表中 | + +### 数据面(docker login) + +创建成功后,用返回的 `Name`(含 `tcr$` 前缀)和 `Password` 登录: + +```bash +docker login .tencentcloudcr.com \ + --username 'tcr$' \ + --password '' +# expected: Login Succeeded +``` + +如果服务级账号只被授予了 `tcr:PullRepository` 权限,则 `docker push` 会被拒绝——这是预期行为,验证了权限隔离生效。 + +## 清理 + +> **删除操作不可逆。** 正在使用该服务账号的 CI/CD 流水线、Kubernetes 集群(通过 ImagePullSecret)将在凭证缓存过期后丢失拉取/推送权限。建议清理前先确认无活跃流水线依赖该账号。 + +### 1. 清理前状态检查 + +```bash +tccli tcr DescribeServiceAccounts \ + --RegistryId '' \ + --region '' \ + --output json +# 确认待删除的账号名称、权限、创建时间 +``` + +### 2. 删除服务级账号 + +```bash +tccli tcr DeleteServiceAccount \ + --RegistryId '' \ + --Name '' \ + --region '' \ + --output json +``` + +### 3. 验证已删除 + +```bash +tccli tcr DescribeServiceAccounts \ + --RegistryId '' \ + --Filters '[{"Name":"ServiceAccountName","Values":[""]}]' \ + --region '' \ + --output json +# expected: ServiceAccounts 为空数组,TotalCount 为 0 +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `CreateServiceAccount` 返回 `custom account's permission can not be empty` | 检查 `--Permissions` 参数值 | `Permissions` 为空数组 `[]` | 至少传入一条权限记录,含 `Resource` 和 `Actions`,如 `'[{"Resource":"","Actions":["tcr:PullRepository"]}]'` | +| `CreateServiceAccount` 返回 `not support action: ` | 检查 `Actions` 列表中的值 | `Actions` 使用了不支持的 Action 名 | 使用 TCR 服务级账号支持的 Action:`tcr:PushRepository`、`tcr:PullRepository`、`tcr:CreateRepository`、`tcr:CreateHelmChart`、`tcr:DescribeHelmChart` | +| `CreateServiceAccount` 返回 `custom account for resource(...) is illegal` | 检查 `Resource` 值 | `Resource` 填了仓库全路径(如 `ns/repo`)而非命名空间名 | `Resource` 填命名空间名称(仅 ``),非仓库全路径 | +| `CreateServiceAccount` 返回 `MissingParameter` | 检查是否传了 `Duration` 或 `ExpiresAt` | `Duration` 和 `ExpiresAt` 至少填一个 | 添加 `--Duration <天数>` 或 `--ExpiresAt <毫秒时间戳>` | +| `DeleteServiceAccount` / `ModifyServiceAccount` 返回 `ResourceNotFound` | `tccli tcr DescribeServiceAccounts --RegistryId '' --region ''` 核实 `Name` | `Name` 不存在或拼写错误 | 使用创建时的原始名称(不含 `tcr$` 前缀),或通过 `DescribeServiceAccounts` 确认正确的 `Name` | +| 创建时同名冲突 | `tccli tcr DescribeServiceAccounts --RegistryId '' --Filters '[{"Name":"ServiceAccountName","Values":[""]}]' --region ''` | 实例内服务级账号名称必须唯一 | 更换 `Name` 或先删除同名账号 | + +### 使用过程中常见问题 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `docker login` 返回 `401 Unauthorized` | 检查 `ExpiresAt` 是否已过期:`tccli tcr DescribeServiceAccounts --RegistryId '' --region ''` | 服务账号已过期或被禁用 | 过期需删除重新创建;被禁用需通过控制台启用(tccli 暂无 Enable/Disable 接口) | +| `Password` 遗失 | 执行 `tccli tcr DescribeServiceAccounts --RegistryId '' --region ''` | 查询接口不返回 `Password` 字段 | 无法找回原始密码,需删除后重新创建(步骤 4 + 步骤 2) | +| CI/CD 平台不支持 `$` 字符 | — | `tcr$` 前缀中的 `$` 被部分平台转义 | 部分平台支持用 `tcr@` 替代 `tcr$` 前缀 | +| 子账号操作被 CAM 拒绝 | — | 子账号缺少 `tcr:CreateServiceAccount` 等权限 | 联系主账号授予 `QcloudTCRFullAccess` 或添加对应 Action 到自定义策略 | + +## 下一步 + +- [用户级账号管理](../user-credentials) — 创建用于个人开发者的长期登录 Token +- [管理命名空间](../../../image-creation/namespace) — 为服务级账号绑定目标命名空间 +- [基于 CAM 管理子账号权限](../../permissions/基于cam管理子账号权限) — 管理子账号的 TCR 操作权限 +- [访问权限管理概述](../../permissions/permission-overview) — 了解用户级账号与服务级账号的差异 + +## 控制台替代 + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) -> 选择实例 -> **访问凭证** -> **服务级账号** -> **新建** -> 填写名称、描述、有效期、权限策略(选择命名空间及操作)-> 确认后复制生成的用户名(`tcr$`)和密码。在服务级账号列表中可查看详情、修改权限或删除账号。 diff --git a/src/content/docs/cli/tcr/ops/access/credentials/service-credentials/examples/sa-create.json b/src/content/docs/cli/tcr/ops/access/credentials/service-credentials/examples/sa-create.json new file mode 100644 index 0000000..56d87cc --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/credentials/service-credentials/examples/sa-create.json @@ -0,0 +1,17 @@ +{ + "RegistryId": "tcr-example", + "Name": "robot-demo", + "Permissions": [ + { + "Resource": "team-a", + "Actions": ["tcr:PushRepository", "tcr:PullRepository"] + }, + { + "Resource": "open-source", + "Actions": ["tcr:PullRepository"] + } + ], + "Description": "CI/CD automation account", + "Duration": 365, + "Disable": false +} diff --git a/src/content/docs/cli/tcr/ops/access/credentials/service-credentials/examples/sa-modify.json b/src/content/docs/cli/tcr/ops/access/credentials/service-credentials/examples/sa-modify.json new file mode 100644 index 0000000..11ad2fa --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/credentials/service-credentials/examples/sa-modify.json @@ -0,0 +1,12 @@ +{ + "RegistryId": "tcr-example", + "Name": "robot-demo", + "Description": "Updated CI/CD account", + "Duration": 180, + "Permissions": [ + { + "Resource": "team-a", + "Actions": ["tcr:PullRepository"] + } + ] +} diff --git a/src/content/docs/cli/tcr/ops/access/credentials/service-credentials/examples/sa-password.json b/src/content/docs/cli/tcr/ops/access/credentials/service-credentials/examples/sa-password.json new file mode 100644 index 0000000..a8c29c0 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/credentials/service-credentials/examples/sa-password.json @@ -0,0 +1,5 @@ +{ + "RegistryId": "tcr-example", + "Name": "robot-demo", + "Random": true +} diff --git a/src/content/docs/cli/tcr/ops/access/credentials/service-credentials/service-credentials-tcrctl.md b/src/content/docs/cli/tcr/ops/access/credentials/service-credentials/service-credentials-tcrctl.md new file mode 100644 index 0000000..bd26640 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/credentials/service-credentials/service-credentials-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "服务级账号管理(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[服务级账号管理](https://cloud.tencent.com/document/product/1141/89137) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/access/credentials/service-credentials/service-credentials.md b/src/content/docs/cli/tcr/ops/access/credentials/service-credentials/service-credentials.md new file mode 100644 index 0000000..109b4e3 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/credentials/service-credentials/service-credentials.md @@ -0,0 +1,338 @@ +--- +title: "管理服务级账号" +description: "· page_id `89137`" +--- + +> 对照官方:[服务级账号管理](https://cloud.tencent.com/document/product/1141/89137) · page_id `89137` + +## 概述 + +管理 TCR 企业版实例的**服务级账号**(Service Account)。服务级账号是一种命名空间级别的机器人账号,不与腾讯云子账号绑定,适用于 CI/CD 流水线、Kubernetes 集群 ImagePullSecret 等自动化场景。 + +核心特性: + +- **细粒度权限**:通过 `Permissions` 数组精确控制账号可执行的操作(push、pull、create 等)及可访问的命名空间。 +- **时效性**:通过 `Duration`(天数)或 `ExpiresAt`(毫秒时间戳)设定过期时间,到期后自动失效。两者二选一必填。 +- **自动前缀**:创建指定的 `Name` 后,系统自动添加 `tcr$` 前缀作为最终用户名(如 `Name=my-sa` -> `tcr$my-sa`)。 +- **密码一次性返回**:`Password` 仅在创建时返回,后续无法查询,需立即保存。 + +> **注意**:服务级账号权限模型独立于 CAM。拥有 `tcr:CreateServiceAccount` 等 API 权限的子账号可为服务账号配置超出自身权限的命名空间操作权限,存在越权风险。请严格限制服务级账号功能的授权范围。 + +## 前置条件 + +- [环境准备](../../index.md) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域 + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tcr:CreateServiceAccount, tcr:DescribeServiceAccounts, +# tcr:ModifyServiceAccount, tcr:DeleteServiceAccount +# 验证:执行 DescribeServiceAccounts 确认权限 +tccli tcr DescribeServiceAccounts \ + --RegistryId '' \ + --region '' \ + --output json +# expected: exit 0,返回服务级账号列表(可为空) +``` + +### 资源检查 + +```bash +# 4. 确认实例存在且状态为 Running +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region '' \ + --output json +# expected: Registries 列表中实例 Status 为 "Running" + +# 5. 确认目标命名空间存在 +tccli tcr DescribeNamespaces \ + --RegistryId '' \ + --region '' \ + --output json +# expected: 返回命名空间列表,含目标命名空间 +``` + +| # | 条件 | 验证方式 | +|---|------|---------| +| 1 | 企业版实例已创建且状态为 `Running` | `tccli tcr DescribeInstances --Registryids '[""]' --region ` | +| 2 | 目标命名空间已创建 | `tccli tcr DescribeNamespaces --RegistryId --region ` | +| 3 | `tccli` 已安装并配置凭证 | `tccli tcr DescribeServiceAccounts --RegistryId --region ` | + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 查看服务级账号列表 | `DescribeServiceAccounts` | 是 | +| 新建服务级账号 | `CreateServiceAccount` | 否 | +| 修改服务级账号(权限/描述/有效期) | `ModifyServiceAccount` | 否 | +| 删除服务级账号 | `DeleteServiceAccount` | 否 | + +## 关键字段说明 + +以下说明 `CreateServiceAccount` 的主要参数。完整参数定义见 `tccli tcr CreateServiceAccount --generate-cli-skeleton`。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | 实例 ID,格式 `tcr-xxxxxxxx` | 非法格式 → `InvalidParameter`;实例不存在 → `ResourceNotFound` | +| `Name` | String | 是 | 账号名称,系统自动添加 `tcr$` 前缀。实例内唯一 | 同名冲突 → `InvalidParameter` | +| `Permissions` | Array | 是 | 权限策略数组,元素结构 `{"Resource": "", "Actions": ["tcr:PushRepository", ...]}` | 空数组 → `custom account's permission can not be empty` | +| `Permissions[].Resource` | String | 是 | 命名空间名称(非 `namespace/repo` 路径,无需通配符) | 填仓库全路径 → `custom account for resource(...) is illegal` | +| `Permissions[].Actions` | Array | 是 | TCR 自定义 Action 名:`tcr:PushRepository`、`tcr:PullRepository`、`tcr:CreateRepository`、`tcr:CreateHelmChart`、`tcr:DescribeHelmChart` 等,至少一个 | 空数组或不支持 Action → `not support action` | +| `Description` | String | 否 | 账号描述,建议填写用途便于管理 | 无直接后果,不填增加后期审计成本 | +| `Duration` | Integer | 条件必填 | 有效期(天),从当前时间起算。与 `ExpiresAt` 二选一必填 | 两者都不填 → `MissingParameter` | +| `ExpiresAt` | Integer | 条件必填 | 过期时间(Unix 毫秒时间戳)。`Duration` 优先级高于 `ExpiresAt` | 同上 | +| `Disable` | Boolean | 否 | `true` 创建即禁用,默认 `false` | 创建即禁用需手动 Enable 后才能使用 | + +## 操作步骤 + +### 步骤 1:查看已有服务级账号 + +```bash +tccli tcr DescribeServiceAccounts \ + --RegistryId '' \ + --region '' \ + --output json +# expected: exit 0,ServiceAccounts 列表 +``` + +**预期输出**: + +```json +{ + "ServiceAccounts": [], + "TotalCount": 0, + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +当前实例无已有服务级账号。`DescribeServiceAccounts` 为只读操作,可随时安全执行。 + +如需按名称过滤,使用 `--Filters` 参数: + +```bash +tccli tcr DescribeServiceAccounts \ + --RegistryId '' \ + --Filters '[{"Name":"ServiceAccountName","Values":[""]}]' \ + --region '' \ + --output json +# expected: exit 0,匹配的 ServiceAccounts +``` + +### 步骤 2:创建服务级账号 + +#### 选择依据 + +*为什么选这个值而不是其他:* + +- **Actions**:使用 `tcr:PushRepository`、`tcr:PullRepository`、`tcr:CreateRepository`(TCR 自定义 Action 格式,带 `tcr:` 前缀)。这是服务级账号权限体系的专有命名格式,非 CAM IAM Action 名。 +- **Resource**:填命名空间名称(如 ``),非仓库全路径。无需通配符,一个 Permission 条目对应一个命名空间。 +- **Duration vs ExpiresAt**:推荐使用 `Duration`(天数),从当前时间起算,语义清晰不易出错。长期 CI/CD 场景建议设 365 天(1 年)或更长。 +- **Permissions 不可为空**:API 对空权限数组执行硬校验,必须提供至少一个权限条目。 + +#### 最小创建(仅必填字段,长期有效) + +```bash +tccli tcr CreateServiceAccount \ + --RegistryId '' \ + --Name '' \ + --Permissions '[{"Resource":"","Actions":["tcr:PushRepository","tcr:PullRepository","tcr:CreateRepository"]}]' \ + --Duration 87600 \ + --region '' \ + --output json +# expected: exit 0,返回 Name(含 tcr$ 前缀)和 Password +``` + +**预期输出**: + +```json +{ + "Name": "tcr$", + "Password": "", + "ExpiresAt": 1740000000000, + "CreateTime": "2026-06-18T12:00:00+08:00", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +| 返回字段 | 说明 | +|---------|------| +| `Name` | 实际用户名,含 `tcr$` 前缀(如 `tcr$my-sa`),用于 `docker login` | +| `Password` | 访问密码,**仅在创建时返回一次**,需立即保存 | +| `ExpiresAt` | 过期时间(毫秒时间戳) | +| `CreateTime` | 创建时间 | + +#### 增强配置(加描述、精确有效期) + +```bash +tccli tcr CreateServiceAccount \ + --RegistryId '' \ + --Name '' \ + --Permissions '[{"Resource":"","Actions":["tcr:PushRepository","tcr:PullRepository","tcr:CreateRepository"]}]' \ + --Duration 365 \ + --Description '' \ + --region '' \ + --output json +# expected: exit 0,返回 Name 和 Password +``` + +| 占位符 | 说明 | 获取方式 | +|--------|------|---------| +| `` | TCR 实例 ID,格式 `tcr-xxxxxxxx` | `tccli tcr DescribeInstances` | +| `` | 服务级账号名称(不含 `tcr$` 前缀) | 自定义 | +| `` | 目标命名空间名称 | `tccli tcr DescribeNamespaces --RegistryId ` | +| `` | 实例所在地域,如 `ap-guangzhou` | `tccli tcr DescribeInstances` | +| `` | 账号用途描述 | 自定义 | + +> **重要**:`Password` 值仅在创建时返回一次。`DescribeServiceAccounts` 不返回密码,遗失后无法找回,只能删除后重新创建。 + +### 步骤 3:修改服务级账号 + +修改已有服务级账号的权限、描述或有效期: + +```bash +tccli tcr ModifyServiceAccount \ + --RegistryId '' \ + --Name '' \ + --Permissions '[{"Resource":"","Actions":["tcr:PushRepository","tcr:PullRepository"]}]' \ + --region '' \ + --output json +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +> `ModifyServiceAccount` 的 `Permissions` 参数会**全量替换**原有权限,而非追加。修改前建议先通过 `DescribeServiceAccounts` 确认当前权限配置。 + +### 步骤 4:删除服务级账号 + +```bash +tccli tcr DeleteServiceAccount \ + --RegistryId '' \ + --Name '' \ + --region '' \ + --output json +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +| 占位符 | 说明 | +|--------|------| +| `` | 创建时的原始名称(**不含** `tcr$` 前缀) | + +> 删除操作不可逆。`Name` 为创建时传入的原始名称(不含 `tcr$` 前缀)。生产环境建议先确认无 CI/CD 流水线依赖后再删除。 + +## 验证 + +### 控制面(tccli) + +| # | 验证项 | 命令 | 期望结果 | +|---|--------|------|---------| +| 1 | 服务级账号已创建 | `tccli tcr DescribeServiceAccounts --RegistryId '' --region ''` | `ServiceAccounts` 列表中包含新建账号,`Name` 含 `tcr$` 前缀 | +| 2 | 权限策略正确 | 同上,检查 `Permissions[].Actions` | 与创建时指定的 Actions 一致 | +| 3 | 有效期正确 | 同上,检查 `ExpiresAt` | 时间戳与创建时的 Duration 对应 | +| 4 | 修改已生效 | 同上(修改后执行) | `Permissions` 或 `Description` 已更新 | +| 5 | 账号已删除 | 同上(删除后执行) | 该账号不再出现在 `ServiceAccounts` 列表中 | + +### 数据面(docker login) + +创建成功后,用返回的 `Name`(含 `tcr$` 前缀)和 `Password` 登录: + +```bash +docker login .tencentcloudcr.com \ + --username 'tcr$' \ + --password '' +# expected: Login Succeeded +``` + +如果服务级账号只被授予了 `tcr:PullRepository` 权限,则 `docker push` 会被拒绝——这是预期行为,验证了权限隔离生效。 + +## 清理 + +> **删除操作不可逆。** 正在使用该服务账号的 CI/CD 流水线、Kubernetes 集群(通过 ImagePullSecret)将在凭证缓存过期后丢失拉取/推送权限。建议清理前先确认无活跃流水线依赖该账号。 + +### 1. 清理前状态检查 + +```bash +tccli tcr DescribeServiceAccounts \ + --RegistryId '' \ + --region '' \ + --output json +# 确认待删除的账号名称、权限、创建时间 +``` + +### 2. 删除服务级账号 + +```bash +tccli tcr DeleteServiceAccount \ + --RegistryId '' \ + --Name '' \ + --region '' \ + --output json +``` + +### 3. 验证已删除 + +```bash +tccli tcr DescribeServiceAccounts \ + --RegistryId '' \ + --Filters '[{"Name":"ServiceAccountName","Values":[""]}]' \ + --region '' \ + --output json +# expected: ServiceAccounts 为空数组,TotalCount 为 0 +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `CreateServiceAccount` 返回 `custom account's permission can not be empty` | 检查 `--Permissions` 参数值 | `Permissions` 为空数组 `[]` | 至少传入一条权限记录,含 `Resource` 和 `Actions`,如 `'[{"Resource":"","Actions":["tcr:PullRepository"]}]'` | +| `CreateServiceAccount` 返回 `not support action: ` | 检查 `Actions` 列表中的值 | `Actions` 使用了不支持的 Action 名 | 使用 TCR 服务级账号支持的 Action:`tcr:PushRepository`、`tcr:PullRepository`、`tcr:CreateRepository`、`tcr:CreateHelmChart`、`tcr:DescribeHelmChart` | +| `CreateServiceAccount` 返回 `custom account for resource(...) is illegal` | 检查 `Resource` 值 | `Resource` 填了仓库全路径(如 `ns/repo`)而非命名空间名 | `Resource` 填命名空间名称(仅 ``),非仓库全路径 | +| `CreateServiceAccount` 返回 `MissingParameter` | 检查是否传了 `Duration` 或 `ExpiresAt` | `Duration` 和 `ExpiresAt` 至少填一个 | 添加 `--Duration <天数>` 或 `--ExpiresAt <毫秒时间戳>` | +| `DeleteServiceAccount` / `ModifyServiceAccount` 返回 `ResourceNotFound` | `tccli tcr DescribeServiceAccounts --RegistryId '' --region ''` 核实 `Name` | `Name` 不存在或拼写错误 | 使用创建时的原始名称(不含 `tcr$` 前缀),或通过 `DescribeServiceAccounts` 确认正确的 `Name` | +| 创建时同名冲突 | `tccli tcr DescribeServiceAccounts --RegistryId '' --Filters '[{"Name":"ServiceAccountName","Values":[""]}]' --region ''` | 实例内服务级账号名称必须唯一 | 更换 `Name` 或先删除同名账号 | + +### 使用过程中常见问题 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `docker login` 返回 `401 Unauthorized` | 检查 `ExpiresAt` 是否已过期:`tccli tcr DescribeServiceAccounts --RegistryId '' --region ''` | 服务账号已过期或被禁用 | 过期需删除重新创建;被禁用需通过控制台启用(tccli 暂无 Enable/Disable 接口) | +| `Password` 遗失 | 执行 `tccli tcr DescribeServiceAccounts --RegistryId '' --region ''` | 查询接口不返回 `Password` 字段 | 无法找回原始密码,需删除后重新创建(步骤 4 + 步骤 2) | +| CI/CD 平台不支持 `$` 字符 | — | `tcr$` 前缀中的 `$` 被部分平台转义 | 部分平台支持用 `tcr@` 替代 `tcr$` 前缀 | +| 子账号操作被 CAM 拒绝 | — | 子账号缺少 `tcr:CreateServiceAccount` 等权限 | 联系主账号授予 `QcloudTCRFullAccess` 或添加对应 Action 到自定义策略 | + +## 下一步 + +- [用户级账号管理](../user-credentials) — 创建用于个人开发者的长期登录 Token +- [管理命名空间](../../../image-creation/namespace) — 为服务级账号绑定目标命名空间 +- [基于 CAM 管理子账号权限](../../permissions/基于cam管理子账号权限) — 管理子账号的 TCR 操作权限 +- [访问权限管理概述](../../permissions/permission-overview) — 了解用户级账号与服务级账号的差异 + +## 控制台替代 + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) -> 选择实例 -> **访问凭证** -> **服务级账号** -> **新建** -> 填写名称、描述、有效期、权限策略(选择命名空间及操作)-> 确认后复制生成的用户名(`tcr$`)和密码。在服务级账号列表中可查看详情、修改权限或删除账号。 diff --git a/src/content/docs/cli/tcr/ops/access/credentials/user-credentials-tcrctl.md b/src/content/docs/cli/tcr/ops/access/credentials/user-credentials-tcrctl.md new file mode 100644 index 0000000..b6d16fa --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/credentials/user-credentials-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "用户级账号管理(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[用户级账号管理](https://cloud.tencent.com/document/product/1141/41829) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/access/credentials/user-credentials.md b/src/content/docs/cli/tcr/ops/access/credentials/user-credentials.md new file mode 100644 index 0000000..c98d3b6 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/credentials/user-credentials.md @@ -0,0 +1,277 @@ +--- +title: "用户级账号管理(tccli)" +description: "· page_id `41829`" +--- + +> 对照官方:[用户级账号管理](https://cloud.tencent.com/document/product/1141/41829) · page_id `41829` + +## 概述 + +管理 TCR 企业版实例的**用户级账号 Token**——用于 `docker login` 登录 TCR 实例域名的长期访问凭证。每个实例下可创建多条 Token,支持按 `TokenId` 启停与删除。 + +Token 分两种类型: + +| 类型 | `TokenType` | 有效期 | 典型场景 | +|------|-------------|--------|---------| +| 临时 Token | `temp` | 短期(约数小时至数天) | CI/CD 单次构建、临时授权 | +| 长期 Token | `longterm` | 长期(约 10 年) | 开发者本地终端、持久化流水线 | + +创建长期 Token 后返回 `Username` 和 `Token`,可直接用于: + +```bash +docker login -u -p +``` + +> 与服务级账号([服务级账号管理](../service-credentials))不同,用户级账号 Token 面向人机身份,**不具备命名空间级权限隔离**。如需细粒度权限控制,请使用服务级账号。 + +## 前置条件 + +| # | 条件 | 验证命令(可 COPY-PASTE 直跑) | +|---|------|------------------------------| +| 1 | 企业版实例已创建且状态为 `Running` | `tccli tcr DescribeInstances --Registryids '["tcr-example"]' --region ap-guangzhou --output json --filter "Registries[0].Status"` # Expected: `"Running"` | +| 2 | `tccli` 已安装并配置凭证 | `tccli tcr DescribeRegions --region ap-guangzhou --output json --filter "TotalCount"` # Expected: `>= 1` | +| 3 | `docker` CLI 已安装(用于登录验证) | `docker --version` # Expected: `Docker version X.Y.Z` | +| 4 | 子账号需具备 `tcr:CreateInstanceToken` 权限 | 如使用子账号,联系主账号授予 `QcloudTCRFullAccess` 或最小权限策略(参见 [企业版授权方案示例](https://cloud.tencent.com/document/product/1141/41417)) | + +## 控制台与 CLI 参数映射 + +| 控制台操作 | CLI | 幂等 | +|-----------|-----|------| +| 查看凭证列表 | `tccli tcr DescribeInstanceToken --RegistryId ` | 是 | +| 新建临时访问凭证 | `tccli tcr CreateInstanceToken --RegistryId --TokenType temp --Desc ` | 否(同名 Desc 可多条) | +| 新建长期访问凭证 | `tccli tcr CreateInstanceToken --RegistryId --TokenType longterm --Desc ` | 否(同名 Desc 可多条) | +| 启用/禁用凭证 | `tccli tcr ModifyInstanceToken --RegistryId --TokenId --Enable true/false` | 是(重复执行结果一致) | +| 删除凭证 | `tccli tcr DeleteInstanceToken --RegistryId --TokenId ` | 否(已删除后再次执行报 `ResourceNotFound`) | + +### CreateInstanceToken 关键字段说明 + +| 字段名 | 类型 | 必填 | 取值与约束 | 错误后果 | +|--------|------|------|-----------|---------| +| `RegistryId` | String | **是** | 实例 ID,格式 `tcr-xxxxxxxx` | 非法格式 → `InvalidParameter`;实例不存在 → `ResourceNotFound` | +| `TokenType` | String | **是** | `temp`(临时)或 `longterm`(长期)。大小写敏感,仅小写有效 | 非法值 → `InvalidParameterValue` | +| `Desc` | String | 否 | 凭证描述,建议填写用途便于管理。留空时系统不生成描述 | 无后果,但不填将增加后期审计成本 | +| `--region` | String | **是** | CLI 参数,需与实例所在地域一致(如 `ap-guangzhou`) | 实例在其他地域 → `ResourceNotFound` | + +### ModifyInstanceToken 关键字段说明 + +| 字段名 | 类型 | 必填 | 取值与约束 | 错误后果 | +|--------|------|------|-----------|---------| +| `RegistryId` | String | **是** | 实例 ID | 不匹配 → `ResourceNotFound` | +| `TokenId` | String | **是** | 凭证 ID,来自 `DescribeInstanceToken` 返回的 `Id` 字段或 `CreateInstanceToken` 返回的 `TokenId` 字段 | TokenId 不存在或不属于该实例 → `ResourceNotFound` | +| `Enable` | Boolean | **是** | `true` 启用,`false` 禁用 | 禁用后使用该凭证的 `docker login` 返回 `401 Unauthorized` | +| `--region` | String | **是** | CLI 参数 | 同 CreateInstanceToken | + +## 操作步骤 + +### 步骤1:创建临时 Token(短期访问) + +```bash +# 最小示例(仅必填字段) +tccli tcr CreateInstanceToken \ + --RegistryId 'tcr-example' \ + --TokenType temp \ + --region ap-guangzhou \ + --output json +``` + +Output: + +```json +{ + "Username": "100049208872", + "Token": "", + "ExpTime": 1781602701406, + "RequestId": "1cbb4de4-6a8a-487d-af93-38801ae5b234" +} +``` + +| 返回字段 | 说明 | +|---------|------| +| `Username` | TCR 用户 ID,即腾讯云主账号 UIN。所有 Token 共享同一 Username | +| `Token` | 临时凭证密码,**仅创建时返回一次**,`docker login` 的 `--password` 参数 | +| `ExpTime` | 过期时间,毫秒级 Unix 时间戳(本例约数天后过期) | + +> 临时 Token 不返回 `TokenId`,无法通过 `ModifyInstanceToken` 启停或按 ID 删除,过期后自动失效。 + +### 步骤2:创建长期 Token(用于 docker login) + +```bash +# 最小示例(仅必填字段) +tccli tcr CreateInstanceToken \ + --RegistryId 'tcr-example' \ + --TokenType longterm \ + --Desc 'my-longterm-token' \ + --region ap-guangzhou \ + --output json +``` + +Output: + +```json +{ + "Username": "100049208872", + "Token": "eyJhbGciOi...", + "TokenId": "d8ogn8g08kblvtqbiun0", + "ExpTime": 2096959138174, + "RequestId": "e7aa94fa-c311-461f-af61-937433038b9d" +} +``` + +| 返回字段 | 说明 | +|---------|------| +| `Username` | TCR 用户 ID(同临时 Token) | +| `Token` | 长期访问凭证密码,**仅在创建时返回一次**。用于 `docker login` 的 `--password` 参数 | +| `TokenId` | 凭证唯一标识符,用于后续 `ModifyInstanceToken` / `DeleteInstanceToken` 的 `--TokenId` 参数 | +| `ExpTime` | 过期时间,毫秒级 Unix 时间戳。本例约 10 年有效期 | + +> **重要**:`Token` 值**仅在创建时返回一次**。创建后务必立即保存——`DescribeInstanceToken` 不返回 `Token` 密码值,遗失后无法找回,只能删除旧凭证后重新创建。 + +### 步骤3:查看已有 Token + +```bash +tccli tcr DescribeInstanceToken \ + --RegistryId 'tcr-example' \ + --region ap-guangzhou \ + --output json +``` + +Output: + +```json +{ + "TotalCount": 1, + "Tokens": [ + { + "Id": "d8ogn8g08kblvtqbiun0", + "Desc": "my-longterm-token", + "RegistryId": "tcr-example", + "Enabled": true, + "CreatedAt": "2026-06-16T12:37:50+08:00", + "ExpiredAt": 2096959138174 + } + ], + "RequestId": "29618872-20f3-4673-b1ea-46a59bd9f2c7" +} +``` + +> 注意:查询结果中字段名为 `Id`(非 `TokenId`),用于 `ModifyInstanceToken` / `DeleteInstanceToken` 的 `--TokenId` 参数。`Token` 密码值不会在查询结果中返回。 + +### 步骤4:启用/禁用 Token + +```bash +# 启用 +tccli tcr ModifyInstanceToken \ + --RegistryId 'tcr-example' \ + --TokenId 'd8ogn8g08kblvtqbiun0' \ + --Enable true \ + --region ap-guangzhou \ + --output json + +# 禁用 +tccli tcr ModifyInstanceToken \ + --RegistryId 'tcr-example' \ + --TokenId 'd8ogn8g08kblvtqbiun0' \ + --Enable false \ + --region ap-guangzhou \ + --output json +``` + +禁用后,使用该凭证的 `docker login` 将失败(返回 `401 Unauthorized`)。生产环境中建议对不再使用但暂不删除的凭证先执行禁用。 + +### 步骤5:删除 Token + +```bash +tccli tcr DeleteInstanceToken \ + --RegistryId 'tcr-example' \ + --TokenId 'd8ogn8g08kblvtqbiun0' \ + --region ap-guangzhou \ + --output json +``` + +Output: + +```json +{ + "RequestId": "fcb487f7-68e0-41ed-9141-6b7dd62694f8" +} +``` + +> 删除操作不可逆。删除后,使用该凭证的 `docker login` 会话将在凭证缓存过期后失效。建议先禁用(步骤4)再删除,确保无活跃流水线依赖该凭证。 + +## 验证 + +### Control plane (tccli)——多维度验证 + +| # | 验证项 | 命令 | 期望结果 | +|---|--------|------|---------| +| 1 | Token 已创建 | `tccli tcr DescribeInstanceToken --RegistryId 'tcr-example' --region ap-guangzhou` | 列表中包含新创建的 `Id`,`Desc` 与创建时一致 | +| 2 | Token 已启用 | 同上 | 对应 `Enabled` 为 `true` | +| 3 | Token 已禁用 | 同上(禁用后执行) | 对应 `Enabled` 为 `false` | +| 4 | Token 已删除 | 同上(删除后执行) | 该 `Id` 不再出现在 `Tokens` 数组中 | +| 5 | 创建同名 Token(非幂等验证) | 重复执行步骤2 | 返回新的 `TokenId`(允许同名多条) | + +### Data plane (docker login) + +使用 Token 登录 TCR 实例: + +```bash +docker login tcr-example.tencentcloudcr.com --username 100049208872 --password +``` + +- 将 `tcr-example.tencentcloudcr.com` 替换为实例实际公网域名(`DescribeInstances` 返回的 `PublicDomain`) +- `` 和 `` 替换为创建时返回的对应值 +- 预期输出 `Login Succeeded` + +## 清理 + +> **删除 Token 不可逆。** 正在使用该 Token 的 CI/CD 流水线、开发者终端、TKE 集群(通过 TCR 插件)将在凭证缓存过期后丢失拉取/推送权限。建议清理前: +> 1. 先禁用(`ModifyInstanceToken --Enable false`)观察数日 +> 2. 确认无流水线告警后再执行删除 +> 3. TKE 插件或 CODING DevOps 自动创建的凭证,删除前需先解除关联 + +```bash +# 1. 先禁用 +tccli tcr ModifyInstanceToken \ + --RegistryId 'tcr-example' \ + --TokenId 'd8ogn8g08kblvtqbiun0' \ + --Enable false \ + --region ap-guangzhou + +# 2. 确认禁用生效 +tccli tcr DescribeInstanceToken \ + --RegistryId 'tcr-example' \ + --region ap-guangzhou \ + --filter "Tokens[?Id=='d8ogn8g08kblvtqbiun0'].Enabled" +# Expected: [false] + +# 3. 再删除 +tccli tcr DeleteInstanceToken \ + --RegistryId 'tcr-example' \ + --TokenId 'd8ogn8g08kblvtqbiun0' \ + --region ap-guangzhou +``` + +## 排障 + +| 现象 | 诊断命令 | 根因 | 修复 | +|------|---------|------|------| +| `docker login` 返回 `401 Unauthorized` | `tccli tcr DescribeInstanceToken --RegistryId 'tcr-example' --region ap-guangzhou` 检查 `Enabled` 与 Token 是否存在 | Token 已禁用或已删除 | 若禁用,执行 `ModifyInstanceToken --Enable true` 重新启用 | +| `docker login` 返回 `401 Unauthorized`(Token 状态正常) | 确认域名是否正确:`tccli tcr DescribeInstances --Registryids '["tcr-example"]' --region ap-guangzhou --filter "Registries[0].PublicDomain"` | 实例域名拼写错误,或使用了内网地址但当前网络不在 VPC 内 | 使用 `PublicDomain` 值重新登录 | +| `CreateInstanceToken` 返回 `UnauthorizedOperation` | — | 子账号缺少 `tcr:CreateInstanceToken` 权限 | 联系主账号授予 `QcloudTCRFullAccess` 或添加 `CreateInstanceToken` 到自定义策略 | +| `ModifyInstanceToken` / `DeleteInstanceToken` 报 `ResourceNotFound` | `tccli tcr DescribeInstanceToken --RegistryId 'tcr-example' --region ap-guangzhou` 核实 TokenId | `TokenId` 不存在或不属于指定 `RegistryId` | 使用 `DescribeInstanceToken` 返回的 `Id` 字段值作为 `--TokenId` | +| `Token` 密码值遗失 | `tccli tcr DescribeInstanceToken --RegistryId 'tcr-example' --region ap-guangzhou` | 查询接口不返回 `Token` 密码值 | 无法找回原始 Token,需删除旧凭证后重新创建(步骤5+步骤2) | +| 多个 Token 管理混乱 | `tccli tcr DescribeInstanceToken --RegistryId 'tcr-example' --region ap-guangzhou --output json` 审计列表 | 创建时未填写 `Desc`,或凭证数量过多 | 创建时填写有意义的 `Desc`,定期审计并清理无用 Token | +| TKE 插件或 CODING DevOps 自动创建的凭证删除被拒 | — | 凭证由外部服务自动创建并关联 | 先解除 TKE 集群关联或 CODING DevOps 集成,再删除凭证 | +| `CreateInstanceToken` 返回 `InvalidParameterValue` | — | `TokenType` 参数值非法(非 `temp` 或 `longterm`) | 确认 `--TokenType` 小写,值为 `temp` 或 `longterm` | +| `docker login` 提示 `Error saving credentials` | `docker --version` | Docker credential helper 配置问题 | 检查 `~/.docker/config.json` 中的 `credsStore` 设置,或使用 `--password-stdin` 替代 | + +## 下一步 + +- [服务级账号管理](../service-credentials)(page_id `89137`)——创建命名空间级权限隔离的机器人账号 +- [基于 CAM 管理子账号权限](../permissions/cam-subaccount)——管理子账号的 TCR 操作权限 +- [管理命名空间](../../image-creation/namespace)——创建命名空间后使用 Token 推送镜像 +- [获取实例访问域名](../../create)——确认 `PublicDomain` 用于 `docker login` + +## 控制台替代 + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 选择实例 → **访问凭证** → **新建访问凭证** → 填写描述 → 确认后**立即复制生成的 Token**(仅显示一次)。在凭证列表中可启用、禁用或删除凭证。 diff --git a/src/content/docs/cli/tcr/ops/access/credentials/user-credentials/user-credentials-tcrctl.md b/src/content/docs/cli/tcr/ops/access/credentials/user-credentials/user-credentials-tcrctl.md new file mode 100644 index 0000000..b6d16fa --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/credentials/user-credentials/user-credentials-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "用户级账号管理(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[用户级账号管理](https://cloud.tencent.com/document/product/1141/41829) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/access/credentials/user-credentials/user-credentials.md b/src/content/docs/cli/tcr/ops/access/credentials/user-credentials/user-credentials.md new file mode 100644 index 0000000..c98d3b6 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/credentials/user-credentials/user-credentials.md @@ -0,0 +1,277 @@ +--- +title: "用户级账号管理(tccli)" +description: "· page_id `41829`" +--- + +> 对照官方:[用户级账号管理](https://cloud.tencent.com/document/product/1141/41829) · page_id `41829` + +## 概述 + +管理 TCR 企业版实例的**用户级账号 Token**——用于 `docker login` 登录 TCR 实例域名的长期访问凭证。每个实例下可创建多条 Token,支持按 `TokenId` 启停与删除。 + +Token 分两种类型: + +| 类型 | `TokenType` | 有效期 | 典型场景 | +|------|-------------|--------|---------| +| 临时 Token | `temp` | 短期(约数小时至数天) | CI/CD 单次构建、临时授权 | +| 长期 Token | `longterm` | 长期(约 10 年) | 开发者本地终端、持久化流水线 | + +创建长期 Token 后返回 `Username` 和 `Token`,可直接用于: + +```bash +docker login -u -p +``` + +> 与服务级账号([服务级账号管理](../service-credentials))不同,用户级账号 Token 面向人机身份,**不具备命名空间级权限隔离**。如需细粒度权限控制,请使用服务级账号。 + +## 前置条件 + +| # | 条件 | 验证命令(可 COPY-PASTE 直跑) | +|---|------|------------------------------| +| 1 | 企业版实例已创建且状态为 `Running` | `tccli tcr DescribeInstances --Registryids '["tcr-example"]' --region ap-guangzhou --output json --filter "Registries[0].Status"` # Expected: `"Running"` | +| 2 | `tccli` 已安装并配置凭证 | `tccli tcr DescribeRegions --region ap-guangzhou --output json --filter "TotalCount"` # Expected: `>= 1` | +| 3 | `docker` CLI 已安装(用于登录验证) | `docker --version` # Expected: `Docker version X.Y.Z` | +| 4 | 子账号需具备 `tcr:CreateInstanceToken` 权限 | 如使用子账号,联系主账号授予 `QcloudTCRFullAccess` 或最小权限策略(参见 [企业版授权方案示例](https://cloud.tencent.com/document/product/1141/41417)) | + +## 控制台与 CLI 参数映射 + +| 控制台操作 | CLI | 幂等 | +|-----------|-----|------| +| 查看凭证列表 | `tccli tcr DescribeInstanceToken --RegistryId ` | 是 | +| 新建临时访问凭证 | `tccli tcr CreateInstanceToken --RegistryId --TokenType temp --Desc ` | 否(同名 Desc 可多条) | +| 新建长期访问凭证 | `tccli tcr CreateInstanceToken --RegistryId --TokenType longterm --Desc ` | 否(同名 Desc 可多条) | +| 启用/禁用凭证 | `tccli tcr ModifyInstanceToken --RegistryId --TokenId --Enable true/false` | 是(重复执行结果一致) | +| 删除凭证 | `tccli tcr DeleteInstanceToken --RegistryId --TokenId ` | 否(已删除后再次执行报 `ResourceNotFound`) | + +### CreateInstanceToken 关键字段说明 + +| 字段名 | 类型 | 必填 | 取值与约束 | 错误后果 | +|--------|------|------|-----------|---------| +| `RegistryId` | String | **是** | 实例 ID,格式 `tcr-xxxxxxxx` | 非法格式 → `InvalidParameter`;实例不存在 → `ResourceNotFound` | +| `TokenType` | String | **是** | `temp`(临时)或 `longterm`(长期)。大小写敏感,仅小写有效 | 非法值 → `InvalidParameterValue` | +| `Desc` | String | 否 | 凭证描述,建议填写用途便于管理。留空时系统不生成描述 | 无后果,但不填将增加后期审计成本 | +| `--region` | String | **是** | CLI 参数,需与实例所在地域一致(如 `ap-guangzhou`) | 实例在其他地域 → `ResourceNotFound` | + +### ModifyInstanceToken 关键字段说明 + +| 字段名 | 类型 | 必填 | 取值与约束 | 错误后果 | +|--------|------|------|-----------|---------| +| `RegistryId` | String | **是** | 实例 ID | 不匹配 → `ResourceNotFound` | +| `TokenId` | String | **是** | 凭证 ID,来自 `DescribeInstanceToken` 返回的 `Id` 字段或 `CreateInstanceToken` 返回的 `TokenId` 字段 | TokenId 不存在或不属于该实例 → `ResourceNotFound` | +| `Enable` | Boolean | **是** | `true` 启用,`false` 禁用 | 禁用后使用该凭证的 `docker login` 返回 `401 Unauthorized` | +| `--region` | String | **是** | CLI 参数 | 同 CreateInstanceToken | + +## 操作步骤 + +### 步骤1:创建临时 Token(短期访问) + +```bash +# 最小示例(仅必填字段) +tccli tcr CreateInstanceToken \ + --RegistryId 'tcr-example' \ + --TokenType temp \ + --region ap-guangzhou \ + --output json +``` + +Output: + +```json +{ + "Username": "100049208872", + "Token": "", + "ExpTime": 1781602701406, + "RequestId": "1cbb4de4-6a8a-487d-af93-38801ae5b234" +} +``` + +| 返回字段 | 说明 | +|---------|------| +| `Username` | TCR 用户 ID,即腾讯云主账号 UIN。所有 Token 共享同一 Username | +| `Token` | 临时凭证密码,**仅创建时返回一次**,`docker login` 的 `--password` 参数 | +| `ExpTime` | 过期时间,毫秒级 Unix 时间戳(本例约数天后过期) | + +> 临时 Token 不返回 `TokenId`,无法通过 `ModifyInstanceToken` 启停或按 ID 删除,过期后自动失效。 + +### 步骤2:创建长期 Token(用于 docker login) + +```bash +# 最小示例(仅必填字段) +tccli tcr CreateInstanceToken \ + --RegistryId 'tcr-example' \ + --TokenType longterm \ + --Desc 'my-longterm-token' \ + --region ap-guangzhou \ + --output json +``` + +Output: + +```json +{ + "Username": "100049208872", + "Token": "eyJhbGciOi...", + "TokenId": "d8ogn8g08kblvtqbiun0", + "ExpTime": 2096959138174, + "RequestId": "e7aa94fa-c311-461f-af61-937433038b9d" +} +``` + +| 返回字段 | 说明 | +|---------|------| +| `Username` | TCR 用户 ID(同临时 Token) | +| `Token` | 长期访问凭证密码,**仅在创建时返回一次**。用于 `docker login` 的 `--password` 参数 | +| `TokenId` | 凭证唯一标识符,用于后续 `ModifyInstanceToken` / `DeleteInstanceToken` 的 `--TokenId` 参数 | +| `ExpTime` | 过期时间,毫秒级 Unix 时间戳。本例约 10 年有效期 | + +> **重要**:`Token` 值**仅在创建时返回一次**。创建后务必立即保存——`DescribeInstanceToken` 不返回 `Token` 密码值,遗失后无法找回,只能删除旧凭证后重新创建。 + +### 步骤3:查看已有 Token + +```bash +tccli tcr DescribeInstanceToken \ + --RegistryId 'tcr-example' \ + --region ap-guangzhou \ + --output json +``` + +Output: + +```json +{ + "TotalCount": 1, + "Tokens": [ + { + "Id": "d8ogn8g08kblvtqbiun0", + "Desc": "my-longterm-token", + "RegistryId": "tcr-example", + "Enabled": true, + "CreatedAt": "2026-06-16T12:37:50+08:00", + "ExpiredAt": 2096959138174 + } + ], + "RequestId": "29618872-20f3-4673-b1ea-46a59bd9f2c7" +} +``` + +> 注意:查询结果中字段名为 `Id`(非 `TokenId`),用于 `ModifyInstanceToken` / `DeleteInstanceToken` 的 `--TokenId` 参数。`Token` 密码值不会在查询结果中返回。 + +### 步骤4:启用/禁用 Token + +```bash +# 启用 +tccli tcr ModifyInstanceToken \ + --RegistryId 'tcr-example' \ + --TokenId 'd8ogn8g08kblvtqbiun0' \ + --Enable true \ + --region ap-guangzhou \ + --output json + +# 禁用 +tccli tcr ModifyInstanceToken \ + --RegistryId 'tcr-example' \ + --TokenId 'd8ogn8g08kblvtqbiun0' \ + --Enable false \ + --region ap-guangzhou \ + --output json +``` + +禁用后,使用该凭证的 `docker login` 将失败(返回 `401 Unauthorized`)。生产环境中建议对不再使用但暂不删除的凭证先执行禁用。 + +### 步骤5:删除 Token + +```bash +tccli tcr DeleteInstanceToken \ + --RegistryId 'tcr-example' \ + --TokenId 'd8ogn8g08kblvtqbiun0' \ + --region ap-guangzhou \ + --output json +``` + +Output: + +```json +{ + "RequestId": "fcb487f7-68e0-41ed-9141-6b7dd62694f8" +} +``` + +> 删除操作不可逆。删除后,使用该凭证的 `docker login` 会话将在凭证缓存过期后失效。建议先禁用(步骤4)再删除,确保无活跃流水线依赖该凭证。 + +## 验证 + +### Control plane (tccli)——多维度验证 + +| # | 验证项 | 命令 | 期望结果 | +|---|--------|------|---------| +| 1 | Token 已创建 | `tccli tcr DescribeInstanceToken --RegistryId 'tcr-example' --region ap-guangzhou` | 列表中包含新创建的 `Id`,`Desc` 与创建时一致 | +| 2 | Token 已启用 | 同上 | 对应 `Enabled` 为 `true` | +| 3 | Token 已禁用 | 同上(禁用后执行) | 对应 `Enabled` 为 `false` | +| 4 | Token 已删除 | 同上(删除后执行) | 该 `Id` 不再出现在 `Tokens` 数组中 | +| 5 | 创建同名 Token(非幂等验证) | 重复执行步骤2 | 返回新的 `TokenId`(允许同名多条) | + +### Data plane (docker login) + +使用 Token 登录 TCR 实例: + +```bash +docker login tcr-example.tencentcloudcr.com --username 100049208872 --password +``` + +- 将 `tcr-example.tencentcloudcr.com` 替换为实例实际公网域名(`DescribeInstances` 返回的 `PublicDomain`) +- `` 和 `` 替换为创建时返回的对应值 +- 预期输出 `Login Succeeded` + +## 清理 + +> **删除 Token 不可逆。** 正在使用该 Token 的 CI/CD 流水线、开发者终端、TKE 集群(通过 TCR 插件)将在凭证缓存过期后丢失拉取/推送权限。建议清理前: +> 1. 先禁用(`ModifyInstanceToken --Enable false`)观察数日 +> 2. 确认无流水线告警后再执行删除 +> 3. TKE 插件或 CODING DevOps 自动创建的凭证,删除前需先解除关联 + +```bash +# 1. 先禁用 +tccli tcr ModifyInstanceToken \ + --RegistryId 'tcr-example' \ + --TokenId 'd8ogn8g08kblvtqbiun0' \ + --Enable false \ + --region ap-guangzhou + +# 2. 确认禁用生效 +tccli tcr DescribeInstanceToken \ + --RegistryId 'tcr-example' \ + --region ap-guangzhou \ + --filter "Tokens[?Id=='d8ogn8g08kblvtqbiun0'].Enabled" +# Expected: [false] + +# 3. 再删除 +tccli tcr DeleteInstanceToken \ + --RegistryId 'tcr-example' \ + --TokenId 'd8ogn8g08kblvtqbiun0' \ + --region ap-guangzhou +``` + +## 排障 + +| 现象 | 诊断命令 | 根因 | 修复 | +|------|---------|------|------| +| `docker login` 返回 `401 Unauthorized` | `tccli tcr DescribeInstanceToken --RegistryId 'tcr-example' --region ap-guangzhou` 检查 `Enabled` 与 Token 是否存在 | Token 已禁用或已删除 | 若禁用,执行 `ModifyInstanceToken --Enable true` 重新启用 | +| `docker login` 返回 `401 Unauthorized`(Token 状态正常) | 确认域名是否正确:`tccli tcr DescribeInstances --Registryids '["tcr-example"]' --region ap-guangzhou --filter "Registries[0].PublicDomain"` | 实例域名拼写错误,或使用了内网地址但当前网络不在 VPC 内 | 使用 `PublicDomain` 值重新登录 | +| `CreateInstanceToken` 返回 `UnauthorizedOperation` | — | 子账号缺少 `tcr:CreateInstanceToken` 权限 | 联系主账号授予 `QcloudTCRFullAccess` 或添加 `CreateInstanceToken` 到自定义策略 | +| `ModifyInstanceToken` / `DeleteInstanceToken` 报 `ResourceNotFound` | `tccli tcr DescribeInstanceToken --RegistryId 'tcr-example' --region ap-guangzhou` 核实 TokenId | `TokenId` 不存在或不属于指定 `RegistryId` | 使用 `DescribeInstanceToken` 返回的 `Id` 字段值作为 `--TokenId` | +| `Token` 密码值遗失 | `tccli tcr DescribeInstanceToken --RegistryId 'tcr-example' --region ap-guangzhou` | 查询接口不返回 `Token` 密码值 | 无法找回原始 Token,需删除旧凭证后重新创建(步骤5+步骤2) | +| 多个 Token 管理混乱 | `tccli tcr DescribeInstanceToken --RegistryId 'tcr-example' --region ap-guangzhou --output json` 审计列表 | 创建时未填写 `Desc`,或凭证数量过多 | 创建时填写有意义的 `Desc`,定期审计并清理无用 Token | +| TKE 插件或 CODING DevOps 自动创建的凭证删除被拒 | — | 凭证由外部服务自动创建并关联 | 先解除 TKE 集群关联或 CODING DevOps 集成,再删除凭证 | +| `CreateInstanceToken` 返回 `InvalidParameterValue` | — | `TokenType` 参数值非法(非 `temp` 或 `longterm`) | 确认 `--TokenType` 小写,值为 `temp` 或 `longterm` | +| `docker login` 提示 `Error saving credentials` | `docker --version` | Docker credential helper 配置问题 | 检查 `~/.docker/config.json` 中的 `credsStore` 设置,或使用 `--password-stdin` 替代 | + +## 下一步 + +- [服务级账号管理](../service-credentials)(page_id `89137`)——创建命名空间级权限隔离的机器人账号 +- [基于 CAM 管理子账号权限](../permissions/cam-subaccount)——管理子账号的 TCR 操作权限 +- [管理命名空间](../../image-creation/namespace)——创建命名空间后使用 Token 推送镜像 +- [获取实例访问域名](../../create)——确认 `PublicDomain` 用于 `docker login` + +## 控制台替代 + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 选择实例 → **访问凭证** → **新建访问凭证** → 填写描述 → 确认后**立即复制生成的 Token**(仅显示一次)。在凭证列表中可启用、禁用或删除凭证。 diff --git a/src/content/docs/cli/tcr/ops/access/domain/custom-domain-tcrctl.md b/src/content/docs/cli/tcr/ops/access/domain/custom-domain-tcrctl.md new file mode 100644 index 0000000..649a6a6 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/domain/custom-domain-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "配置自定义域名(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[配置自定义域名](https://cloud.tencent.com/document/product/1141/53879) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/access/domain/custom-domain.md b/src/content/docs/cli/tcr/ops/access/domain/custom-domain.md new file mode 100644 index 0000000..e4f626e --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/domain/custom-domain.md @@ -0,0 +1,477 @@ +--- +title: "配置自定义域名(tccli)" +description: "· page_id `53879`" +--- + +> 对照官方:[配置自定义域名](https://cloud.tencent.com/document/product/1141/53879) · page_id `53879` + +## 概述 + +通过 `tccli tcr CreateInstanceCustomizedDomain` 为 TCR 企业版实例配置自定义域名。自定义域名可实现: + +- **品牌统一**:使用公司规划的域名(如 `registry.example.com`)访问容器镜像服务 +- **零停机迁移**:从其他镜像仓库迁移至 TCR 时继续沿用原有域名,CI/CD 流水线和发布配置无需变更 +- **多域名共存**:一个实例可配置多个自定义域名,且不影响实例默认域名(`.tencentcloudcr.com`) + +核心链路:**生成/获取 SSL 证书 --> 上传至腾讯云 SSL 证书服务 --> 绑定至 TCR 实例**。证书可通过以下方式获取: + +| 方式 | 成本 | 适用场景 | 浏览器 | CLI(docker) | +|------|------|---------|:--:|:--:| +| `openssl` 自签名 | 零费用 | 开发/测试环境 | 警告 | 正常(需配置 CA 信任) | +| 购买商业 CA 证书 | 付费 | 生产环境 | 正常 | 正常 | +| `tccli ssl ApplyCertificate` | 付费 | 腾讯云一站式购买 | 正常 | 正常 | + +> **跨产品依赖**:自定义域名强依赖腾讯云 SSL 证书服务(`tccli ssl`)。无论证书来源,均须先通过 `tccli ssl UploadCertificate` 将证书上传至 SSL 服务,再使用返回的 `CertificateId` 绑定域名。 + +## 前置条件 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域(如 ap-guangzhou) + +# 3. 检查 CAM 权限 — TCR 相关 +# 需要以下 Action 名: +# tcr:DescribeInstances, tcr:DescribeInstanceCustomizedDomain +# tcr:CreateInstanceCustomizedDomain, tcr:DeleteInstanceCustomizedDomain +# 验证:执行 DescribeInstances 确认权限 +tccli tcr DescribeInstances --region ap-guangzhou +# expected: exit 0,返回实例列表(可为空 TotalCount: 0) + +# 4. 检查跨产品 CAM 权限 — SSL 证书服务 +# 需要以下 Action 名: +# ssl:UploadCertificate, ssl:DescribeCertificates +tccli ssl DescribeCertificates --region ap-guangzhou +# expected: exit 0,返回证书列表(可为空 TotalCount: 0) + +# 5. 检查 openssl(仅自签名证书方案需要) +openssl version +# expected: exit 0,显示 OpenSSL 版本 +``` + +### 资源检查 + +```bash +# 6. 确认 TCR 企业版实例存在且状态正常 +tccli tcr DescribeInstances --Registryids '[""]' --region ap-guangzhou +# expected: exit 0, Status: "Running" + +# 7. 确认目标域名已注册(境内实例需完成 ICP 备案;境外实例无需备案) +nslookup +# expected: 域名已注册并可解析(或后续将配置解析) + +# 8. 确认有可用的 SSL 证书(或准备生成) +tccli ssl DescribeCertificates --region ap-guangzhou +# expected: exit 0,若有可用证书则记录 CertificateId +``` + +前置依赖: +- 已完成[创建企业版实例](../../../create),实例 `Status` 为 `Running` +- 已拥有域名(可通过[域名注册服务](https://console.cloud.tencent.com/domain)注册) +- 境内实例使用的域名需完成 ICP 备案 +- 已安装 `openssl`(自签名证书方案),或已持有 PEM 格式证书文件(上传方案) + +## 控制台与 CLI 参数映射 + +| 控制台操作 | CLI | 幂等 | +|-----------|-----|:--:| +| 查看自定义域名列表 | `tccli tcr DescribeInstanceCustomizedDomain --RegistryId ` | 是 | +| 添加自定义域名 | `tccli tcr CreateInstanceCustomizedDomain` | 否(重名报错) | +| 更新域名证书 | 先 `DeleteInstanceCustomizedDomain`,再 `CreateInstanceCustomizedDomain`(无直接修改 API) | — | +| 删除自定义域名 | `tccli tcr DeleteInstanceCustomizedDomain` | 否(删除不存在的域名返回错误) | +| 上传 SSL 证书 | `tccli ssl UploadCertificate` | 否 | +| 查看 SSL 证书列表 | `tccli ssl DescribeCertificates` | 是 | + +## 关键字段说明 + +以下说明 `CreateInstanceCustomizedDomain` 和 `UploadCertificate` 的主要参数。 + +### TCR 侧(tccli tcr) + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | TCR 实例 ID,由 `DescribeInstances` 返回 | `ResourceNotFound` — 实例不存在或地域错误 | +| `DomainName` | String | 是 | 已完成 ICP 备案的自定义域名(境内实例),须与 SSL 证书绑定的域名一致 | `FailedOperation.DependenceError` — 证书与域名不匹配或域名未备案 | +| `CertificateId` | String | 是 | SSL 证书 ID,仅支持已在腾讯云 SSL 证书服务内托管的证书。购买证书 ID 格式 `cert-xxxxx`,上传证书 ID 为随机字符串 | `FailedOperation.CertificateNotFound` — 证书不存在或未托管至腾讯云 | + +### SSL 证书服务侧(tccli ssl) + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `CertificatePublicKey` | String | 是 | PEM 格式证书公钥。**注意参数名为 `PublicKey` 而非 `Cert`** | `InvalidParameter` — 格式不合法 | +| `CertificatePrivateKey` | String | 是 | PEM 格式证书私钥 | `InvalidParameter` — 格式不合法或与公钥不匹配 | +| `CertificateType` | String | 是 | 固定 `SVR`(服务器证书),TCR 仅接受服务器证书类型 | `InvalidParameter` — 填写其他类型(如 `CA`)则拒绝 | +| `Alias` | String | 否 | 证书别名,便于在证书列表中识别,建议填写有业务含义的名称 | 无严重后果,仅影响证书可识别性 | +| `Repeatable` | Boolean | 否 | 是否允许重复上传相同证书,默认 `false` | `true` 时可重复上传(不推荐) | + +## 操作步骤 + +### 步骤1:生成或准备 SSL 证书 + +若已有 CA 签发的正式证书(PEM 格式),跳过此步。若为开发/测试环境或无已有证书,使用 `openssl` 生成自签名证书。 + +#### 选择依据 + +- **自签名 vs 正式证书**:开发/测试环境使用 `openssl` 生成自签名证书,零费用、即时可用;生产环境必须使用 CA 签发的正式证书。 +- **有效期**:自签名证书建议 `-days 365`(1 年)。测试环境可使用更短有效期(如 `-days 1`)。 +- **CN 字段**:`/CN=` 必须与后续绑定的自定义域名完全一致,否则客户端验证失败。 +- **加密强度**:建议 RSA 2048 位(`rsa:2048`),性能与安全性均衡。 + +#### 执行生成 + +```bash +openssl req -x509 -newkey rsa:2048 \ + -keyout \ + -out \ + -days 1 \ + -subj '/CN=' \ + -nodes +# expected: exit 0,生成 cert.pem 和 key.pem +``` + +| 参数 | 说明 | +|------|------| +| `-x509` | 输出自签名 X.509 证书(非证书请求 CSR) | +| `-newkey rsa:2048` | 同时生成 2048 位 RSA 私钥 | +| `-keyout ` | 私钥输出路径 | +| `-out ` | 证书输出路径 | +| `-days 1` | 证书有效期天数(测试用 1 天,生产建议 365 天) | +| `-subj '/CN='` | 证书主题,CN 必须与后续绑定的域名一致 | +| `-nodes` | 私钥不加密码保护(No DES),便于自动化脚本读取 | + +> **真机实测**:`openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 1 -subj '/CN=test-kerwinwjyan-rewrite.example.com' -nodes` 执行成功,生成证书和私钥。 + +### 步骤2:上传 SSL 证书至腾讯云 + +通过 `tccli ssl UploadCertificate` 将证书托管至腾讯云 SSL 证书服务。已购买腾讯云 SSL 证书(`cert-xxxxx` 格式)且已签发的,跳过此步。 + +#### 选择依据 + +- **CertificateType**:固定选 `SVR`(服务器证书),TCR 自定义域名只接受服务器证书类型。 +- **Alias**:推荐填写有业务含义的别名,如 `"tcr-custom-domain-test"`,便于在证书列表(`DescribeCertificates`)中快速识别。 +- **参数名注意**:`--CertificatePublicKey` 参数名中为 `PublicKey`,非 `Cert`。拼写错误将导致 `Unknown parameter`。 + +#### 最小配置(仅必填字段) + +```bash +tccli ssl UploadCertificate \ + --CertificatePublicKey "$(cat )" \ + --CertificatePrivateKey "$(cat )" \ + --CertificateType SVR \ + --region ap-guangzhou +# expected: exit 0,返回 CertificateId +``` + +#### 增强配置(含别名) + +```bash +tccli ssl UploadCertificate \ + --CertificatePublicKey "$(cat )" \ + --CertificatePrivateKey "$(cat )" \ + --CertificateType SVR \ + --Alias '<证书别名>' \ + --region ap-guangzhou +# expected: exit 0,返回 CertificateId +``` + +**输出**: + +```json +{ + "CertificateId": "YWwE5I4b", + "RequestId": "..." +} +``` + +> **真机实测**:`tccli ssl UploadCertificate --CertificatePublicKey "$(cat cert.pem)" --CertificatePrivateKey "$(cat key.pem)" --CertificateType SVR --Alias 'tcr-test-cert' --region ap-guangzhou` 返回 `CertificateId: "YWwE5I4b"`。 + +记录返回的 `CertificateId`,后续步骤使用。 + +### 步骤3:查看当前自定义域名列表 + +绑定前确认当前未配置目标域名,避免重复创建导致报错: + +```bash +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId \ + --region ap-guangzhou \ + --output json +# expected: exit 0,返回当前域名列表 +``` + +**输出(无自定义域名时)**: + +```json +{ + "DomainInfoList": [], + "TotalCount": 0, + "RequestId": "..." +} +``` + +### 步骤4:绑定自定义域名至 TCR 实例 + +#### 选择依据 + +- **DomainName**:使用已完成 ICP 备案的域名(境内实例),须与 SSL 证书中 CN 字段(或 SAN 字段)一致。 +- **CertificateId**:使用步骤 2 上传返回的 `CertificateId`(如 `YWwE5I4b`),或已购买证书的 ID(`cert-xxxxx` 格式)。 +- **无额外可选参数**:此操作仅 3 个必填参数(`RegistryId`、`DomainName`、`CertificateId`),含义显然,单层命令即可。 + +```bash +tccli tcr CreateInstanceCustomizedDomain \ + --RegistryId \ + --DomainName \ + --CertificateId \ + --region ap-guangzhou +# expected: exit 0,返回 RequestId +``` + +**输出**: + +```json +{ + "RequestId": "0ecf086e-..." +} +``` + +> **真机实测**:`tccli tcr CreateInstanceCustomizedDomain --RegistryId tcr-nn8smeyj --DomainName test-kerwinwjyan-rewrite.example.com --CertificateId YWwE5I4b --region ap-guangzhou` 返回 `RequestId: "0ecf086e-..."`。 + +### 步骤5:验证域名绑定状态 + +轮询确认自定义域名已生效: + +```bash +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId \ + --region ap-guangzhou \ + --output json +# expected: exit 0, DomainInfoList 含目标域名,Status: "SUCCESS" +``` + +**输出(绑定成功后)**: + +```json +{ + "DomainInfoList": [ + { + "DomainName": "test-kerwinwjyan-rewrite.example.com", + "CertId": "YWwE5I4b", + "Status": "SUCCESS" + } + ], + "TotalCount": 1, + "RequestId": "..." +} +``` + +> **真机实测**:查询返回 `DomainInfoList: [{DomainName: "test-kerwinwjyan-rewrite.example.com", CertId: "YWwE5I4b", Status: "SUCCESS"}]`,确认绑定成功。 + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 状态 | `DomainInfoList[*].Status` | `SUCCESS` | +| 域名匹配 | `DomainInfoList[*].DomainName` | 与 `--DomainName` 完全一致 | +| 证书绑定 | `DomainInfoList[*].CertId` | 与 `--CertificateId` 完全一致 | + +`Status: "SUCCESS"` 表示自定义域名已生效,证书已正确下发。 + +### 步骤6:更新域名证书(可选) + +TCR 无直接修改自定义域名证书的 API。如需更新证书(证书过期或需升级),采用「先删后建」模式: + +```bash +# 删除旧域名绑定 +tccli tcr DeleteInstanceCustomizedDomain \ + --RegistryId \ + --DomainName \ + --region ap-guangzhou + +# 使用新证书重新创建 +tccli tcr CreateInstanceCustomizedDomain \ + --RegistryId \ + --DomainName \ + --CertificateId \ + --region ap-guangzhou +``` + +> **注意**:删除再创建的过程中,自定义域名短暂不可用(通常 1-2 分钟)。更新证书后无需重新配置 DNS 解析记录。 + +### 步骤7:删除自定义域名 + +```bash +tccli tcr DeleteInstanceCustomizedDomain \ + --RegistryId \ + --DomainName \ + --region ap-guangzhou +# expected: exit 0,返回 RequestId +``` + +> **真机实测**:`tccli tcr DeleteInstanceCustomizedDomain --RegistryId tcr-nn8smeyj --DomainName test-kerwinwjyan-rewrite.example.com --region ap-guangzhou` 成功删除。 + +## 验证 + +### 控制面(tccli) + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 证书已上传 | `tccli ssl DescribeCertificates --region ap-guangzhou --output json \| jq '.Certificates[] \| select(.CertificateId=="")'` | 返回证书详情 | +| TLD 域名已绑定 | `tccli tcr DescribeInstanceCustomizedDomain --RegistryId --region ap-guangzhou` | `DomainInfoList` 含目标域名,`Status: "SUCCESS"` | +| 域名绑定状态 | `tccli tcr DescribeInstanceCustomizedDomain --RegistryId --region ap-guangzhou --output json \| jq '.DomainInfoList[0].Status'` | `"SUCCESS"` | +| 证书 ID 匹配 | `tccli tcr DescribeInstanceCustomizedDomain --RegistryId --region ap-guangzhou --output json \| jq '.DomainInfoList[0].CertId'` | 与上传返回的 `CertificateId` 一致 | +| 域名已删除 | `tccli tcr DescribeInstanceCustomizedDomain --RegistryId --region ap-guangzhou` | `DomainInfoList` 不含目标域名,或 `TotalCount: 0` | + +### 数据面 + +自定义域名绑定后,需配合 DNS 解析和网络配置才能实际访问: + +```bash +# 验证 DNS 解析生效 +nslookup +# expected: 返回解析结果指向 TCR 内网 IP 或实例默认域名 + +# 验证 HTTPS 可达性 +curl -I https:///v2/ +# expected: HTTP/1.1 401 Unauthorized(401 表示可达但未认证,为正常响应) +``` + +> DNS 解析配置(DNSPod/Private DNS)和网络配置(公网入口/内网 VPC 接入)非本页面范围,详见[下一步](#下一步)。 + +## 清理 + +> **副作用警告**:删除自定义域名后,使用该域名的 Docker 客户端将无法通过自定义域名登录和拉取镜像,需切换至 TCR 默认公网域名(`.tencentcloudcr.com`)。 + +### 1. 删除自定义域名 + +清理前状态检查: + +```bash +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId \ + --region ap-guangzhou +# expected: 确认待删除的目标域名 +``` + +```bash +tccli tcr DeleteInstanceCustomizedDomain \ + --RegistryId \ + --DomainName \ + --region ap-guangzhou +# expected: exit 0,返回 RequestId +``` + +验证已删除: + +```bash +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId \ + --region ap-guangzhou +# expected: DomainInfoList 中不再包含目标域名,或 TotalCount 为 0 +``` + +### 2. 清理 DNS 解析记录 + +同时清理在 DNSPod / Private DNS 中为该域名配置的解析记录,避免 DNS 污染。 + +### 3. 清理 SSL 证书(可选) + +> 删除自定义域名不会自动删除关联的 SSL 证书。如证书不再需要,前往 [SSL 证书控制台](https://console.cloud.tencent.com/ssl) 手动删除。注意:**已过期或已吊销的证书不支持删除**。 + +## 排障 + +### 跨产品依赖链 + +自定义域名功能存在以下跨产品依赖,排障时需逐层检查: + +``` +自定义域名 (CreateInstanceCustomizedDomain) + └─ 前提:CertificateId(已在腾讯云 SSL 证书服务中托管) + └─ 前提:ssl UploadCertificate(上传证书至腾讯云 SSL) + ├─ 前提:证书文件(PEM 格式公钥 + 私钥) + └─ 前提:CertificateType: SVR(服务器证书类型) +``` + +> **排障入口**:若 `CreateInstanceCustomizedDomain` 返回证书相关错误,首先执行 `tccli ssl DescribeCertificates --region ` 确认证书在 SSL 服务中的状态,而非在 TCR 侧排查。证书问题(过期、不匹配、类型错误)必须在 SSL 服务侧解决。 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ssl UploadCertificate` 返回 Unknown parameter: `CertificateCert` | 检查参数名拼写 | 错误使用了 `--CertificateCert`,正确参数名为 `--CertificatePublicKey` | 改用 `--CertificatePublicKey "$(cat )"` | +| `ssl UploadCertificate` 返回 `InvalidParameter` | 检查 PEM 格式:`openssl x509 -in -text -noout` 验证证书;`openssl rsa -in -check -noout` 验证私钥 | 证书公钥/私钥格式不合法或内容不匹配 | 确保证书和私钥均为 PEM 格式(以 `-----BEGIN` 开头),且属于同一证书对 | +| `CreateInstanceCustomizedDomain` 返回 `FailedOperation.DependenceError` | `tccli ssl DescribeCertificates --region ap-guangzhou` 确认证书存在且状态正常 | `CertificateId` 无效或证书未托管至腾讯云 | 确认证书 ID 正确,证书已在 SSL 证书服务中托管且已绑定该域名 | +| `CreateInstanceCustomizedDomain` 返回 `FailedOperation.CertificateNotFound` | `tccli ssl DescribeCertificates --region ap-guangzhou --output json \| jq '.Certificates[] \| .CertificateId'` 搜索目标证书 | 证书 ID 不存在或属于其他账号 | 确认 `CertificateId` 来自 `ssl UploadCertificate` 返回值或 SSL 控制台 | +| `CreateInstanceCustomizedDomain` 返回 `FailedOperation` | `tccli tcr DescribeInstances --Registryids '[""]' --region ap-guangzhou` 确认实例 Status | 实例非 `Running` 状态,或域名已被占用 | 确认实例状态正常;检查域名是否已绑定至当前实例或其它实例 | +| `DeleteInstanceCustomizedDomain` 返回错误 | `tccli tcr DescribeInstanceCustomizedDomain --RegistryId --region ap-guangzhou` 确认域名绑定状态 | 域名未绑定至当前实例 | 确认 `DomainName` 拼写正确,且该域名确实已绑定 | +| `CreateInstanceCustomizedDomain` 返回 `InvalidParameter` | 检查域名格式及备案状态 | 域名格式非法或境内实例域名未备案 | 确认域名格式正确(如 `registry.example.com`),境内实例确保已完成 ICP 备案 | + +### 创建成功但状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 自定义域名 `Status` 长时间为 `CREATING`(超过 2 分钟) | `tccli tcr DescribeInstanceCustomizedDomain --RegistryId --region ap-guangzhou` 持续轮询 | SSL 证书下发延迟 | 等待 1-2 分钟后重新查询;若持续非 `SUCCESS`,检查 SSL 证书状态是否正常 | +| 自定义域名 `Status` 为 `FAILED` | `tccli ssl DescribeCertificates --region ap-guangzhou` 确认证书详情 | 证书与域名不匹配或证书状态异常 | 确认证书 CN/SAN 包含目标域名且状态为「已签发」;重新上传或购买正确证书 | +| 自定义域名绑定成功但 HTTPS 访问返回 `certificate signed by unknown authority` | `echo \| openssl s_client -connect :443 -servername 2>/dev/null \| openssl x509 -noout -issuer` 检查证书链 | 使用了自签名证书,客户端不信任该 CA | **CLI 使用不受影响**。若需消除警告,在各客户端添加 CA 证书:`mkdir -p /etc/docker/certs.d// && cp /etc/docker/certs.d//ca.crt`;浏览器访问需手动信任该证书 | +| 自定义域名绑定成功但无法访问(502/503) | `curl -I https:///v2/` 测试连通性 | DNS 解析未配置、网络链路不通、或公网入口未开启 | 检查 DNS 解析记录;公网场景确认公网入口已开启且白名单已配置;内网场景确认 VPC 已接入 | +| DNS 解析记录配置后长时间不生效 | `nslookup ` 检查解析结果 | DNS TTL 缓存未过期 | 等待 TTL 过期(通常数分钟),或刷新本地 DNS 缓存 | + +### 真机验证全链路记录 + +以下为本次 rewrite 真实环境执行的完整链路(已验证通过,实例 `tcr-nn8smeyj`,证书 `YWwE5I4b`): + +```bash +# 1. 生成自签名证书(有效期 1 天) +openssl req -x509 -newkey rsa:2048 \ + -keyout key.pem -out cert.pem \ + -days 1 -nodes \ + -subj '/CN=test-kerwinwjyan-rewrite.example.com' +# 成功:生成 key.pem 和 cert.pem + +# 2. 上传证书至腾讯云 SSL 证书服务 +tccli ssl UploadCertificate \ + --CertificatePublicKey "$(cat cert.pem)" \ + --CertificatePrivateKey "$(cat key.pem)" \ + --CertificateType SVR \ + --Alias 'tcr-test-cert' \ + --region ap-guangzhou +# → CertificateId: "YWwE5I4b" + +# 3. 绑定自定义域名至 TCR 实例 +tccli tcr CreateInstanceCustomizedDomain \ + --RegistryId tcr-nn8smeyj \ + --DomainName test-kerwinwjyan-rewrite.example.com \ + --CertificateId YWwE5I4b \ + --region ap-guangzhou +# → RequestId: "0ecf086e-..." + +# 4. 验证绑定状态 +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId tcr-nn8smeyj \ + --region ap-guangzhou +# → DomainInfoList: [{DomainName: "test-kerwinwjyan-rewrite.example.com", CertId: "YWwE5I4b", Status: "SUCCESS"}] + +# 5. 删除自定义域名 +tccli tcr DeleteInstanceCustomizedDomain \ + --RegistryId tcr-nn8smeyj \ + --DomainName test-kerwinwjyan-rewrite.example.com \ + --region ap-guangzhou +# 成功删除 +``` + +> 自签名证书仅适用于开发/测试环境。生产环境请使用 CA 签发的正式证书。`ssl UploadCertificate` 支持的 PEM 格式包括 RSA 2048、ECC 等多种密钥类型。证书上传后可跨 TCR 实例复用(同一证书 ID 可用于多个实例)。 + +## 下一步 + +- [配置内网访问控制](../../network/private-access) — VPC 内网链路管理(`ManageInternalEndpoint`),使 VPC 内容器客户端可通过内网访问 TCR 实例 +- [配置公网访问控制](../../network/public-access) — 公网白名单管理(`CreateSecurityPolicy` / `DeleteSecurityPolicy`),使公网客户端可访问 TCR 实例 +- [配置自定义域名及云联网实现跨地域内网访问](../../../../practices/custom-domain-ccn) — 跨地域统一域名方案(CCN + Private DNS + 自定义域名) +- [管理命名空间](../../../image-creation/namespace) — 创建命名空间与镜像仓库,配合自定义域名推送/拉取镜像 +- [环境准备](../../../../../index.md) — 返回 TCR 工具链入口 + +## 控制台替代 + +[容器镜像服务控制台](https://console.cloud.tencent.com/tcr) -> 左侧导航栏**域名管理** -> 选择实例地域和实例 ID -> 单击**添加自定义域名**,在弹窗中配置域名及证书信息后确定。控制台支持**更新证书**及**删除**操作。控制台上传证书需先在 [SSL 证书控制台](https://console.cloud.tencent.com/ssl) 完成证书托管。 diff --git a/src/content/docs/cli/tcr/ops/access/domain/custom-domain/custom-domain-tcrctl.md b/src/content/docs/cli/tcr/ops/access/domain/custom-domain/custom-domain-tcrctl.md new file mode 100644 index 0000000..649a6a6 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/domain/custom-domain/custom-domain-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "配置自定义域名(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[配置自定义域名](https://cloud.tencent.com/document/product/1141/53879) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/access/domain/custom-domain/custom-domain.md b/src/content/docs/cli/tcr/ops/access/domain/custom-domain/custom-domain.md new file mode 100644 index 0000000..e30a881 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/domain/custom-domain/custom-domain.md @@ -0,0 +1,477 @@ +--- +title: "配置自定义域名(tccli)" +description: "· page_id `53879`" +--- + +> 对照官方:[配置自定义域名](https://cloud.tencent.com/document/product/1141/53879) · page_id `53879` + +## 概述 + +通过 `tccli tcr CreateInstanceCustomizedDomain` 为 TCR 企业版实例配置自定义域名。自定义域名可实现: + +- **品牌统一**:使用公司规划的域名(如 `registry.example.com`)访问容器镜像服务 +- **零停机迁移**:从其他镜像仓库迁移至 TCR 时继续沿用原有域名,CI/CD 流水线和发布配置无需变更 +- **多域名共存**:一个实例可配置多个自定义域名,且不影响实例默认域名(`.tencentcloudcr.com`) + +核心链路:**生成/获取 SSL 证书 --> 上传至腾讯云 SSL 证书服务 --> 绑定至 TCR 实例**。证书可通过以下方式获取: + +| 方式 | 成本 | 适用场景 | 浏览器 | CLI(docker) | +|------|------|---------|:--:|:--:| +| `openssl` 自签名 | 零费用 | 开发/测试环境 | 警告 | 正常(需配置 CA 信任) | +| 购买商业 CA 证书 | 付费 | 生产环境 | 正常 | 正常 | +| `tccli ssl ApplyCertificate` | 付费 | 腾讯云一站式购买 | 正常 | 正常 | + +> **跨产品依赖**:自定义域名强依赖腾讯云 SSL 证书服务(`tccli ssl`)。无论证书来源,均须先通过 `tccli ssl UploadCertificate` 将证书上传至 SSL 服务,再使用返回的 `CertificateId` 绑定域名。 + +## 前置条件 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域(如 ap-guangzhou) + +# 3. 检查 CAM 权限 — TCR 相关 +# 需要以下 Action 名: +# tcr:DescribeInstances, tcr:DescribeInstanceCustomizedDomain +# tcr:CreateInstanceCustomizedDomain, tcr:DeleteInstanceCustomizedDomain +# 验证:执行 DescribeInstances 确认权限 +tccli tcr DescribeInstances --region ap-guangzhou +# expected: exit 0,返回实例列表(可为空 TotalCount: 0) + +# 4. 检查跨产品 CAM 权限 — SSL 证书服务 +# 需要以下 Action 名: +# ssl:UploadCertificate, ssl:DescribeCertificates +tccli ssl DescribeCertificates --region ap-guangzhou +# expected: exit 0,返回证书列表(可为空 TotalCount: 0) + +# 5. 检查 openssl(仅自签名证书方案需要) +openssl version +# expected: exit 0,显示 OpenSSL 版本 +``` + +### 资源检查 + +```bash +# 6. 确认 TCR 企业版实例存在且状态正常 +tccli tcr DescribeInstances --Registryids '[""]' --region ap-guangzhou +# expected: exit 0, Status: "Running" + +# 7. 确认目标域名已注册(境内实例需完成 ICP 备案;境外实例无需备案) +nslookup +# expected: 域名已注册并可解析(或后续将配置解析) + +# 8. 确认有可用的 SSL 证书(或准备生成) +tccli ssl DescribeCertificates --region ap-guangzhou +# expected: exit 0,若有可用证书则记录 CertificateId +``` + +前置依赖: +- 已完成[创建企业版实例](../../../create),实例 `Status` 为 `Running` +- 已拥有域名(可通过[域名注册服务](https://console.cloud.tencent.com/domain)注册) +- 境内实例使用的域名需完成 ICP 备案 +- 已安装 `openssl`(自签名证书方案),或已持有 PEM 格式证书文件(上传方案) + +## 控制台与 CLI 参数映射 + +| 控制台操作 | CLI | 幂等 | +|-----------|-----|:--:| +| 查看自定义域名列表 | `tccli tcr DescribeInstanceCustomizedDomain --RegistryId ` | 是 | +| 添加自定义域名 | `tccli tcr CreateInstanceCustomizedDomain` | 否(重名报错) | +| 更新域名证书 | 先 `DeleteInstanceCustomizedDomain`,再 `CreateInstanceCustomizedDomain`(无直接修改 API) | — | +| 删除自定义域名 | `tccli tcr DeleteInstanceCustomizedDomain` | 否(删除不存在的域名返回错误) | +| 上传 SSL 证书 | `tccli ssl UploadCertificate` | 否 | +| 查看 SSL 证书列表 | `tccli ssl DescribeCertificates` | 是 | + +## 关键字段说明 + +以下说明 `CreateInstanceCustomizedDomain` 和 `UploadCertificate` 的主要参数。 + +### TCR 侧(tccli tcr) + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | TCR 实例 ID,由 `DescribeInstances` 返回 | `ResourceNotFound` — 实例不存在或地域错误 | +| `DomainName` | String | 是 | 已完成 ICP 备案的自定义域名(境内实例),须与 SSL 证书绑定的域名一致 | `FailedOperation.DependenceError` — 证书与域名不匹配或域名未备案 | +| `CertificateId` | String | 是 | SSL 证书 ID,仅支持已在腾讯云 SSL 证书服务内托管的证书。购买证书 ID 格式 `cert-xxxxx`,上传证书 ID 为随机字符串 | `FailedOperation.CertificateNotFound` — 证书不存在或未托管至腾讯云 | + +### SSL 证书服务侧(tccli ssl) + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `CertificatePublicKey` | String | 是 | PEM 格式证书公钥。**注意参数名为 `PublicKey` 而非 `Cert`** | `InvalidParameter` — 格式不合法 | +| `CertificatePrivateKey` | String | 是 | PEM 格式证书私钥 | `InvalidParameter` — 格式不合法或与公钥不匹配 | +| `CertificateType` | String | 是 | 固定 `SVR`(服务器证书),TCR 仅接受服务器证书类型 | `InvalidParameter` — 填写其他类型(如 `CA`)则拒绝 | +| `Alias` | String | 否 | 证书别名,便于在证书列表中识别,建议填写有业务含义的名称 | 无严重后果,仅影响证书可识别性 | +| `Repeatable` | Boolean | 否 | 是否允许重复上传相同证书,默认 `false` | `true` 时可重复上传(不推荐) | + +## 操作步骤 + +### 步骤1:生成或准备 SSL 证书 + +若已有 CA 签发的正式证书(PEM 格式),跳过此步。若为开发/测试环境或无已有证书,使用 `openssl` 生成自签名证书。 + +#### 选择依据 + +- **自签名 vs 正式证书**:开发/测试环境使用 `openssl` 生成自签名证书,零费用、即时可用;生产环境必须使用 CA 签发的正式证书。 +- **有效期**:自签名证书建议 `-days 365`(1 年)。测试环境可使用更短有效期(如 `-days 1`)。 +- **CN 字段**:`/CN=` 必须与后续绑定的自定义域名完全一致,否则客户端验证失败。 +- **加密强度**:建议 RSA 2048 位(`rsa:2048`),性能与安全性均衡。 + +#### 执行生成 + +```bash +openssl req -x509 -newkey rsa:2048 \ + -keyout \ + -out \ + -days 1 \ + -subj '/CN=' \ + -nodes +# expected: exit 0,生成 cert.pem 和 key.pem +``` + +| 参数 | 说明 | +|------|------| +| `-x509` | 输出自签名 X.509 证书(非证书请求 CSR) | +| `-newkey rsa:2048` | 同时生成 2048 位 RSA 私钥 | +| `-keyout ` | 私钥输出路径 | +| `-out ` | 证书输出路径 | +| `-days 1` | 证书有效期天数(测试用 1 天,生产建议 365 天) | +| `-subj '/CN='` | 证书主题,CN 必须与后续绑定的域名一致 | +| `-nodes` | 私钥不加密码保护(No DES),便于自动化脚本读取 | + +> **真机实测**:`openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 1 -subj '/CN=test-kerwinwjyan-rewrite.example.com' -nodes` 执行成功,生成证书和私钥。 + +### 步骤2:上传 SSL 证书至腾讯云 + +通过 `tccli ssl UploadCertificate` 将证书托管至腾讯云 SSL 证书服务。已购买腾讯云 SSL 证书(`cert-xxxxx` 格式)且已签发的,跳过此步。 + +#### 选择依据 + +- **CertificateType**:固定选 `SVR`(服务器证书),TCR 自定义域名只接受服务器证书类型。 +- **Alias**:推荐填写有业务含义的别名,如 `"tcr-custom-domain-test"`,便于在证书列表(`DescribeCertificates`)中快速识别。 +- **参数名注意**:`--CertificatePublicKey` 参数名中为 `PublicKey`,非 `Cert`。拼写错误将导致 `Unknown parameter`。 + +#### 最小配置(仅必填字段) + +```bash +tccli ssl UploadCertificate \ + --CertificatePublicKey "$(cat )" \ + --CertificatePrivateKey "$(cat )" \ + --CertificateType SVR \ + --region ap-guangzhou +# expected: exit 0,返回 CertificateId +``` + +#### 增强配置(含别名) + +```bash +tccli ssl UploadCertificate \ + --CertificatePublicKey "$(cat )" \ + --CertificatePrivateKey "$(cat )" \ + --CertificateType SVR \ + --Alias '<证书别名>' \ + --region ap-guangzhou +# expected: exit 0,返回 CertificateId +``` + +**输出**: + +```json +{ + "CertificateId": "YWwE5I4b", + "RequestId": "..." +} +``` + +> **真机实测**:`tccli ssl UploadCertificate --CertificatePublicKey "$(cat cert.pem)" --CertificatePrivateKey "$(cat key.pem)" --CertificateType SVR --Alias 'tcr-test-cert' --region ap-guangzhou` 返回 `CertificateId: "YWwE5I4b"`。 + +记录返回的 `CertificateId`,后续步骤使用。 + +### 步骤3:查看当前自定义域名列表 + +绑定前确认当前未配置目标域名,避免重复创建导致报错: + +```bash +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId \ + --region ap-guangzhou \ + --output json +# expected: exit 0,返回当前域名列表 +``` + +**输出(无自定义域名时)**: + +```json +{ + "DomainInfoList": [], + "TotalCount": 0, + "RequestId": "..." +} +``` + +### 步骤4:绑定自定义域名至 TCR 实例 + +#### 选择依据 + +- **DomainName**:使用已完成 ICP 备案的域名(境内实例),须与 SSL 证书中 CN 字段(或 SAN 字段)一致。 +- **CertificateId**:使用步骤 2 上传返回的 `CertificateId`(如 `YWwE5I4b`),或已购买证书的 ID(`cert-xxxxx` 格式)。 +- **无额外可选参数**:此操作仅 3 个必填参数(`RegistryId`、`DomainName`、`CertificateId`),含义显然,单层命令即可。 + +```bash +tccli tcr CreateInstanceCustomizedDomain \ + --RegistryId \ + --DomainName \ + --CertificateId \ + --region ap-guangzhou +# expected: exit 0,返回 RequestId +``` + +**输出**: + +```json +{ + "RequestId": "0ecf086e-..." +} +``` + +> **真机实测**:`tccli tcr CreateInstanceCustomizedDomain --RegistryId tcr-nn8smeyj --DomainName test-kerwinwjyan-rewrite.example.com --CertificateId YWwE5I4b --region ap-guangzhou` 返回 `RequestId: "0ecf086e-..."`。 + +### 步骤5:验证域名绑定状态 + +轮询确认自定义域名已生效: + +```bash +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId \ + --region ap-guangzhou \ + --output json +# expected: exit 0, DomainInfoList 含目标域名,Status: "SUCCESS" +``` + +**输出(绑定成功后)**: + +```json +{ + "DomainInfoList": [ + { + "DomainName": "test-kerwinwjyan-rewrite.example.com", + "CertId": "YWwE5I4b", + "Status": "SUCCESS" + } + ], + "TotalCount": 1, + "RequestId": "..." +} +``` + +> **真机实测**:查询返回 `DomainInfoList: [{DomainName: "test-kerwinwjyan-rewrite.example.com", CertId: "YWwE5I4b", Status: "SUCCESS"}]`,确认绑定成功。 + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 状态 | `DomainInfoList[*].Status` | `SUCCESS` | +| 域名匹配 | `DomainInfoList[*].DomainName` | 与 `--DomainName` 完全一致 | +| 证书绑定 | `DomainInfoList[*].CertId` | 与 `--CertificateId` 完全一致 | + +`Status: "SUCCESS"` 表示自定义域名已生效,证书已正确下发。 + +### 步骤6:更新域名证书(可选) + +TCR 无直接修改自定义域名证书的 API。如需更新证书(证书过期或需升级),采用「先删后建」模式: + +```bash +# 删除旧域名绑定 +tccli tcr DeleteInstanceCustomizedDomain \ + --RegistryId \ + --DomainName \ + --region ap-guangzhou + +# 使用新证书重新创建 +tccli tcr CreateInstanceCustomizedDomain \ + --RegistryId \ + --DomainName \ + --CertificateId \ + --region ap-guangzhou +``` + +> **注意**:删除再创建的过程中,自定义域名短暂不可用(通常 1-2 分钟)。更新证书后无需重新配置 DNS 解析记录。 + +### 步骤7:删除自定义域名 + +```bash +tccli tcr DeleteInstanceCustomizedDomain \ + --RegistryId \ + --DomainName \ + --region ap-guangzhou +# expected: exit 0,返回 RequestId +``` + +> **真机实测**:`tccli tcr DeleteInstanceCustomizedDomain --RegistryId tcr-nn8smeyj --DomainName test-kerwinwjyan-rewrite.example.com --region ap-guangzhou` 成功删除。 + +## 验证 + +### 控制面(tccli) + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 证书已上传 | `tccli ssl DescribeCertificates --region ap-guangzhou --output json \| jq '.Certificates[] \| select(.CertificateId=="")'` | 返回证书详情 | +| TLD 域名已绑定 | `tccli tcr DescribeInstanceCustomizedDomain --RegistryId --region ap-guangzhou` | `DomainInfoList` 含目标域名,`Status: "SUCCESS"` | +| 域名绑定状态 | `tccli tcr DescribeInstanceCustomizedDomain --RegistryId --region ap-guangzhou --output json \| jq '.DomainInfoList[0].Status'` | `"SUCCESS"` | +| 证书 ID 匹配 | `tccli tcr DescribeInstanceCustomizedDomain --RegistryId --region ap-guangzhou --output json \| jq '.DomainInfoList[0].CertId'` | 与上传返回的 `CertificateId` 一致 | +| 域名已删除 | `tccli tcr DescribeInstanceCustomizedDomain --RegistryId --region ap-guangzhou` | `DomainInfoList` 不含目标域名,或 `TotalCount: 0` | + +### 数据面 + +自定义域名绑定后,需配合 DNS 解析和网络配置才能实际访问: + +```bash +# 验证 DNS 解析生效 +nslookup +# expected: 返回解析结果指向 TCR 内网 IP 或实例默认域名 + +# 验证 HTTPS 可达性 +curl -I https:///v2/ +# expected: HTTP/1.1 401 Unauthorized(401 表示可达但未认证,为正常响应) +``` + +> DNS 解析配置(DNSPod/Private DNS)和网络配置(公网入口/内网 VPC 接入)非本页面范围,详见[下一步](#下一步)。 + +## 清理 + +> **副作用警告**:删除自定义域名后,使用该域名的 Docker 客户端将无法通过自定义域名登录和拉取镜像,需切换至 TCR 默认公网域名(`.tencentcloudcr.com`)。 + +### 1. 删除自定义域名 + +清理前状态检查: + +```bash +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId \ + --region ap-guangzhou +# expected: 确认待删除的目标域名 +``` + +```bash +tccli tcr DeleteInstanceCustomizedDomain \ + --RegistryId \ + --DomainName \ + --region ap-guangzhou +# expected: exit 0,返回 RequestId +``` + +验证已删除: + +```bash +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId \ + --region ap-guangzhou +# expected: DomainInfoList 中不再包含目标域名,或 TotalCount 为 0 +``` + +### 2. 清理 DNS 解析记录 + +同时清理在 DNSPod / Private DNS 中为该域名配置的解析记录,避免 DNS 污染。 + +### 3. 清理 SSL 证书(可选) + +> 删除自定义域名不会自动删除关联的 SSL 证书。如证书不再需要,前往 [SSL 证书控制台](https://console.cloud.tencent.com/ssl) 手动删除。注意:**已过期或已吊销的证书不支持删除**。 + +## 排障 + +### 跨产品依赖链 + +自定义域名功能存在以下跨产品依赖,排障时需逐层检查: + +``` +自定义域名 (CreateInstanceCustomizedDomain) + └─ 前提:CertificateId(已在腾讯云 SSL 证书服务中托管) + └─ 前提:ssl UploadCertificate(上传证书至腾讯云 SSL) + ├─ 前提:证书文件(PEM 格式公钥 + 私钥) + └─ 前提:CertificateType: SVR(服务器证书类型) +``` + +> **排障入口**:若 `CreateInstanceCustomizedDomain` 返回证书相关错误,首先执行 `tccli ssl DescribeCertificates --region ` 确认证书在 SSL 服务中的状态,而非在 TCR 侧排查。证书问题(过期、不匹配、类型错误)必须在 SSL 服务侧解决。 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ssl UploadCertificate` 返回 Unknown parameter: `CertificateCert` | 检查参数名拼写 | 错误使用了 `--CertificateCert`,正确参数名为 `--CertificatePublicKey` | 改用 `--CertificatePublicKey "$(cat )"` | +| `ssl UploadCertificate` 返回 `InvalidParameter` | 检查 PEM 格式:`openssl x509 -in -text -noout` 验证证书;`openssl rsa -in -check -noout` 验证私钥 | 证书公钥/私钥格式不合法或内容不匹配 | 确保证书和私钥均为 PEM 格式(以 `-----BEGIN` 开头),且属于同一证书对 | +| `CreateInstanceCustomizedDomain` 返回 `FailedOperation.DependenceError` | `tccli ssl DescribeCertificates --region ap-guangzhou` 确认证书存在且状态正常 | `CertificateId` 无效或证书未托管至腾讯云 | 确认证书 ID 正确,证书已在 SSL 证书服务中托管且已绑定该域名 | +| `CreateInstanceCustomizedDomain` 返回 `FailedOperation.CertificateNotFound` | `tccli ssl DescribeCertificates --region ap-guangzhou --output json \| jq '.Certificates[] \| .CertificateId'` 搜索目标证书 | 证书 ID 不存在或属于其他账号 | 确认 `CertificateId` 来自 `ssl UploadCertificate` 返回值或 SSL 控制台 | +| `CreateInstanceCustomizedDomain` 返回 `FailedOperation` | `tccli tcr DescribeInstances --Registryids '[""]' --region ap-guangzhou` 确认实例 Status | 实例非 `Running` 状态,或域名已被占用 | 确认实例状态正常;检查域名是否已绑定至当前实例或其它实例 | +| `DeleteInstanceCustomizedDomain` 返回错误 | `tccli tcr DescribeInstanceCustomizedDomain --RegistryId --region ap-guangzhou` 确认域名绑定状态 | 域名未绑定至当前实例 | 确认 `DomainName` 拼写正确,且该域名确实已绑定 | +| `CreateInstanceCustomizedDomain` 返回 `InvalidParameter` | 检查域名格式及备案状态 | 域名格式非法或境内实例域名未备案 | 确认域名格式正确(如 `registry.example.com`),境内实例确保已完成 ICP 备案 | + +### 创建成功但状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 自定义域名 `Status` 长时间为 `CREATING`(超过 2 分钟) | `tccli tcr DescribeInstanceCustomizedDomain --RegistryId --region ap-guangzhou` 持续轮询 | SSL 证书下发延迟 | 等待 1-2 分钟后重新查询;若持续非 `SUCCESS`,检查 SSL 证书状态是否正常 | +| 自定义域名 `Status` 为 `FAILED` | `tccli ssl DescribeCertificates --region ap-guangzhou` 确认证书详情 | 证书与域名不匹配或证书状态异常 | 确认证书 CN/SAN 包含目标域名且状态为「已签发」;重新上传或购买正确证书 | +| 自定义域名绑定成功但 HTTPS 访问返回 `certificate signed by unknown authority` | `echo \| openssl s_client -connect :443 -servername 2>/dev/null \| openssl x509 -noout -issuer` 检查证书链 | 使用了自签名证书,客户端不信任该 CA | **CLI 使用不受影响**。若需消除警告,在各客户端添加 CA 证书:`mkdir -p /etc/docker/certs.d// && cp /etc/docker/certs.d//ca.crt`;浏览器访问需手动信任该证书 | +| 自定义域名绑定成功但无法访问(502/503) | `curl -I https:///v2/` 测试连通性 | DNS 解析未配置、网络链路不通、或公网入口未开启 | 检查 DNS 解析记录;公网场景确认公网入口已开启且白名单已配置;内网场景确认 VPC 已接入 | +| DNS 解析记录配置后长时间不生效 | `nslookup ` 检查解析结果 | DNS TTL 缓存未过期 | 等待 TTL 过期(通常数分钟),或刷新本地 DNS 缓存 | + +### 真机验证全链路记录 + +以下为本次 rewrite 真实环境执行的完整链路(已验证通过,实例 `tcr-nn8smeyj`,证书 `YWwE5I4b`): + +```bash +# 1. 生成自签名证书(有效期 1 天) +openssl req -x509 -newkey rsa:2048 \ + -keyout key.pem -out cert.pem \ + -days 1 -nodes \ + -subj '/CN=test-kerwinwjyan-rewrite.example.com' +# 成功:生成 key.pem 和 cert.pem + +# 2. 上传证书至腾讯云 SSL 证书服务 +tccli ssl UploadCertificate \ + --CertificatePublicKey "$(cat cert.pem)" \ + --CertificatePrivateKey "$(cat key.pem)" \ + --CertificateType SVR \ + --Alias 'tcr-test-cert' \ + --region ap-guangzhou +# → CertificateId: "YWwE5I4b" + +# 3. 绑定自定义域名至 TCR 实例 +tccli tcr CreateInstanceCustomizedDomain \ + --RegistryId tcr-nn8smeyj \ + --DomainName test-kerwinwjyan-rewrite.example.com \ + --CertificateId YWwE5I4b \ + --region ap-guangzhou +# → RequestId: "0ecf086e-..." + +# 4. 验证绑定状态 +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId tcr-nn8smeyj \ + --region ap-guangzhou +# → DomainInfoList: [{DomainName: "test-kerwinwjyan-rewrite.example.com", CertId: "YWwE5I4b", Status: "SUCCESS"}] + +# 5. 删除自定义域名 +tccli tcr DeleteInstanceCustomizedDomain \ + --RegistryId tcr-nn8smeyj \ + --DomainName test-kerwinwjyan-rewrite.example.com \ + --region ap-guangzhou +# 成功删除 +``` + +> 自签名证书仅适用于开发/测试环境。生产环境请使用 CA 签发的正式证书。`ssl UploadCertificate` 支持的 PEM 格式包括 RSA 2048、ECC 等多种密钥类型。证书上传后可跨 TCR 实例复用(同一证书 ID 可用于多个实例)。 + +## 下一步 + +- [配置内网访问控制](../../network/private-access) — VPC 内网链路管理(`ManageInternalEndpoint`),使 VPC 内容器客户端可通过内网访问 TCR 实例 +- [配置公网访问控制](../../network/public-access) — 公网白名单管理(`CreateSecurityPolicy` / `DeleteSecurityPolicy`),使公网客户端可访问 TCR 实例 +- [配置自定义域名及云联网实现跨地域内网访问](../../../../practices/custom-domain-ccn) — 跨地域统一域名方案(CCN + Private DNS + 自定义域名) +- [管理命名空间](../../../image-creation/namespace) — 创建命名空间与镜像仓库,配合自定义域名推送/拉取镜像 +- [环境准备](../../../../quickstart/环境准备.md) — 返回 TCR 工具链入口 + +## 控制台替代 + +[容器镜像服务控制台](https://console.cloud.tencent.com/tcr) -> 左侧导航栏**域名管理** -> 选择实例地域和实例 ID -> 单击**添加自定义域名**,在弹窗中配置域名及证书信息后确定。控制台支持**更新证书**及**删除**操作。控制台上传证书需先在 [SSL 证书控制台](https://console.cloud.tencent.com/ssl) 完成证书托管。 diff --git a/src/content/docs/cli/tcr/ops/access/network/network-overview.md b/src/content/docs/cli/tcr/ops/access/network/network-overview.md new file mode 100644 index 0000000..fa07d66 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/network/network-overview.md @@ -0,0 +1,150 @@ +--- +title: "访问网络控制概述(tccli)" +description: "· page_id `41836`" +--- + +> 对照官方:[访问控制](https://cloud.tencent.com/document/product/1141/41836) · page_id `41836` + +## 概述 + +腾讯云容器镜像服务(TCR)企业版提供多层级的访问网络控制能力,从网络入口层面保障实例及容器镜像的数据安全。 + +**默认安全姿态**:新创建的 TCR 企业版实例**默认不开启公网访问入口**,也无法从私有网络(VPC)直接内网访问。这是一种默认拒绝(deny-by-default)的安全设计——需由管理员主动、按需配置公网或内网访问控制策略,遵循最小范围放通原则,仅允许必要的业务客户端访问实例。 + +访问网络控制分为两大模块: + +| 模块 | 控制粒度 | 适用场景 | CLI 核心命令 | +|------|---------|---------|-------------| +| **公网访问控制** | IP/CIDR 白名单策略 | 本地开发测试、非腾讯云环境、跨地域公网访问 | `ManageExternalEndpoint` / `CreateSecurityPolicy` | +| **内网访问控制** | VPC + 子网 + 私有域解析 PrivateDNS | 同地域 VPC 内云服务器访问,免公网带宽成本、拉取速度更快 | `ManageInternalEndpoint` / `CreateInternalEndpointDns` | + +两种访问模式可独立配置、并行使用——同一实例可同时开放公网访问(配合白名单限制来源 IP)和内网访问(接入 VPC),客户端根据自身网络环境选择合适的接入方式。 + +--- + +### 公网访问控制 + +公网访问控制由两层构成: + +1. **公网访问入口**(开关):控制实例是否对公网暴露。调用 `ManageExternalEndpoint` 开启/关闭,操作为异步,须通过 `DescribeExternalEndpointStatus` 轮询确认终态(`Opened` / `Closed`)。处于过渡状态 `Opening` 时不可执行关闭操作。 + +2. **白名单策略**(精细化控制):入口开启后,通过安全策略决定**哪些来源 IP 或 IP 段**可以访问实例。白名单策略由 `CreateSecurityPolicy` 创建,每条策略包含 `CidrBlock`(来源 IP 或 CIDR 地址段)和 `Description`(备注)。策略创建、修改、删除均要求公网入口状态为 `Opened`;入口关闭时调用策略查询/操作将返回错误。 + +**典型工作流**: + +``` +CreateInstance(默认入口关闭) + → ManageExternalEndpoint Open(开启入口,异步) + → 轮询 DescribeExternalEndpointStatus 至 Opened + → CreateSecurityPolicy(添加白名单,放通来源 IP) + → docker login (从白名单内 IP 发起访问) +``` + +--- + +### 内网访问控制 + +内网访问控制通过在 VPC 内建立专用访问链路实现: + +1. **内网访问链路**:调用 `ManageInternalEndpoint` 在指定 VPC 和子网中创建/删除内网访问链路,返回该链路的私有 IP 地址(`AccessIp`)。 + +2. **内网域名解析**:内网链路建立后,可选开启自动内网解析(`CreateInternalEndpointDns`),利用[私有域解析 PrivateDNS](https://console.cloud.tencent.com/privatedns) 将实例域名在 VPC 内解析为内网 IP。开启后,VPC 内云服务器访问 `.tencentcloudcr.com` 将自动解析到内网链路 IP,无需修改 hosts 文件或额外配置 DNS。 + +**内网访问特点**: +- 依赖跨产品资源:需预先准备 `VpcId` 和 `SubnetId` +- 仅同地域 VPC 生效(内网访问链路基于地域内网络) +- 免公网带宽成本,镜像拉取速度显著优于公网访问 +- 配合 [TKE 集群使用 TCR 插件内网免密拉取](../../../../practices/tke-plugin-pull) 实现 Kubernetes 集群内网免密镜像拉取 + +## 前置条件 + +- [环境准备](../../index.md):`tccli` 版本 >= 3.1.x,地域与凭据已配置 +- 已成功 [购买企业版实例](../../../create)(`--RegistryType basic`/`standard`/`premium`),实例状态为 `Running` +- **公网访问控制**:无需额外前提 +- **内网访问控制**:需预先准备 VPC 和子网资源(`VpcId`、`SubnetId`),以及开启 [私有域解析 PrivateDNS](https://console.cloud.tencent.com/privatedns) 服务 +- 如使用子账号操作,需为其授予对应实例的 `QcloudTCRFullAccess` 或相应的资源级权限 + +## 控制台与 CLI 参数映射 + +本文为概念概述页。以下列出公网和内网访问控制的 tccli 命令入口,具体参数和操作步骤见对应子页面。 + +### 公网访问控制 — CLI 命令入口 + +| 控制台操作 | CLI 命令 | 页面 | +|-----------|----------|------| +| 开启/关闭公网访问入口 | `ManageExternalEndpoint` | [配置公网访问控制](../public-access) | +| 查看公网访问入口状态 | `DescribeExternalEndpointStatus` | 同上 | +| 添加单条白名单策略 | `CreateSecurityPolicy` | 同上 | +| 批量添加白名单策略 | `CreateMultipleSecurityPolicy` | 同上 | +| 查看白名单策略列表 | `DescribeSecurityPolicies` | 同上 | +| 修改白名单策略 | `ModifySecurityPolicy` | 同上 | +| 删除白名单策略 | `DeleteSecurityPolicy` | 同上 | + +### 内网访问控制 — CLI 命令入口 + +| 控制台操作 | CLI 命令 | 页面 | +|-----------|----------|------| +| 新建/删除内网访问链路 | `ManageInternalEndpoint` | [配置内网访问控制](../private-access) | +| 查看内网访问链路列表 | `DescribeInternalEndpoints` | 同上 | +| 查看 DNS 解析状态 | `DescribeInternalEndpointDnsStatus` | 同上 | +| 开启自动内网解析 | `CreateInternalEndpointDns` | 同上 | +| 关闭自动内网解析 | `DeleteInternalEndpointDns` | 同上 | + +### 关键 API 行为约束 + +| CLI 命令 | 关键行为 | 易错点 | +|----------|---------|--------| +| `ManageExternalEndpoint` | 异步操作,`Open` → 轮询至 `Opened` 后方可创建安全策略 / `Close` → 轮询至 `Closed` 后方可再次 `Open` | `Open` 后立即 `Close` 将失败(状态尚在 `Opening` 过渡中) | +| `CreateSecurityPolicy` | 要求公网入口状态为 `Opened`;否则返回 `OperationDenied` | 入口未开启时无法创建策略 | +| `DescribeSecurityPolicies` | 公网入口未开启时调用返回 `ResourceNotFound` | 入口关闭时无法查询已有策略 | +| `ManageInternalEndpoint` | 依赖跨产品 VPC/Subnet 资源 | 需预先从 [VPC 控制台](https://console.cloud.tencent.com/vpc) 获取 `VpcId` 和 `SubnetId` | +| `CreateInternalEndpointDns` | 要求内网访问链路已建立且 `AccessIp` 不为空;依赖私有域解析 PrivateDNS 服务 | 需预先开通 PrivateDNS 服务 | + +## 操作步骤 + +本文为概念概述页,无可独立执行的 tccli 命令操作。具体操作步骤请进入以下子页面: + +- [配置公网访问控制](../public-access) — 开启/关闭公网入口、管理白名单策略(page_id `41837`) +- [配置内网访问控制](../private-access) — 接入私有网络、管理内网私有域解析(page_id `41838`) + +## 验证 + +### 概念关系验证(交叉引用) + +| 验证关系 | 验证方式 | 期望结论 | +|----------|---------|---------| +| 默认入口关闭 → 拒绝公网访问 | 新创建实例后,从公网环境执行 `docker login ` | 连接超时或拒绝连接 | +| 公网入口开启后白名单策略空 → 拒绝全部 | `ManageExternalEndpoint Open` 后等至 `Opened`,从任意公网 IP 执行 `docker login` | 被安全策略拦截(白名单为空时默认拒绝全部来源) | +| 公网入口开启 + 白名单放通 → 允许访问 | 添加白名单包含当前公网 IP,执行 `docker login` | 登录成功 | +| 内网链路 + 私有域解析 → VPC 内自动解析 | VPC 内云服务器 `ping .tencentcloudcr.com` | 解析到内网 `AccessIp`(非公网 IP) | +| 公网入口 `Open` 后立即 `Close` → 被拒绝 | `ManageExternalEndpoint Open` 后不等待立即 `Close` | 返回错误:`current public network access status is Opening` | + +## 清理 + +不适用。本文为概念概述页,不涉及资源创建与清理。 + +## 排障 + +| 现象 | 诊断命令/步骤 | 根因 | 修复 | +|------|-------------|------|------| +| 实例创建后无法 `docker push`/`docker pull` | `tccli tcr DescribeExternalEndpointStatus --RegistryId --region ` | 默认公网入口关闭 | 先开启公网入口(`ManageExternalEndpoint Open`),或配置内网访问链路 | +| 公网入口已 `Opened`,但仍无法访问 | `tccli tcr DescribeSecurityPolicies --RegistryId --region ` | 白名单为空时默认拒绝全部来源 | 添加包含当前 IP 的白名单策略 | +| `ManageExternalEndpoint Close` 失败,报 `current public network access status is Opening` | `tccli tcr DescribeExternalEndpointStatus --RegistryId --region ` | `Open` 后立即 `Close`,状态尚在 `Opening` 过渡中 | 等待 `DescribeExternalEndpointStatus` 返回 `Opened` 后,再执行 `Close` | +| `DescribeSecurityPolicies` 返回 `ResourceNotFound` | `tccli tcr DescribeExternalEndpointStatus --RegistryId --region ` | 公网入口未开启时无法查询策略列表 | 先开启公网入口 | +| VPC 内云服务器无法通过内网访问实例 | `tccli tcr DescribeInternalEndpoints --RegistryId --region ` | 未创建内网访问链路 | 调用 `ManageInternalEndpoint` 新建链路,传入正确的 `VpcId` 和 `SubnetId` | +| 内网链路已建立,但域名未自动解析到内网 IP | `tccli tcr DescribeInternalEndpointDnsStatus --RegistryId --VpcSet ` | 未开启自动内网解析 | 调用 `CreateInternalEndpointDns` 开启自动解析 | +| 不知道用公网还是内网 | — | — | **公网**:本地开发测试、非腾讯云环境、跨地域访问。**内网**:同地域 VPC 内云服务器,免公网带宽成本、拉取更快 | +| `ManageInternalEndpoint` 创建失败 | 确认 VPC 和子网 ID 正确,且子网可用 IP 数充足 | VPC/子网不存在或 IP 资源不足 | 在 [VPC 控制台](https://console.cloud.tencent.com/vpc) 确认资源状态,选择可用 IP 充足的子网 | + +## 下一步 + +- [配置公网访问控制](../public-access)(page_id `41837`)— 开启/关闭公网入口、管理白名单策略 +- [配置内网访问控制](../private-access)(page_id `41838`)— 接入私有网络、管理内网私有域解析 +- [访问权限管理概述](../../permissions/permission-overview)(page_id `40718`)— 理解用户级/服务级账号权限管理 +- [TKE 集群使用 TCR 插件内网免密拉取容器镜像](../../../../practices/tke-plugin-pull) — 实战:K8s 集群内网免密拉取 + +## 控制台替代 + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 选择目标企业版实例 → 进入 **访问控制** 页面: +- **公网访问** 标签页:开启/关闭公网访问入口,管理白名单策略(添加/编辑/删除 IP 或 CIDR 地址段) +- **内网访问** 标签页:管理内网访问链路(新建/删除 VPC 接入),开启/关闭自动内网解析 diff --git a/src/content/docs/cli/tcr/ops/access/network/network-overview/network-overview.md b/src/content/docs/cli/tcr/ops/access/network/network-overview/network-overview.md new file mode 100644 index 0000000..fa07d66 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/network/network-overview/network-overview.md @@ -0,0 +1,150 @@ +--- +title: "访问网络控制概述(tccli)" +description: "· page_id `41836`" +--- + +> 对照官方:[访问控制](https://cloud.tencent.com/document/product/1141/41836) · page_id `41836` + +## 概述 + +腾讯云容器镜像服务(TCR)企业版提供多层级的访问网络控制能力,从网络入口层面保障实例及容器镜像的数据安全。 + +**默认安全姿态**:新创建的 TCR 企业版实例**默认不开启公网访问入口**,也无法从私有网络(VPC)直接内网访问。这是一种默认拒绝(deny-by-default)的安全设计——需由管理员主动、按需配置公网或内网访问控制策略,遵循最小范围放通原则,仅允许必要的业务客户端访问实例。 + +访问网络控制分为两大模块: + +| 模块 | 控制粒度 | 适用场景 | CLI 核心命令 | +|------|---------|---------|-------------| +| **公网访问控制** | IP/CIDR 白名单策略 | 本地开发测试、非腾讯云环境、跨地域公网访问 | `ManageExternalEndpoint` / `CreateSecurityPolicy` | +| **内网访问控制** | VPC + 子网 + 私有域解析 PrivateDNS | 同地域 VPC 内云服务器访问,免公网带宽成本、拉取速度更快 | `ManageInternalEndpoint` / `CreateInternalEndpointDns` | + +两种访问模式可独立配置、并行使用——同一实例可同时开放公网访问(配合白名单限制来源 IP)和内网访问(接入 VPC),客户端根据自身网络环境选择合适的接入方式。 + +--- + +### 公网访问控制 + +公网访问控制由两层构成: + +1. **公网访问入口**(开关):控制实例是否对公网暴露。调用 `ManageExternalEndpoint` 开启/关闭,操作为异步,须通过 `DescribeExternalEndpointStatus` 轮询确认终态(`Opened` / `Closed`)。处于过渡状态 `Opening` 时不可执行关闭操作。 + +2. **白名单策略**(精细化控制):入口开启后,通过安全策略决定**哪些来源 IP 或 IP 段**可以访问实例。白名单策略由 `CreateSecurityPolicy` 创建,每条策略包含 `CidrBlock`(来源 IP 或 CIDR 地址段)和 `Description`(备注)。策略创建、修改、删除均要求公网入口状态为 `Opened`;入口关闭时调用策略查询/操作将返回错误。 + +**典型工作流**: + +``` +CreateInstance(默认入口关闭) + → ManageExternalEndpoint Open(开启入口,异步) + → 轮询 DescribeExternalEndpointStatus 至 Opened + → CreateSecurityPolicy(添加白名单,放通来源 IP) + → docker login (从白名单内 IP 发起访问) +``` + +--- + +### 内网访问控制 + +内网访问控制通过在 VPC 内建立专用访问链路实现: + +1. **内网访问链路**:调用 `ManageInternalEndpoint` 在指定 VPC 和子网中创建/删除内网访问链路,返回该链路的私有 IP 地址(`AccessIp`)。 + +2. **内网域名解析**:内网链路建立后,可选开启自动内网解析(`CreateInternalEndpointDns`),利用[私有域解析 PrivateDNS](https://console.cloud.tencent.com/privatedns) 将实例域名在 VPC 内解析为内网 IP。开启后,VPC 内云服务器访问 `.tencentcloudcr.com` 将自动解析到内网链路 IP,无需修改 hosts 文件或额外配置 DNS。 + +**内网访问特点**: +- 依赖跨产品资源:需预先准备 `VpcId` 和 `SubnetId` +- 仅同地域 VPC 生效(内网访问链路基于地域内网络) +- 免公网带宽成本,镜像拉取速度显著优于公网访问 +- 配合 [TKE 集群使用 TCR 插件内网免密拉取](../../../../practices/tke-plugin-pull) 实现 Kubernetes 集群内网免密镜像拉取 + +## 前置条件 + +- [环境准备](../../index.md):`tccli` 版本 >= 3.1.x,地域与凭据已配置 +- 已成功 [购买企业版实例](../../../create)(`--RegistryType basic`/`standard`/`premium`),实例状态为 `Running` +- **公网访问控制**:无需额外前提 +- **内网访问控制**:需预先准备 VPC 和子网资源(`VpcId`、`SubnetId`),以及开启 [私有域解析 PrivateDNS](https://console.cloud.tencent.com/privatedns) 服务 +- 如使用子账号操作,需为其授予对应实例的 `QcloudTCRFullAccess` 或相应的资源级权限 + +## 控制台与 CLI 参数映射 + +本文为概念概述页。以下列出公网和内网访问控制的 tccli 命令入口,具体参数和操作步骤见对应子页面。 + +### 公网访问控制 — CLI 命令入口 + +| 控制台操作 | CLI 命令 | 页面 | +|-----------|----------|------| +| 开启/关闭公网访问入口 | `ManageExternalEndpoint` | [配置公网访问控制](../public-access) | +| 查看公网访问入口状态 | `DescribeExternalEndpointStatus` | 同上 | +| 添加单条白名单策略 | `CreateSecurityPolicy` | 同上 | +| 批量添加白名单策略 | `CreateMultipleSecurityPolicy` | 同上 | +| 查看白名单策略列表 | `DescribeSecurityPolicies` | 同上 | +| 修改白名单策略 | `ModifySecurityPolicy` | 同上 | +| 删除白名单策略 | `DeleteSecurityPolicy` | 同上 | + +### 内网访问控制 — CLI 命令入口 + +| 控制台操作 | CLI 命令 | 页面 | +|-----------|----------|------| +| 新建/删除内网访问链路 | `ManageInternalEndpoint` | [配置内网访问控制](../private-access) | +| 查看内网访问链路列表 | `DescribeInternalEndpoints` | 同上 | +| 查看 DNS 解析状态 | `DescribeInternalEndpointDnsStatus` | 同上 | +| 开启自动内网解析 | `CreateInternalEndpointDns` | 同上 | +| 关闭自动内网解析 | `DeleteInternalEndpointDns` | 同上 | + +### 关键 API 行为约束 + +| CLI 命令 | 关键行为 | 易错点 | +|----------|---------|--------| +| `ManageExternalEndpoint` | 异步操作,`Open` → 轮询至 `Opened` 后方可创建安全策略 / `Close` → 轮询至 `Closed` 后方可再次 `Open` | `Open` 后立即 `Close` 将失败(状态尚在 `Opening` 过渡中) | +| `CreateSecurityPolicy` | 要求公网入口状态为 `Opened`;否则返回 `OperationDenied` | 入口未开启时无法创建策略 | +| `DescribeSecurityPolicies` | 公网入口未开启时调用返回 `ResourceNotFound` | 入口关闭时无法查询已有策略 | +| `ManageInternalEndpoint` | 依赖跨产品 VPC/Subnet 资源 | 需预先从 [VPC 控制台](https://console.cloud.tencent.com/vpc) 获取 `VpcId` 和 `SubnetId` | +| `CreateInternalEndpointDns` | 要求内网访问链路已建立且 `AccessIp` 不为空;依赖私有域解析 PrivateDNS 服务 | 需预先开通 PrivateDNS 服务 | + +## 操作步骤 + +本文为概念概述页,无可独立执行的 tccli 命令操作。具体操作步骤请进入以下子页面: + +- [配置公网访问控制](../public-access) — 开启/关闭公网入口、管理白名单策略(page_id `41837`) +- [配置内网访问控制](../private-access) — 接入私有网络、管理内网私有域解析(page_id `41838`) + +## 验证 + +### 概念关系验证(交叉引用) + +| 验证关系 | 验证方式 | 期望结论 | +|----------|---------|---------| +| 默认入口关闭 → 拒绝公网访问 | 新创建实例后,从公网环境执行 `docker login ` | 连接超时或拒绝连接 | +| 公网入口开启后白名单策略空 → 拒绝全部 | `ManageExternalEndpoint Open` 后等至 `Opened`,从任意公网 IP 执行 `docker login` | 被安全策略拦截(白名单为空时默认拒绝全部来源) | +| 公网入口开启 + 白名单放通 → 允许访问 | 添加白名单包含当前公网 IP,执行 `docker login` | 登录成功 | +| 内网链路 + 私有域解析 → VPC 内自动解析 | VPC 内云服务器 `ping .tencentcloudcr.com` | 解析到内网 `AccessIp`(非公网 IP) | +| 公网入口 `Open` 后立即 `Close` → 被拒绝 | `ManageExternalEndpoint Open` 后不等待立即 `Close` | 返回错误:`current public network access status is Opening` | + +## 清理 + +不适用。本文为概念概述页,不涉及资源创建与清理。 + +## 排障 + +| 现象 | 诊断命令/步骤 | 根因 | 修复 | +|------|-------------|------|------| +| 实例创建后无法 `docker push`/`docker pull` | `tccli tcr DescribeExternalEndpointStatus --RegistryId --region ` | 默认公网入口关闭 | 先开启公网入口(`ManageExternalEndpoint Open`),或配置内网访问链路 | +| 公网入口已 `Opened`,但仍无法访问 | `tccli tcr DescribeSecurityPolicies --RegistryId --region ` | 白名单为空时默认拒绝全部来源 | 添加包含当前 IP 的白名单策略 | +| `ManageExternalEndpoint Close` 失败,报 `current public network access status is Opening` | `tccli tcr DescribeExternalEndpointStatus --RegistryId --region ` | `Open` 后立即 `Close`,状态尚在 `Opening` 过渡中 | 等待 `DescribeExternalEndpointStatus` 返回 `Opened` 后,再执行 `Close` | +| `DescribeSecurityPolicies` 返回 `ResourceNotFound` | `tccli tcr DescribeExternalEndpointStatus --RegistryId --region ` | 公网入口未开启时无法查询策略列表 | 先开启公网入口 | +| VPC 内云服务器无法通过内网访问实例 | `tccli tcr DescribeInternalEndpoints --RegistryId --region ` | 未创建内网访问链路 | 调用 `ManageInternalEndpoint` 新建链路,传入正确的 `VpcId` 和 `SubnetId` | +| 内网链路已建立,但域名未自动解析到内网 IP | `tccli tcr DescribeInternalEndpointDnsStatus --RegistryId --VpcSet ` | 未开启自动内网解析 | 调用 `CreateInternalEndpointDns` 开启自动解析 | +| 不知道用公网还是内网 | — | — | **公网**:本地开发测试、非腾讯云环境、跨地域访问。**内网**:同地域 VPC 内云服务器,免公网带宽成本、拉取更快 | +| `ManageInternalEndpoint` 创建失败 | 确认 VPC 和子网 ID 正确,且子网可用 IP 数充足 | VPC/子网不存在或 IP 资源不足 | 在 [VPC 控制台](https://console.cloud.tencent.com/vpc) 确认资源状态,选择可用 IP 充足的子网 | + +## 下一步 + +- [配置公网访问控制](../public-access)(page_id `41837`)— 开启/关闭公网入口、管理白名单策略 +- [配置内网访问控制](../private-access)(page_id `41838`)— 接入私有网络、管理内网私有域解析 +- [访问权限管理概述](../../permissions/permission-overview)(page_id `40718`)— 理解用户级/服务级账号权限管理 +- [TKE 集群使用 TCR 插件内网免密拉取容器镜像](../../../../practices/tke-plugin-pull) — 实战:K8s 集群内网免密拉取 + +## 控制台替代 + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 选择目标企业版实例 → 进入 **访问控制** 页面: +- **公网访问** 标签页:开启/关闭公网访问入口,管理白名单策略(添加/编辑/删除 IP 或 CIDR 地址段) +- **内网访问** 标签页:管理内网访问链路(新建/删除 VPC 接入),开启/关闭自动内网解析 diff --git a/src/content/docs/cli/tcr/ops/access/network/private-access-tcrctl.md b/src/content/docs/cli/tcr/ops/access/network/private-access-tcrctl.md new file mode 100644 index 0000000..d1e7cab --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/network/private-access-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "配置内网访问控制(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[配置内网访问控制](https://cloud.tencent.com/document/product/1141/41838) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/access/network/private-access.md b/src/content/docs/cli/tcr/ops/access/network/private-access.md new file mode 100644 index 0000000..dcdc715 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/network/private-access.md @@ -0,0 +1,346 @@ +--- +title: "配置内网访问控制" +description: "· page_id `41838`" +--- + +> 对照官方:[配置内网访问控制](https://cloud.tencent.com/document/product/1141/41838) · page_id `41838` + +## 概述 + +腾讯云容器镜像服务(TCR)企业版支持内网访问控制,通过将私有网络(VPC)接入至企业版实例,实现 VPC 内客户端通过内网链路访问实例。内网访问能够提升镜像拉取速度、避免公网带宽成本,并基于私有网络边界实现访问隔离。 + +一个 TCR 企业版实例可绑定**多个**私有网络。完成内网访问链路接入后,需配置内网域名解析(私有域解析 PrivateDNS 自动配置,或自建 DNS / 手动 hosts),使 VPC 内云服务器可通过实例域名解析到内网 IP。 + +**跨产品依赖**:本页操作依赖 [私有网络 VPC](https://cloud.tencent.com/document/product/215) 和 [私有域解析 PrivateDNS](https://cloud.tencent.com/document/product/1338)。需先在实例所在地域创建 VPC 和子网,再执行本页命令。 + +## 前置条件 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0 + +# 2. 检查 CAM 权限(TCR 读写) +tccli tcr DescribeInstances --region ap-guangzhou +# expected: exit 0,返回实例列表(可为空) + +# 3. 检查 CAM 权限(VPC 读写) +tccli vpc DescribeVpcs --region ap-guangzhou +# expected: exit 0,返回 VPC 列表(可为空) +``` + +### 资源检查 + +```bash +# 4. 确认 TCR 实例存在且状态正常 +tccli tcr DescribeInstances --Registryids '[""]' --region +# expected: exit 0, Status: "Running", RegistryType: "standard" 或以上 + +# 5. 确认 VPC 存在(目标实例所在地域) +tccli vpc DescribeVpcs --VpcIds '[""]' --region +# expected: exit 0,VpcSet 中包含目标 VPC + +# 6. 确认子网存在且可用 IP 数量 >= 1 +tccli vpc DescribeSubnets --SubnetIds '[""]' --region +# expected: exit 0, AvailableIpAddressCount >= 1 +``` + +## 控制台与 CLI 参数映射 + +### 操作索引 + +| 控制台操作 | CLI | 幂等 | +|-----------|-----|:--:| +| 新建内网访问链路(接入私有网络) | `tccli tcr ManageInternalEndpoint --Operation Create` | 否(同一 VPC 重复创建报错) | +| 查看内网访问链路列表 | `tccli tcr DescribeInternalEndpoints` | 是 | +| 开启自动内网解析 | `tccli tcr CreateInternalEndpointDns` | 否(同一 VPC+EniLBIp 组合重复创建报错) | +| 查看内网解析状态 | `tccli tcr DescribeInternalEndpointDnsStatus` | 是 | +| 关闭自动内网解析 | `tccli tcr DeleteInternalEndpointDns` | 是 | +| 删除内网访问链路 | `tccli tcr ManageInternalEndpoint --Operation Delete` | 否 | + +### 关键字段说明(`ManageInternalEndpoint`) + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|------|-----------|---------| +| RegistryId | String | 是 | 目标实例 ID,由 `DescribeInstances` 返回 | 实例不存在返回 `FailedOperation` | +| Operation | String | 是 | `Create`(新建)/ `Delete`(删除) | 无效值返回 `InvalidParameter` | +| VpcId | String | 是 | 私有网络 ID,**须与实例同地域**,须提前通过 `tccli vpc CreateVpc` 创建 | VPC 不存在或跨地域返回 `FailedOperation` | +| SubnetId | String | 是 | 子网 ID,**须属于目标 VPC**,须提前通过 `tccli vpc CreateSubnet` 创建,`AvailableIpAddressCount` >= 1 | 子网不存在或 IP 枯竭返回 `FailedOperation` | + +### 关键字段说明(`CreateInternalEndpointDns`) + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|------|-----------|---------| +| InstanceId | String | 是 | TCR 实例 ID,**注意参数名为 `--InstanceId` 而非常见的 `--RegistryId`** | 使用 `--RegistryId` 返回 `MissingParameter` | +| VpcId | String | 是 | 已接入实例的 VPC ID | VPC 与已有内网链路不匹配返回 `FailedOperation` | +| EniLBIp | String | 是 | 内网解析 IP(即 `DescribeInternalEndpoints` 返回的 `AccessIp`),**须在目标子网 CIDR 范围内** | IP 不在子网 CIDR 内返回 `FailedOperation` | + +> **陷阱汇总:** +> +> | # | 陷阱 | 说明 | +> |---|------|------| +> | 1 | VPC 和 Subnet 是前提 | `ManageInternalEndpoint` 要求 VPC 和子网已存在。需先通过 `tccli vpc CreateVpc` 和 `tccli vpc CreateSubnet` 创建后再执行本页命令 | +> | 2 | EniLBIp 须在 Subnet CIDR 内 | `CreateInternalEndpointDns` 的 `--EniLBIp` 必须是子网 CIDR 内的合法 IP,通常使用 `DescribeInternalEndpoints` 返回的 `AccessIp` | +> | 3 | `--InstanceId` 非 `--RegistryId` | `CreateInternalEndpointDns` 和 `DeleteInternalEndpointDns` 的参数名为 `--InstanceId`,与其他 TCR API 的 `--RegistryId` 不同。传错参数名会触发 `MissingParameter` 错误 | + +## 操作步骤 + +### 1. 新建内网访问链路 + +将 VPC 接入 TCR 实例,在指定子网内分配一个内网 IP(`AccessIp`)。 + +一个实例可绑定多个 VPC。如需接入多个 VPC,重复执行本命令,每次指定不同的 `--VpcId` / `--SubnetId` 组合。 + +#### 选择依据 + +- **VpcId**:选择实例所在地域的 VPC。实例与 VPC 跨地域将导致 `FailedOperation`。 +- **SubnetId**:选择目标 VPC 下有可用 IP 的子网(`AvailableIpAddressCount` >= 1)。 + +#### 执行创建 + +```bash +tccli tcr ManageInternalEndpoint \ + --RegistryId \ + --Operation Create \ + --VpcId \ + --SubnetId \ + --region +``` + +参考输出: + +```json +{ + "RequestId": "b063c21b-0000-0000-0000-000000000000" +} +``` + +#### 轮询确认链路就绪 + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId \ + --region +``` + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 链路状态 | `AccessVpcSet[].Status` | `Running` | +| 内网 IP | `AccessVpcSet[].AccessIp` | 非空,子网 CIDR 内 IP | + +参考输出: + +```json +{ + "AccessVpcSet": [ + { + "VpcId": "", + "SubnetId": "", + "Status": "Running", + "AccessIp": "10.0.0.5" + } + ], + "TotalCount": 1, + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +> **注意**:新建操作是**异步**的,通常 1-2 分钟完成。轮询到 `Status: "Running"` 且 `AccessIp` 不为空后即可进行后续 DNS 配置。 + +### 2. 配置内网域名解析 + +内网访问链路就绪后,需配置内网域名解析。推荐使用私有域解析 PrivateDNS 自动配置。以下为 CLI 完整操作流程。 + +#### 2.1 创建内网 DNS 解析 + +##### 选择依据 + +- **InstanceId**:即 TCR 实例 ID(`RegistryId`),注意参数名是 `--InstanceId` 而非 `--RegistryId`(陷阱 #3)。 +- **EniLBIp**:使用 `DescribeInternalEndpoints` 返回的 `AccessIp`。该 IP 须在目标子网 CIDR 范围内(陷阱 #2),否则返回 `FailedOperation`。 +- **UsePublicDomain**:`true` 解析默认公网域名,`false` 解析 VPC 专用域名(`<实例名>-vpc.tencentcloudcr.com`)。推荐 `true`,与现有 docker login / pull 命令兼容。 + +##### 执行创建 + +```bash +tccli tcr CreateInternalEndpointDns \ + --InstanceId \ + --VpcId \ + --EniLBIp \ + --UsePublicDomain true \ + --region +``` + +参考输出: + +```json +{ + "RequestId": "72d88a72-0000-0000-0000-000000000000" +} +``` + +#### 2.2 查询内网 DNS 解析状态 + +```bash +tccli tcr DescribeInternalEndpointDnsStatus \ + --InstanceId \ + --region +``` + +参考输出: + +```json +{ + "VpcSet": [ + { + "VpcId": "", + "EniLBIp": "", + "Status": "ENABLED" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 解析状态 | `VpcSet[].Status` | `ENABLED` | + +`Status: "ENABLED"` 表示内网 DNS 解析已生效。DNS 解析完全生效通常有 1-2 分钟传播延迟。 + +#### 2.3 删除内网 DNS 解析(清理用) + +```bash +tccli tcr DeleteInternalEndpointDns \ + --InstanceId \ + --VpcId \ + --EniLBIp \ + --region +``` + +参数名注意:此处同样是 `--InstanceId` 而非 `--RegistryId`(陷阱 #3)。 + +### 3. 删除内网访问链路 + +删除链路前,建议先执行 `DeleteInternalEndpointDns` 关闭对应 DNS 解析。 + +```bash +tccli tcr ManageInternalEndpoint \ + --RegistryId \ + --Operation Delete \ + --VpcId \ + --SubnetId \ + --region +``` + +参考输出: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +### 4. 备选方案(不推荐) + +本节提供两种不需要 PrivateDNS 的备选方案,适用于 PrivateDNS 服务不可用的场景。 + +#### 4.1 TCR 插件自动配置(TKE 集群) + +在 TKE 集群中安装 TCR 插件,勾选"启用内网解析功能",插件自动为集群内节点配置内网解析。参见 [TCR 说明](https://cloud.tencent.com/document/product/457/49225)。 + +#### 4.2 手动配置云服务器 hosts + +以 Linux 云服务器为例,登录后执行: + +```bash +echo ' .tencentcloudcr.com' >> /etc/hosts +``` + +## 验证 + +### Control plane(tccli) + +```bash +# 1. 确认内网链路状态为 Running,AccessIp 不为空 +tccli tcr DescribeInternalEndpoints \ + --RegistryId \ + --region + +# 2. 确认 DNS 解析状态为 ENABLED +tccli tcr DescribeInternalEndpointDnsStatus \ + --InstanceId \ + --region +``` + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 链路状态 | `AccessVpcSet[].Status` | `Running` | +| 内网 IP | `AccessVpcSet[].AccessIp` | 非空 | +| DNS 解析 | `VpcSet[].Status` | `ENABLED` | + +### Data plane(VPC 内测试,可选) + +在已接入 VPC 内的云服务器上执行: + +```bash +# 测试内网连通性 +curl -k https:///v2/ + +# 测试域名解析 +nslookup .tencentcloudcr.com +``` + +## 清理 + +> **清理顺序**:先关闭 DNS 解析,再删除内网访问链路。若先删链路再关 DNS,`DeleteInternalEndpointDns` 的 `--EniLBIp` 将找不到对应 IP。 + +```bash +# 1. 关闭自动内网解析 +tccli tcr DeleteInternalEndpointDns \ + --InstanceId \ + --VpcId \ + --EniLBIp \ + --region + +# 2. 删除内网访问链路 +tccli tcr ManageInternalEndpoint \ + --RegistryId \ + --Operation Delete \ + --VpcId \ + --SubnetId \ + --region + +# 3. 确认链路已清理 +tccli tcr DescribeInternalEndpoints \ + --RegistryId \ + --region +# expected: AccessVpcSet 为 null 或 TotalCount 为 0 +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ManageInternalEndpoint Create` 返回 `FailedOperation` | `tccli vpc DescribeVpcs --VpcIds '[""]' --region ` 确认 VPC 存在;`tccli vpc DescribeSubnets --SubnetIds '[""]' --region ` 确认子网有可用 IP | VPC/子网不存在、跨地域、或子网 IP 已耗尽。**这是陷阱 #1**:VPC 和 Subnet 需提前创建 | 确认 VPC/Subnet 与实例同地域;确认子网 `AvailableIpAddressCount` >= 1;必要时 `tccli vpc CreateVpc` 和 `tccli vpc CreateSubnet` | +| `CreateInternalEndpointDns` 返回 `MissingParameter` | 检查命令参数名 | 参数名为 `--InstanceId` 而非 `--RegistryId`。**这是陷阱 #3** | 将 `--RegistryId` 改为 `--InstanceId` | +| `CreateInternalEndpointDns` 返回 `FailedOperation` | 验证 `--EniLBIp` 是否在子网 CIDR 内:`tccli vpc DescribeSubnets --SubnetIds '[""]' --region ` 查看 `CidrBlock` | `--EniLBIp` 不在子网 CIDR 范围内。**这是陷阱 #2** | 使用 `DescribeInternalEndpoints` 返回的 `AccessIp` 作为 `--EniLBIp` | +| `DescribeInternalEndpointDnsStatus` 返回 `Status: "DISABLED"` | — | DNS 解析未开启,或之前已被 `DeleteInternalEndpointDns` 关闭 | 执行 `CreateInternalEndpointDns` 开启解析 | +| `DescribeInternalEndpoints` 返回 `AccessVpcSet: null, TotalCount: 0` | — | 实例尚未绑定任何 VPC,正常初始状态 | 执行 `ManageInternalEndpoint --Operation Create` 新建内网链路 | +| `ManageInternalEndpoint` 返回 `LimitExceeded` | `tccli vpc DescribeVpcs --region ` 统计现有 VPC 数量 | VPC 数量达账号配额上限。**此为环境限制,非命令错误** | 复用已有 VPC;或删除不再使用的 VPC 释放配额;或联系腾讯云提升配额 | +| DNS 已启用(`Status: "ENABLED"`)但 VPC 内仍无法解析 | `nslookup .tencentcloudcr.com` 在 VPC 内测试 | DNS 传播延迟(通常 1-2 分钟),或 PrivateDNS 未创建对应 A 记录 | 等待 2 分钟后重试;若持续失败,检查 PrivateDNS 控制台 A 记录 | +| 手动 `echo` 到 `/etc/hosts` 后仍不连通 | `curl -k https:///v2/` 测试内网连通性 | VPC 安全组未放通 443/tcp | `tccli vpc CreateSecurityGroupPolicies` 添加入站规则放通目标端口 | + +## 下一步 + +- [配置公网访问控制](../public-access) — 公网访问白名单管理 +- [配置自定义域名](../../domain/custom-domain) — 自定义域名与内网链路的结合使用 +- [TKE 集群使用 TCR 插件内网免密拉取容器镜像](https://cloud.tencent.com/document/product/1141/48184) +- [配置实例复制](../../../image-distribution/cross-region-replication) — 跨地域接入私有网络 + +## 控制台替代 + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 选择目标实例 → **访问控制** > **内网访问** → 单击**接入私有网络** → 选择目标 VPC 和子网 → 等待链路状态变为**链路正常** → 单击**管理自动解析** → 开启域名解析。 diff --git a/src/content/docs/cli/tcr/ops/access/network/private-access/examples/tcr-dns-status.json b/src/content/docs/cli/tcr/ops/access/network/private-access/examples/tcr-dns-status.json new file mode 100644 index 0000000..3a06e1c --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/network/private-access/examples/tcr-dns-status.json @@ -0,0 +1,11 @@ +{ + "VpcSet": [ + { + "InstanceId": "tcr-nn8smeyj", + "VpcId": "vpc-of73262z", + "EniLBIp": "10.0.0.5", + "UsePublicDomain": true, + "RegionName": "ap-guangzhou" + } + ] +} diff --git a/src/content/docs/cli/tcr/ops/access/network/private-access/private-access-tcrctl.md b/src/content/docs/cli/tcr/ops/access/network/private-access/private-access-tcrctl.md new file mode 100644 index 0000000..d1e7cab --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/network/private-access/private-access-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "配置内网访问控制(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[配置内网访问控制](https://cloud.tencent.com/document/product/1141/41838) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/access/network/private-access/private-access.md b/src/content/docs/cli/tcr/ops/access/network/private-access/private-access.md new file mode 100644 index 0000000..dcdc715 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/network/private-access/private-access.md @@ -0,0 +1,346 @@ +--- +title: "配置内网访问控制" +description: "· page_id `41838`" +--- + +> 对照官方:[配置内网访问控制](https://cloud.tencent.com/document/product/1141/41838) · page_id `41838` + +## 概述 + +腾讯云容器镜像服务(TCR)企业版支持内网访问控制,通过将私有网络(VPC)接入至企业版实例,实现 VPC 内客户端通过内网链路访问实例。内网访问能够提升镜像拉取速度、避免公网带宽成本,并基于私有网络边界实现访问隔离。 + +一个 TCR 企业版实例可绑定**多个**私有网络。完成内网访问链路接入后,需配置内网域名解析(私有域解析 PrivateDNS 自动配置,或自建 DNS / 手动 hosts),使 VPC 内云服务器可通过实例域名解析到内网 IP。 + +**跨产品依赖**:本页操作依赖 [私有网络 VPC](https://cloud.tencent.com/document/product/215) 和 [私有域解析 PrivateDNS](https://cloud.tencent.com/document/product/1338)。需先在实例所在地域创建 VPC 和子网,再执行本页命令。 + +## 前置条件 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0 + +# 2. 检查 CAM 权限(TCR 读写) +tccli tcr DescribeInstances --region ap-guangzhou +# expected: exit 0,返回实例列表(可为空) + +# 3. 检查 CAM 权限(VPC 读写) +tccli vpc DescribeVpcs --region ap-guangzhou +# expected: exit 0,返回 VPC 列表(可为空) +``` + +### 资源检查 + +```bash +# 4. 确认 TCR 实例存在且状态正常 +tccli tcr DescribeInstances --Registryids '[""]' --region +# expected: exit 0, Status: "Running", RegistryType: "standard" 或以上 + +# 5. 确认 VPC 存在(目标实例所在地域) +tccli vpc DescribeVpcs --VpcIds '[""]' --region +# expected: exit 0,VpcSet 中包含目标 VPC + +# 6. 确认子网存在且可用 IP 数量 >= 1 +tccli vpc DescribeSubnets --SubnetIds '[""]' --region +# expected: exit 0, AvailableIpAddressCount >= 1 +``` + +## 控制台与 CLI 参数映射 + +### 操作索引 + +| 控制台操作 | CLI | 幂等 | +|-----------|-----|:--:| +| 新建内网访问链路(接入私有网络) | `tccli tcr ManageInternalEndpoint --Operation Create` | 否(同一 VPC 重复创建报错) | +| 查看内网访问链路列表 | `tccli tcr DescribeInternalEndpoints` | 是 | +| 开启自动内网解析 | `tccli tcr CreateInternalEndpointDns` | 否(同一 VPC+EniLBIp 组合重复创建报错) | +| 查看内网解析状态 | `tccli tcr DescribeInternalEndpointDnsStatus` | 是 | +| 关闭自动内网解析 | `tccli tcr DeleteInternalEndpointDns` | 是 | +| 删除内网访问链路 | `tccli tcr ManageInternalEndpoint --Operation Delete` | 否 | + +### 关键字段说明(`ManageInternalEndpoint`) + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|------|-----------|---------| +| RegistryId | String | 是 | 目标实例 ID,由 `DescribeInstances` 返回 | 实例不存在返回 `FailedOperation` | +| Operation | String | 是 | `Create`(新建)/ `Delete`(删除) | 无效值返回 `InvalidParameter` | +| VpcId | String | 是 | 私有网络 ID,**须与实例同地域**,须提前通过 `tccli vpc CreateVpc` 创建 | VPC 不存在或跨地域返回 `FailedOperation` | +| SubnetId | String | 是 | 子网 ID,**须属于目标 VPC**,须提前通过 `tccli vpc CreateSubnet` 创建,`AvailableIpAddressCount` >= 1 | 子网不存在或 IP 枯竭返回 `FailedOperation` | + +### 关键字段说明(`CreateInternalEndpointDns`) + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|------|-----------|---------| +| InstanceId | String | 是 | TCR 实例 ID,**注意参数名为 `--InstanceId` 而非常见的 `--RegistryId`** | 使用 `--RegistryId` 返回 `MissingParameter` | +| VpcId | String | 是 | 已接入实例的 VPC ID | VPC 与已有内网链路不匹配返回 `FailedOperation` | +| EniLBIp | String | 是 | 内网解析 IP(即 `DescribeInternalEndpoints` 返回的 `AccessIp`),**须在目标子网 CIDR 范围内** | IP 不在子网 CIDR 内返回 `FailedOperation` | + +> **陷阱汇总:** +> +> | # | 陷阱 | 说明 | +> |---|------|------| +> | 1 | VPC 和 Subnet 是前提 | `ManageInternalEndpoint` 要求 VPC 和子网已存在。需先通过 `tccli vpc CreateVpc` 和 `tccli vpc CreateSubnet` 创建后再执行本页命令 | +> | 2 | EniLBIp 须在 Subnet CIDR 内 | `CreateInternalEndpointDns` 的 `--EniLBIp` 必须是子网 CIDR 内的合法 IP,通常使用 `DescribeInternalEndpoints` 返回的 `AccessIp` | +> | 3 | `--InstanceId` 非 `--RegistryId` | `CreateInternalEndpointDns` 和 `DeleteInternalEndpointDns` 的参数名为 `--InstanceId`,与其他 TCR API 的 `--RegistryId` 不同。传错参数名会触发 `MissingParameter` 错误 | + +## 操作步骤 + +### 1. 新建内网访问链路 + +将 VPC 接入 TCR 实例,在指定子网内分配一个内网 IP(`AccessIp`)。 + +一个实例可绑定多个 VPC。如需接入多个 VPC,重复执行本命令,每次指定不同的 `--VpcId` / `--SubnetId` 组合。 + +#### 选择依据 + +- **VpcId**:选择实例所在地域的 VPC。实例与 VPC 跨地域将导致 `FailedOperation`。 +- **SubnetId**:选择目标 VPC 下有可用 IP 的子网(`AvailableIpAddressCount` >= 1)。 + +#### 执行创建 + +```bash +tccli tcr ManageInternalEndpoint \ + --RegistryId \ + --Operation Create \ + --VpcId \ + --SubnetId \ + --region +``` + +参考输出: + +```json +{ + "RequestId": "b063c21b-0000-0000-0000-000000000000" +} +``` + +#### 轮询确认链路就绪 + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId \ + --region +``` + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 链路状态 | `AccessVpcSet[].Status` | `Running` | +| 内网 IP | `AccessVpcSet[].AccessIp` | 非空,子网 CIDR 内 IP | + +参考输出: + +```json +{ + "AccessVpcSet": [ + { + "VpcId": "", + "SubnetId": "", + "Status": "Running", + "AccessIp": "10.0.0.5" + } + ], + "TotalCount": 1, + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +> **注意**:新建操作是**异步**的,通常 1-2 分钟完成。轮询到 `Status: "Running"` 且 `AccessIp` 不为空后即可进行后续 DNS 配置。 + +### 2. 配置内网域名解析 + +内网访问链路就绪后,需配置内网域名解析。推荐使用私有域解析 PrivateDNS 自动配置。以下为 CLI 完整操作流程。 + +#### 2.1 创建内网 DNS 解析 + +##### 选择依据 + +- **InstanceId**:即 TCR 实例 ID(`RegistryId`),注意参数名是 `--InstanceId` 而非 `--RegistryId`(陷阱 #3)。 +- **EniLBIp**:使用 `DescribeInternalEndpoints` 返回的 `AccessIp`。该 IP 须在目标子网 CIDR 范围内(陷阱 #2),否则返回 `FailedOperation`。 +- **UsePublicDomain**:`true` 解析默认公网域名,`false` 解析 VPC 专用域名(`<实例名>-vpc.tencentcloudcr.com`)。推荐 `true`,与现有 docker login / pull 命令兼容。 + +##### 执行创建 + +```bash +tccli tcr CreateInternalEndpointDns \ + --InstanceId \ + --VpcId \ + --EniLBIp \ + --UsePublicDomain true \ + --region +``` + +参考输出: + +```json +{ + "RequestId": "72d88a72-0000-0000-0000-000000000000" +} +``` + +#### 2.2 查询内网 DNS 解析状态 + +```bash +tccli tcr DescribeInternalEndpointDnsStatus \ + --InstanceId \ + --region +``` + +参考输出: + +```json +{ + "VpcSet": [ + { + "VpcId": "", + "EniLBIp": "", + "Status": "ENABLED" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 解析状态 | `VpcSet[].Status` | `ENABLED` | + +`Status: "ENABLED"` 表示内网 DNS 解析已生效。DNS 解析完全生效通常有 1-2 分钟传播延迟。 + +#### 2.3 删除内网 DNS 解析(清理用) + +```bash +tccli tcr DeleteInternalEndpointDns \ + --InstanceId \ + --VpcId \ + --EniLBIp \ + --region +``` + +参数名注意:此处同样是 `--InstanceId` 而非 `--RegistryId`(陷阱 #3)。 + +### 3. 删除内网访问链路 + +删除链路前,建议先执行 `DeleteInternalEndpointDns` 关闭对应 DNS 解析。 + +```bash +tccli tcr ManageInternalEndpoint \ + --RegistryId \ + --Operation Delete \ + --VpcId \ + --SubnetId \ + --region +``` + +参考输出: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +### 4. 备选方案(不推荐) + +本节提供两种不需要 PrivateDNS 的备选方案,适用于 PrivateDNS 服务不可用的场景。 + +#### 4.1 TCR 插件自动配置(TKE 集群) + +在 TKE 集群中安装 TCR 插件,勾选"启用内网解析功能",插件自动为集群内节点配置内网解析。参见 [TCR 说明](https://cloud.tencent.com/document/product/457/49225)。 + +#### 4.2 手动配置云服务器 hosts + +以 Linux 云服务器为例,登录后执行: + +```bash +echo ' .tencentcloudcr.com' >> /etc/hosts +``` + +## 验证 + +### Control plane(tccli) + +```bash +# 1. 确认内网链路状态为 Running,AccessIp 不为空 +tccli tcr DescribeInternalEndpoints \ + --RegistryId \ + --region + +# 2. 确认 DNS 解析状态为 ENABLED +tccli tcr DescribeInternalEndpointDnsStatus \ + --InstanceId \ + --region +``` + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 链路状态 | `AccessVpcSet[].Status` | `Running` | +| 内网 IP | `AccessVpcSet[].AccessIp` | 非空 | +| DNS 解析 | `VpcSet[].Status` | `ENABLED` | + +### Data plane(VPC 内测试,可选) + +在已接入 VPC 内的云服务器上执行: + +```bash +# 测试内网连通性 +curl -k https:///v2/ + +# 测试域名解析 +nslookup .tencentcloudcr.com +``` + +## 清理 + +> **清理顺序**:先关闭 DNS 解析,再删除内网访问链路。若先删链路再关 DNS,`DeleteInternalEndpointDns` 的 `--EniLBIp` 将找不到对应 IP。 + +```bash +# 1. 关闭自动内网解析 +tccli tcr DeleteInternalEndpointDns \ + --InstanceId \ + --VpcId \ + --EniLBIp \ + --region + +# 2. 删除内网访问链路 +tccli tcr ManageInternalEndpoint \ + --RegistryId \ + --Operation Delete \ + --VpcId \ + --SubnetId \ + --region + +# 3. 确认链路已清理 +tccli tcr DescribeInternalEndpoints \ + --RegistryId \ + --region +# expected: AccessVpcSet 为 null 或 TotalCount 为 0 +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ManageInternalEndpoint Create` 返回 `FailedOperation` | `tccli vpc DescribeVpcs --VpcIds '[""]' --region ` 确认 VPC 存在;`tccli vpc DescribeSubnets --SubnetIds '[""]' --region ` 确认子网有可用 IP | VPC/子网不存在、跨地域、或子网 IP 已耗尽。**这是陷阱 #1**:VPC 和 Subnet 需提前创建 | 确认 VPC/Subnet 与实例同地域;确认子网 `AvailableIpAddressCount` >= 1;必要时 `tccli vpc CreateVpc` 和 `tccli vpc CreateSubnet` | +| `CreateInternalEndpointDns` 返回 `MissingParameter` | 检查命令参数名 | 参数名为 `--InstanceId` 而非 `--RegistryId`。**这是陷阱 #3** | 将 `--RegistryId` 改为 `--InstanceId` | +| `CreateInternalEndpointDns` 返回 `FailedOperation` | 验证 `--EniLBIp` 是否在子网 CIDR 内:`tccli vpc DescribeSubnets --SubnetIds '[""]' --region ` 查看 `CidrBlock` | `--EniLBIp` 不在子网 CIDR 范围内。**这是陷阱 #2** | 使用 `DescribeInternalEndpoints` 返回的 `AccessIp` 作为 `--EniLBIp` | +| `DescribeInternalEndpointDnsStatus` 返回 `Status: "DISABLED"` | — | DNS 解析未开启,或之前已被 `DeleteInternalEndpointDns` 关闭 | 执行 `CreateInternalEndpointDns` 开启解析 | +| `DescribeInternalEndpoints` 返回 `AccessVpcSet: null, TotalCount: 0` | — | 实例尚未绑定任何 VPC,正常初始状态 | 执行 `ManageInternalEndpoint --Operation Create` 新建内网链路 | +| `ManageInternalEndpoint` 返回 `LimitExceeded` | `tccli vpc DescribeVpcs --region ` 统计现有 VPC 数量 | VPC 数量达账号配额上限。**此为环境限制,非命令错误** | 复用已有 VPC;或删除不再使用的 VPC 释放配额;或联系腾讯云提升配额 | +| DNS 已启用(`Status: "ENABLED"`)但 VPC 内仍无法解析 | `nslookup .tencentcloudcr.com` 在 VPC 内测试 | DNS 传播延迟(通常 1-2 分钟),或 PrivateDNS 未创建对应 A 记录 | 等待 2 分钟后重试;若持续失败,检查 PrivateDNS 控制台 A 记录 | +| 手动 `echo` 到 `/etc/hosts` 后仍不连通 | `curl -k https:///v2/` 测试内网连通性 | VPC 安全组未放通 443/tcp | `tccli vpc CreateSecurityGroupPolicies` 添加入站规则放通目标端口 | + +## 下一步 + +- [配置公网访问控制](../public-access) — 公网访问白名单管理 +- [配置自定义域名](../../domain/custom-domain) — 自定义域名与内网链路的结合使用 +- [TKE 集群使用 TCR 插件内网免密拉取容器镜像](https://cloud.tencent.com/document/product/1141/48184) +- [配置实例复制](../../../image-distribution/cross-region-replication) — 跨地域接入私有网络 + +## 控制台替代 + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 选择目标实例 → **访问控制** > **内网访问** → 单击**接入私有网络** → 选择目标 VPC 和子网 → 等待链路状态变为**链路正常** → 单击**管理自动解析** → 开启域名解析。 diff --git a/src/content/docs/cli/tcr/ops/access/network/public-access-tcrctl.md b/src/content/docs/cli/tcr/ops/access/network/public-access-tcrctl.md new file mode 100644 index 0000000..b6b13db --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/network/public-access-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "配置公网访问控制(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[配置公网访问控制](https://cloud.tencent.com/document/product/1141/41837) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/access/network/public-access.md b/src/content/docs/cli/tcr/ops/access/network/public-access.md new file mode 100644 index 0000000..50d5d89 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/network/public-access.md @@ -0,0 +1,447 @@ +--- +title: "配置公网访问控制(tccli)" +description: "· page_id `41837`" +--- + +> 对照官方:[配置公网访问控制](https://cloud.tencent.com/document/product/1141/41837) · page_id `41837` + +## 概述 + +通过 `tccli tcr ManageExternalEndpoint` 开启或关闭 TCR 企业版实例的公网访问入口。新创建的 TCR 企业版实例**默认关闭公网访问入口**(`Status: "Closed"`),无法从公网环境直接推送/拉取镜像;不开启公网访问则无法通过 `docker login` 从外网登录。 + +开启公网访问入口后,任何人可通过实例公网域名(`<实例名>.tencentcloudcr.com`)访问实例,**默认拒绝全部来源**。需额外配置公网白名单策略(`CreateSecurityPolicy` / `CreateMultipleSecurityPolicy`)放通指定 IP 地址段后方可正常访问。 + +`ManageExternalEndpoint` 为**异步操作**。开启后状态流转为 `Closed` → `Opening` → `Opened`,需轮询 `DescribeExternalEndpointStatus` 确认终态。**在 `Opening` 状态下立即执行关闭操作将被拒绝**(见[排障](#排障)),必须等待状态到达 `Opened` 后再操作。 + +> **basic 实例限制**:基础版实例不支持安全策略(白名单)功能。`DescribeSecurityPolicies` 在 basic 实例上返回 `ResourceNotFound` 错误(安全组 ID 未获取到)。公网访问控制的白名单功能需 **standard 及以上** 规格。 + +## 前置条件 + +- [环境准备](../../index.md):`tccli` 已安装配置,地域已设置(默认 `ap-guangzhou`)。 +- 已成功 [创建企业版实例](../../../create),实例状态为 `Running`。 +- 获取目标实例的 `RegistryId`(如 `tcr-nn8smeyj`): + ```bash + tccli tcr DescribeInstances --region ap-guangzhou --output json + ``` +- 如使用子账号操作,需授予对应实例的 `QcloudTCRFullAccess` 或资源级权限,参见 [企业版授权方案示例](https://cloud.tencent.com/document/product/1141/41417)。 +- 若需配置安全策略(白名单),实例规格须为 **standard 或 premium**(basic 不支持)。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | CLI | 幂等 | +|-----------|-----|:--:| +| 开启公网访问入口 | `tccli tcr ManageExternalEndpoint --Operation Create` | 是(重复开启返回成功) | +| 关闭公网访问入口 | `tccli tcr ManageExternalEndpoint --Operation Delete` | 否(`Opening` 状态下关闭将被拒绝,见[排障](#排障)) | +| 查看公网访问入口状态 | `tccli tcr DescribeExternalEndpointStatus` | 是 | +| 查看公网白名单列表 | `tccli tcr DescribeSecurityPolicies` | 是(basic 实例不支持) | +| 添加公网白名单 | `tccli tcr CreateSecurityPolicy` | 是(允许同一 CIDR 重复创建,需 standard+) | +| 修改公网白名单 | `tccli tcr ModifySecurityPolicy --PolicyIndex ` | 是(需 standard+) | +| 批量添加公网白名单 | `tccli tcr CreateMultipleSecurityPolicy` | 是(需 standard+) | +| 删除公网白名单 | `tccli tcr DeleteSecurityPolicy` | 否(策略不存在时报 `ResourceNotFound`,需 standard+) | + +## 关键字段说明 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | 实例 ID,形如 `tcr-nn8smeyj` | 实例不存在 → `ResourceNotFound` | +| `Operation` | String | 是 | `Create` = 开启公网入口,`Delete` = 关闭公网入口 | 填写非法值 → `InvalidParameter` | +| `CidrBlock` | String | 是(白名单) | IPv4 地址或 CIDR 段(如 `10.0.0.1`、`192.168.1.0/24`)。`0.0.0.0/0` 表示所有 IP,不建议直接使用 | 格式不合法 → `InvalidParameter` | +| `Description` | String | 是(白名单) | 白名单策略描述,必填非空 | 为空 → `InvalidParameter` | +| `PolicyIndex` | Integer | 条件 | 策略序号(从 0 开始),用于修改/删除操作,由 `DescribeSecurityPolicies` 返回 | 无效 index → `InvalidParameter` | +| `PolicyVersion` | String | 条件 | 策略版本号,用于删除操作,由 `DescribeSecurityPolicies` 返回 | 版本不匹配 → `ResourceNotFound` | + +## 操作步骤 + +### 步骤1:查看公网访问入口状态(初始 Closed) + +```bash +tccli tcr DescribeExternalEndpointStatus \ + --RegistryId tcr-nn8smeyj \ + --region ap-guangzhou \ + --output json +``` + +**输出**(公网入口关闭状态): + +```json +{ + "Status": "Closed", + "Reason": "", + "RequestId": "4d1c4fc7-4404-45d4-a664-f06f86f01c32" +} +``` + +| Status | 含义 | +|--------|------| +| `Closed` | 公网访问入口已关闭(初始状态) | +| `Opening` | 公网访问入口开启中(过渡态,不可操作) | +| `Opened` | 公网访问入口已开启(终态,可配置白名单) | +| `Deleting` | 公网访问入口关闭中(过渡态) | + +### 步骤2:开启公网访问入口 + +```bash +tccli tcr ManageExternalEndpoint \ + --RegistryId tcr-nn8smeyj \ + --Operation Create \ + --region ap-guangzhou \ + --output json +``` + +| 参数 | 类型 | 必填 | 说明 | +|------|------|:--:|------| +| `RegistryId` | String | 是 | 实例 ID | +| `Operation` | String | 是 | `Create` = 开启公网入口,`Delete` = 关闭公网入口 | + +**输出**: + +```json +{ + "RegistryId": "tcr-nn8smeyj", + "RequestId": "63325fef-3763-4738-bb1f-5a70c37f0272" +} +``` + +`ManageExternalEndpoint` 是**异步操作**,返回成功只表示请求已提交,不代表公网入口已开启。必须轮询 `DescribeExternalEndpointStatus` 确认状态到达 `Opened`。 + +### 步骤3:轮询确认公网入口开启(Opening → Opened) + +```bash +tccli tcr DescribeExternalEndpointStatus \ + --RegistryId tcr-nn8smeyj \ + --region ap-guangzhou \ + --output json +``` + +**输出**(终态 — `Opened`): + +```json +{ + "Status": "Opened", + "Reason": "", + "RequestId": "eac6b301-a322-493a-8e36-83b295459397" +} +``` + +> `Status` 从 `Closed` → `Opening` → `Opened`,中间过渡态 `Opening` 时**不可执行关闭操作**(`ManageExternalEndpoint --Operation Delete`),否则将返回错误(见[排障](#排障))。等待 5--10 秒后再次轮询,直至 `Status` 变为 `Opened`。 + +**状态流转示意图**: + +``` +Closed ──[ManageExternalEndpoint Create]──> Opening ──[轮询等待]──> Opened + │ + [ManageExternalEndpoint Delete] + │ + v +Closed <──[轮询等待]── Deleting <──────────────────────────────────────┘ +``` + +### 步骤4:配置访问白名单策略 + +> **basic 实例注意**:以下白名单操作(`CreateSecurityPolicy`、`DescribeSecurityPolicies` 等)**在 basic 实例上不可用**。basic 实例公网入口开启后无白名单机制,任何人可通过公网域名直接访问。如需白名单控制,请将实例升级至 standard 或 premium。以下命令以 standard 实例为示例。 + +#### 4a. 查看已有白名单 + +```bash +tccli tcr DescribeSecurityPolicies \ + --RegistryId \ + --region ap-guangzhou \ + --output json +``` + +**输出**(standard/premium 实例): + +```json +{ + "SecurityPolicySet": [ + { + "PolicyIndex": 0, + "CidrBlock": "192.168.1.0/24", + "Description": "办公网络出口 IP", + "PolicyVersion": "1" + } + ], + "RequestId": "eac6b301-a322-493a-8e36-83b295459397" +} +``` + +**输出**(basic 实例 — **不支持**): + +```json +{ + "Response": { + "Error": { + "Code": "ResourceNotFound", + "Message": "Failed to get security group id from registry: tcr-nn8smeyj" + }, + "RequestId": "d90d771b-eacb-4b7e-8782-d3f5f10eb207" + } +} +``` + +> basic 实例未绑定安全组,因此 `DescribeSecurityPolicies` 返回 `ResourceNotFound`。这是**预期行为**而非 Bug——basic 规格不提供安全策略白名单功能。 + +#### 4b. 添加公网白名单 + +添加放通 IP 地址段。支持单个 IPv4 地址或 CIDR 格式(如 `192.168.1.0/24`)。`0.0.0.0/0` 表示放通所有来源 IP,**不建议直接使用**。 + +```bash +tccli tcr CreateSecurityPolicy \ + --RegistryId tcr-nn8smeyj \ + --CidrBlock "10.0.0.0/16" \ + --Description "办公网络出口 IP" \ + --region ap-guangzhou \ + --output json +``` + +**输出**: + +```json +{ + "RegistryId": "tcr-nn8smeyj", + "RequestId": "d4e5f6a7-b8c9-0123-defa-234567890123" +} +``` + +真机验证:创建 `CidrBlock: 0.0.0.0/0` 策略返回 `PolicyIndex: 0`,`PolicyVersion: "1"`。可多次调用添加多条白名单策略,每条策略对应一个 IP 地址段。 + +> **安全建议**:白名单应按最小权限原则配置,仅放通必要的公网出口 IP 地址段。如服务器同时有多个出口 IP,需全部添加。 + +#### 4c. 批量添加公网白名单 + +当需要一次添加多条白名单策略时,使用 `CreateMultipleSecurityPolicy` 批量操作: + +```bash +tccli tcr CreateMultipleSecurityPolicy \ + --RegistryId \ + --SecurityGroupPolicySet '[{"Description":"办公网络出口 IP","CidrBlock":"192.168.1.0/24"},{"Description":"VPN 出口 IP","CidrBlock":"10.0.0.0/16"}]' \ + --region ap-guangzhou \ + --output json +``` + +**输出**: + +```json +{ + "RegistryId": "tcr-nn8smeyj", + "RequestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" +} +``` + +> `--SecurityGroupPolicySet` 须为合法 JSON 数组,Shell 中使用单引号包裹以避免转义。 + +#### 4d. 修改公网白名单 + +修改已存在的白名单策略,需指定 `--PolicyIndex`(从 `DescribeSecurityPolicies` 获取)。`--CidrBlock` 和 `--Description` 均为必填,将替换原有内容: + +```bash +tccli tcr ModifySecurityPolicy \ + --RegistryId \ + --PolicyIndex 0 \ + --CidrBlock "10.0.0.0/16" \ + --Description "更新后的 VPN 出口 IP" \ + --region ap-guangzhou \ + --output json +``` + +**输出**: + +```json +{ + "RegistryId": "tcr-nn8smeyj", + "RequestId": "b2c3d4e5-f6a7-8901-bcde-f12345678901" +} +``` + +| 参数 | 说明 | +|------|------| +| `--RegistryId` | 实例 ID | +| `--PolicyIndex` | 策略序号,从 0 开始,与 `DescribeSecurityPolicies` 返回的 `PolicyIndex` 对应 | +| `--CidrBlock` | 修改后的 IP 地址段或 CIDR(必填) | +| `--Description` | 修改后的策略描述(必填) | + +#### 4e. 删除公网白名单 + +删除前先从 `DescribeSecurityPolicies` 获取当前 `PolicyVersion`: + +```bash +tccli tcr DeleteSecurityPolicy \ + --RegistryId \ + --PolicyIndex 0 \ + --PolicyVersion \ + --region ap-guangzhou \ + --output json +``` + +**输出**: + +```json +{ + "RegistryId": "tcr-nn8smeyj", + "RequestId": "c3d4e5f6-a7b8-9012-cdef-123456789012" +} +``` + +也可通过 `--CidrBlock` 指定要删除的 IP 地址段: + +```bash +tccli tcr DeleteSecurityPolicy \ + --RegistryId \ + --CidrBlock "10.0.0.0/16" \ + --PolicyVersion \ + --region ap-guangzhou \ + --output json +``` + +### 步骤5:关闭公网访问入口 + +> **关键前提**:关闭操作要求公网入口状态为 `Opened`。若状态仍处于 `Opening`,关闭将被拒绝。务必先通过 `DescribeExternalEndpointStatus` 确认状态后再操作。 + +```bash +tccli tcr ManageExternalEndpoint \ + --RegistryId tcr-nn8smeyj \ + --Operation Delete \ + --region ap-guangzhou \ + --output json +``` + +**输出**: + +```json +{ + "RegistryId": "tcr-nn8smeyj", + "RequestId": "e8c2171f-fc2f-475d-920f-048f3cb48317" +} +``` + +关闭操作也是异步的,轮询 `DescribeExternalEndpointStatus` 直到 `Status` 变为 `Closed` 确认关闭完成。 + +## 验证 + +### 控制面(tccli) + +| 验证项 | 命令 | 期望结果 | +|--------|------|---------| +| 公网入口已开启 | `DescribeExternalEndpointStatus` | `Status: "Opened"` | +| 公网入口已关闭 | `DescribeExternalEndpointStatus` | `Status: "Closed"` | +| 白名单策略已生效 | `DescribeSecurityPolicies`(standard+) | `SecurityPolicySet` 含目标 CIDR | +| 开启请求已受理 | `ManageExternalEndpoint --Operation Create` | 返回有效 `RequestId` | +| 关闭请求已受理 | `ManageExternalEndpoint --Operation Delete` | 返回有效 `RequestId` | + +```bash +# 确认公网入口状态 +tccli tcr DescribeExternalEndpointStatus \ + --RegistryId tcr-nn8smeyj \ + --region ap-guangzhou \ + --output json +# 期望:Status: "Opened" +``` + +```bash +# 确认白名单策略(仅 standard+ 实例) +tccli tcr DescribeSecurityPolicies \ + --RegistryId \ + --region ap-guangzhou \ + --output json +# 期望:SecurityPolicySet 包含目标 CidrBlock +``` + +### 数据面 + +```bash +# 公网入口开启且白名单放通后,可从外网 docker login +docker login .tencentcloudcr.com +# 期望:Login Succeeded(需先创建访问凭证,参见 CreateInstanceToken) +``` + +## 清理 + +恢复为不开启公网访问且无白名单的原始状态: + +```bash +# 1. 列出并删除所有白名单(仅 standard+ 实例需要) +tccli tcr DescribeSecurityPolicies \ + --RegistryId \ + --region ap-guangzhou \ + --output json + +# 对每个 PolicyIndex 执行(PolicyVersion 从 DescribeSecurityPolicies 获取): +tccli tcr DeleteSecurityPolicy \ + --RegistryId \ + --PolicyIndex \ + --PolicyVersion \ + --region ap-guangzhou \ + --output json + +# 2. 确认状态为 Opened 后关闭公网访问入口 +tccli tcr DescribeExternalEndpointStatus \ + --RegistryId tcr-nn8smeyj \ + --region ap-guangzhou \ + --output json + +# 3. 关闭公网入口 +tccli tcr ManageExternalEndpoint \ + --RegistryId tcr-nn8smeyj \ + --Operation Delete \ + --region ap-guangzhou \ + --output json + +# 4. 轮询确认关闭完成 +tccli tcr DescribeExternalEndpointStatus \ + --RegistryId tcr-nn8smeyj \ + --region ap-guangzhou \ + --output json +# 期望:Status: "Closed" +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ManageExternalEndpoint --Operation Delete` 返回 `Failed to close public network access. The current public network access status is Opening.`(RequestId: `7d5ecc9a-e754-44c0-952d-e3f7174315a0`) | `tccli tcr DescribeExternalEndpointStatus --RegistryId ` 查看当前状态 | 开启操作后立即执行关闭,状态尚在 `Opening` 过渡中,不允许关闭 | 等待状态变为 `Opened` 后再关闭。轮询 `DescribeExternalEndpointStatus` 至 `Status: "Opened"`(通常 5--10 秒) | +| `DescribeSecurityPolicies` 返回 `ResourceNotFound`:"Failed to get security group id from registry: tcr-nn8smeyj" | `tccli tcr DescribeInstances --Registryids '[""]'` 查看 `RegistryType` | 实例规格为 **basic**,basic 实例不支持安全策略(白名单)功能,未绑定安全组 | 升级至 standard 或 premium:`tccli tcr ModifyInstance --RegistryId --RegistryType standard`;或接受 basic 无白名单限制的行为(公网入口开启后无 IP 过滤) | +| `ManageExternalEndpoint` 返回 `FailedOperation` | `tccli tcr DescribeInstances --Registryids '[""]'` 查看 `Status` | 实例状态非 `Running`(如 `Deploying`、`Unhealthy`),或有其他异步操作进行中 | 等待实例状态变为 `Running` 或当前异步操作完成后重试 | +| `CreateSecurityPolicy` 返回 `InvalidParameter` | 检查 `--CidrBlock` 格式 | CIDR 格式不合法(如 `192.168.1.0/abc`)或 `--Description` 为空 | 使用合法 IPv4 地址或 CIDR(如 `192.168.1.0/24`),确保 `--Description` 非空 | +| `CreateSecurityPolicy` 返回 `OperationDenied` | `DescribeExternalEndpointStatus` 查看状态 | 公网入口未开启(状态非 `Opened`)时不允许创建白名单 | 先 `ManageExternalEndpoint --Operation Create` 开启公网入口,轮询至 `Opened` 后再创建 | +| 添加白名单后仍无法访问 | 确认客户端出口 IP | 客户端实际出口 IP 不在白名单 CIDR 范围内;或客户端有多个出口 IP | 查询客户端出口 IP(如 `curl ifconfig.me`),添加到白名单;多出口 IP 需全部添加 | +| `ModifySecurityPolicy` 返回 `InvalidParameter` | `DescribeSecurityPolicies` 确认 `PolicyIndex` | `PolicyIndex` 无效(超出范围 0 ~ N-1),或 `--CidrBlock` / `--Description` 不合法 | 使用 `DescribeSecurityPolicies` 重新获取当前策略列表,确认 `PolicyIndex` 有效 | +| `DeleteSecurityPolicy` 返回 `ResourceNotFound` | `DescribeSecurityPolicies` 确认当前策略 | 策略已被删除,或 `PolicyIndex` 不正确 | 通过 `DescribeSecurityPolicies` 重新确认当前策略列表和 `PolicyVersion` | +| `Delete` 操作后状态长期不变化 | 轮询 `DescribeExternalEndpointStatus` | 异步操作尚在处理中 | 继续轮询(通常数分钟内完成);若超过 5 分钟无变化,联系[在线支持](https://cloud.tencent.com/online-service?from=doc_1141) | +| 公网入口未开启时 `docker login` 失败 | `DescribeExternalEndpointStatus` 确认状态 | 公网入口为 `Closed` 时,公网域名不可达 | 先 `ManageExternalEndpoint --Operation Create` 开启公网入口 | +| 公网入口已开启但 `docker login` 仍失败(basic 实例) | — | basic 实例无白名单,公网入口开启后即可访问。若仍失败,检查网络连接和域名解析 | 确认 `Status: "Opened"`;`nslookup .tencentcloudcr.com` 确认域名解析正常 | + +### Open 后立即 Close 的典型排障流程 + +这是最常见的操作失误场景。正确的流程: + +``` +1. ManageExternalEndpoint --Operation Create (提交开启请求) +2. DescribeExternalEndpointStatus (轮询,5-10s/次) + → Status: "Opening" (过渡态,不可关闭) + → Status: "Opened" (终态,可以关闭) +3. ManageExternalEndpoint --Operation Delete (状态为 Opened 时才能成功) +4. DescribeExternalEndpointStatus (轮询确认 Closed) +``` + +错误流程(将被拒绝): + +``` +1. ManageExternalEndpoint --Operation Create +2. ManageExternalEndpoint --Operation Delete ← 立即关闭,返回错误 + Error: "Failed to close public network access. + The current public network access status is Opening." + RequestId: 7d5ecc9a-e754-44c0-952d-e3f7174315a0 +``` + +## 下一步 + +- [配置内网访问控制](../private-access)(page_id `41838`)— 接入私有网络实现内网免密拉取 +- [访问网络控制概述](../network-overview)(page_id `41836`)— 返回总览页 +- [基于 CAM 管理子账号权限](../../permissions/cam-subaccount) — 配置操作权限 +- [创建企业版实例](../../../create)(page_id `51110`)— 升级实例规格以启用安全策略 + +## 控制台替代 + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 选择目标企业版实例 → **访问控制** > **公网访问** → 点击开启/关闭公网访问入口 → 添加/修改/删除公网白名单。 diff --git a/src/content/docs/cli/tcr/ops/access/network/public-access/public-access-tcrctl.md b/src/content/docs/cli/tcr/ops/access/network/public-access/public-access-tcrctl.md new file mode 100644 index 0000000..b6b13db --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/network/public-access/public-access-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "配置公网访问控制(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[配置公网访问控制](https://cloud.tencent.com/document/product/1141/41837) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/access/network/public-access/public-access.md b/src/content/docs/cli/tcr/ops/access/network/public-access/public-access.md new file mode 100644 index 0000000..50d5d89 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/network/public-access/public-access.md @@ -0,0 +1,447 @@ +--- +title: "配置公网访问控制(tccli)" +description: "· page_id `41837`" +--- + +> 对照官方:[配置公网访问控制](https://cloud.tencent.com/document/product/1141/41837) · page_id `41837` + +## 概述 + +通过 `tccli tcr ManageExternalEndpoint` 开启或关闭 TCR 企业版实例的公网访问入口。新创建的 TCR 企业版实例**默认关闭公网访问入口**(`Status: "Closed"`),无法从公网环境直接推送/拉取镜像;不开启公网访问则无法通过 `docker login` 从外网登录。 + +开启公网访问入口后,任何人可通过实例公网域名(`<实例名>.tencentcloudcr.com`)访问实例,**默认拒绝全部来源**。需额外配置公网白名单策略(`CreateSecurityPolicy` / `CreateMultipleSecurityPolicy`)放通指定 IP 地址段后方可正常访问。 + +`ManageExternalEndpoint` 为**异步操作**。开启后状态流转为 `Closed` → `Opening` → `Opened`,需轮询 `DescribeExternalEndpointStatus` 确认终态。**在 `Opening` 状态下立即执行关闭操作将被拒绝**(见[排障](#排障)),必须等待状态到达 `Opened` 后再操作。 + +> **basic 实例限制**:基础版实例不支持安全策略(白名单)功能。`DescribeSecurityPolicies` 在 basic 实例上返回 `ResourceNotFound` 错误(安全组 ID 未获取到)。公网访问控制的白名单功能需 **standard 及以上** 规格。 + +## 前置条件 + +- [环境准备](../../index.md):`tccli` 已安装配置,地域已设置(默认 `ap-guangzhou`)。 +- 已成功 [创建企业版实例](../../../create),实例状态为 `Running`。 +- 获取目标实例的 `RegistryId`(如 `tcr-nn8smeyj`): + ```bash + tccli tcr DescribeInstances --region ap-guangzhou --output json + ``` +- 如使用子账号操作,需授予对应实例的 `QcloudTCRFullAccess` 或资源级权限,参见 [企业版授权方案示例](https://cloud.tencent.com/document/product/1141/41417)。 +- 若需配置安全策略(白名单),实例规格须为 **standard 或 premium**(basic 不支持)。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | CLI | 幂等 | +|-----------|-----|:--:| +| 开启公网访问入口 | `tccli tcr ManageExternalEndpoint --Operation Create` | 是(重复开启返回成功) | +| 关闭公网访问入口 | `tccli tcr ManageExternalEndpoint --Operation Delete` | 否(`Opening` 状态下关闭将被拒绝,见[排障](#排障)) | +| 查看公网访问入口状态 | `tccli tcr DescribeExternalEndpointStatus` | 是 | +| 查看公网白名单列表 | `tccli tcr DescribeSecurityPolicies` | 是(basic 实例不支持) | +| 添加公网白名单 | `tccli tcr CreateSecurityPolicy` | 是(允许同一 CIDR 重复创建,需 standard+) | +| 修改公网白名单 | `tccli tcr ModifySecurityPolicy --PolicyIndex ` | 是(需 standard+) | +| 批量添加公网白名单 | `tccli tcr CreateMultipleSecurityPolicy` | 是(需 standard+) | +| 删除公网白名单 | `tccli tcr DeleteSecurityPolicy` | 否(策略不存在时报 `ResourceNotFound`,需 standard+) | + +## 关键字段说明 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | 实例 ID,形如 `tcr-nn8smeyj` | 实例不存在 → `ResourceNotFound` | +| `Operation` | String | 是 | `Create` = 开启公网入口,`Delete` = 关闭公网入口 | 填写非法值 → `InvalidParameter` | +| `CidrBlock` | String | 是(白名单) | IPv4 地址或 CIDR 段(如 `10.0.0.1`、`192.168.1.0/24`)。`0.0.0.0/0` 表示所有 IP,不建议直接使用 | 格式不合法 → `InvalidParameter` | +| `Description` | String | 是(白名单) | 白名单策略描述,必填非空 | 为空 → `InvalidParameter` | +| `PolicyIndex` | Integer | 条件 | 策略序号(从 0 开始),用于修改/删除操作,由 `DescribeSecurityPolicies` 返回 | 无效 index → `InvalidParameter` | +| `PolicyVersion` | String | 条件 | 策略版本号,用于删除操作,由 `DescribeSecurityPolicies` 返回 | 版本不匹配 → `ResourceNotFound` | + +## 操作步骤 + +### 步骤1:查看公网访问入口状态(初始 Closed) + +```bash +tccli tcr DescribeExternalEndpointStatus \ + --RegistryId tcr-nn8smeyj \ + --region ap-guangzhou \ + --output json +``` + +**输出**(公网入口关闭状态): + +```json +{ + "Status": "Closed", + "Reason": "", + "RequestId": "4d1c4fc7-4404-45d4-a664-f06f86f01c32" +} +``` + +| Status | 含义 | +|--------|------| +| `Closed` | 公网访问入口已关闭(初始状态) | +| `Opening` | 公网访问入口开启中(过渡态,不可操作) | +| `Opened` | 公网访问入口已开启(终态,可配置白名单) | +| `Deleting` | 公网访问入口关闭中(过渡态) | + +### 步骤2:开启公网访问入口 + +```bash +tccli tcr ManageExternalEndpoint \ + --RegistryId tcr-nn8smeyj \ + --Operation Create \ + --region ap-guangzhou \ + --output json +``` + +| 参数 | 类型 | 必填 | 说明 | +|------|------|:--:|------| +| `RegistryId` | String | 是 | 实例 ID | +| `Operation` | String | 是 | `Create` = 开启公网入口,`Delete` = 关闭公网入口 | + +**输出**: + +```json +{ + "RegistryId": "tcr-nn8smeyj", + "RequestId": "63325fef-3763-4738-bb1f-5a70c37f0272" +} +``` + +`ManageExternalEndpoint` 是**异步操作**,返回成功只表示请求已提交,不代表公网入口已开启。必须轮询 `DescribeExternalEndpointStatus` 确认状态到达 `Opened`。 + +### 步骤3:轮询确认公网入口开启(Opening → Opened) + +```bash +tccli tcr DescribeExternalEndpointStatus \ + --RegistryId tcr-nn8smeyj \ + --region ap-guangzhou \ + --output json +``` + +**输出**(终态 — `Opened`): + +```json +{ + "Status": "Opened", + "Reason": "", + "RequestId": "eac6b301-a322-493a-8e36-83b295459397" +} +``` + +> `Status` 从 `Closed` → `Opening` → `Opened`,中间过渡态 `Opening` 时**不可执行关闭操作**(`ManageExternalEndpoint --Operation Delete`),否则将返回错误(见[排障](#排障))。等待 5--10 秒后再次轮询,直至 `Status` 变为 `Opened`。 + +**状态流转示意图**: + +``` +Closed ──[ManageExternalEndpoint Create]──> Opening ──[轮询等待]──> Opened + │ + [ManageExternalEndpoint Delete] + │ + v +Closed <──[轮询等待]── Deleting <──────────────────────────────────────┘ +``` + +### 步骤4:配置访问白名单策略 + +> **basic 实例注意**:以下白名单操作(`CreateSecurityPolicy`、`DescribeSecurityPolicies` 等)**在 basic 实例上不可用**。basic 实例公网入口开启后无白名单机制,任何人可通过公网域名直接访问。如需白名单控制,请将实例升级至 standard 或 premium。以下命令以 standard 实例为示例。 + +#### 4a. 查看已有白名单 + +```bash +tccli tcr DescribeSecurityPolicies \ + --RegistryId \ + --region ap-guangzhou \ + --output json +``` + +**输出**(standard/premium 实例): + +```json +{ + "SecurityPolicySet": [ + { + "PolicyIndex": 0, + "CidrBlock": "192.168.1.0/24", + "Description": "办公网络出口 IP", + "PolicyVersion": "1" + } + ], + "RequestId": "eac6b301-a322-493a-8e36-83b295459397" +} +``` + +**输出**(basic 实例 — **不支持**): + +```json +{ + "Response": { + "Error": { + "Code": "ResourceNotFound", + "Message": "Failed to get security group id from registry: tcr-nn8smeyj" + }, + "RequestId": "d90d771b-eacb-4b7e-8782-d3f5f10eb207" + } +} +``` + +> basic 实例未绑定安全组,因此 `DescribeSecurityPolicies` 返回 `ResourceNotFound`。这是**预期行为**而非 Bug——basic 规格不提供安全策略白名单功能。 + +#### 4b. 添加公网白名单 + +添加放通 IP 地址段。支持单个 IPv4 地址或 CIDR 格式(如 `192.168.1.0/24`)。`0.0.0.0/0` 表示放通所有来源 IP,**不建议直接使用**。 + +```bash +tccli tcr CreateSecurityPolicy \ + --RegistryId tcr-nn8smeyj \ + --CidrBlock "10.0.0.0/16" \ + --Description "办公网络出口 IP" \ + --region ap-guangzhou \ + --output json +``` + +**输出**: + +```json +{ + "RegistryId": "tcr-nn8smeyj", + "RequestId": "d4e5f6a7-b8c9-0123-defa-234567890123" +} +``` + +真机验证:创建 `CidrBlock: 0.0.0.0/0` 策略返回 `PolicyIndex: 0`,`PolicyVersion: "1"`。可多次调用添加多条白名单策略,每条策略对应一个 IP 地址段。 + +> **安全建议**:白名单应按最小权限原则配置,仅放通必要的公网出口 IP 地址段。如服务器同时有多个出口 IP,需全部添加。 + +#### 4c. 批量添加公网白名单 + +当需要一次添加多条白名单策略时,使用 `CreateMultipleSecurityPolicy` 批量操作: + +```bash +tccli tcr CreateMultipleSecurityPolicy \ + --RegistryId \ + --SecurityGroupPolicySet '[{"Description":"办公网络出口 IP","CidrBlock":"192.168.1.0/24"},{"Description":"VPN 出口 IP","CidrBlock":"10.0.0.0/16"}]' \ + --region ap-guangzhou \ + --output json +``` + +**输出**: + +```json +{ + "RegistryId": "tcr-nn8smeyj", + "RequestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" +} +``` + +> `--SecurityGroupPolicySet` 须为合法 JSON 数组,Shell 中使用单引号包裹以避免转义。 + +#### 4d. 修改公网白名单 + +修改已存在的白名单策略,需指定 `--PolicyIndex`(从 `DescribeSecurityPolicies` 获取)。`--CidrBlock` 和 `--Description` 均为必填,将替换原有内容: + +```bash +tccli tcr ModifySecurityPolicy \ + --RegistryId \ + --PolicyIndex 0 \ + --CidrBlock "10.0.0.0/16" \ + --Description "更新后的 VPN 出口 IP" \ + --region ap-guangzhou \ + --output json +``` + +**输出**: + +```json +{ + "RegistryId": "tcr-nn8smeyj", + "RequestId": "b2c3d4e5-f6a7-8901-bcde-f12345678901" +} +``` + +| 参数 | 说明 | +|------|------| +| `--RegistryId` | 实例 ID | +| `--PolicyIndex` | 策略序号,从 0 开始,与 `DescribeSecurityPolicies` 返回的 `PolicyIndex` 对应 | +| `--CidrBlock` | 修改后的 IP 地址段或 CIDR(必填) | +| `--Description` | 修改后的策略描述(必填) | + +#### 4e. 删除公网白名单 + +删除前先从 `DescribeSecurityPolicies` 获取当前 `PolicyVersion`: + +```bash +tccli tcr DeleteSecurityPolicy \ + --RegistryId \ + --PolicyIndex 0 \ + --PolicyVersion \ + --region ap-guangzhou \ + --output json +``` + +**输出**: + +```json +{ + "RegistryId": "tcr-nn8smeyj", + "RequestId": "c3d4e5f6-a7b8-9012-cdef-123456789012" +} +``` + +也可通过 `--CidrBlock` 指定要删除的 IP 地址段: + +```bash +tccli tcr DeleteSecurityPolicy \ + --RegistryId \ + --CidrBlock "10.0.0.0/16" \ + --PolicyVersion \ + --region ap-guangzhou \ + --output json +``` + +### 步骤5:关闭公网访问入口 + +> **关键前提**:关闭操作要求公网入口状态为 `Opened`。若状态仍处于 `Opening`,关闭将被拒绝。务必先通过 `DescribeExternalEndpointStatus` 确认状态后再操作。 + +```bash +tccli tcr ManageExternalEndpoint \ + --RegistryId tcr-nn8smeyj \ + --Operation Delete \ + --region ap-guangzhou \ + --output json +``` + +**输出**: + +```json +{ + "RegistryId": "tcr-nn8smeyj", + "RequestId": "e8c2171f-fc2f-475d-920f-048f3cb48317" +} +``` + +关闭操作也是异步的,轮询 `DescribeExternalEndpointStatus` 直到 `Status` 变为 `Closed` 确认关闭完成。 + +## 验证 + +### 控制面(tccli) + +| 验证项 | 命令 | 期望结果 | +|--------|------|---------| +| 公网入口已开启 | `DescribeExternalEndpointStatus` | `Status: "Opened"` | +| 公网入口已关闭 | `DescribeExternalEndpointStatus` | `Status: "Closed"` | +| 白名单策略已生效 | `DescribeSecurityPolicies`(standard+) | `SecurityPolicySet` 含目标 CIDR | +| 开启请求已受理 | `ManageExternalEndpoint --Operation Create` | 返回有效 `RequestId` | +| 关闭请求已受理 | `ManageExternalEndpoint --Operation Delete` | 返回有效 `RequestId` | + +```bash +# 确认公网入口状态 +tccli tcr DescribeExternalEndpointStatus \ + --RegistryId tcr-nn8smeyj \ + --region ap-guangzhou \ + --output json +# 期望:Status: "Opened" +``` + +```bash +# 确认白名单策略(仅 standard+ 实例) +tccli tcr DescribeSecurityPolicies \ + --RegistryId \ + --region ap-guangzhou \ + --output json +# 期望:SecurityPolicySet 包含目标 CidrBlock +``` + +### 数据面 + +```bash +# 公网入口开启且白名单放通后,可从外网 docker login +docker login .tencentcloudcr.com +# 期望:Login Succeeded(需先创建访问凭证,参见 CreateInstanceToken) +``` + +## 清理 + +恢复为不开启公网访问且无白名单的原始状态: + +```bash +# 1. 列出并删除所有白名单(仅 standard+ 实例需要) +tccli tcr DescribeSecurityPolicies \ + --RegistryId \ + --region ap-guangzhou \ + --output json + +# 对每个 PolicyIndex 执行(PolicyVersion 从 DescribeSecurityPolicies 获取): +tccli tcr DeleteSecurityPolicy \ + --RegistryId \ + --PolicyIndex \ + --PolicyVersion \ + --region ap-guangzhou \ + --output json + +# 2. 确认状态为 Opened 后关闭公网访问入口 +tccli tcr DescribeExternalEndpointStatus \ + --RegistryId tcr-nn8smeyj \ + --region ap-guangzhou \ + --output json + +# 3. 关闭公网入口 +tccli tcr ManageExternalEndpoint \ + --RegistryId tcr-nn8smeyj \ + --Operation Delete \ + --region ap-guangzhou \ + --output json + +# 4. 轮询确认关闭完成 +tccli tcr DescribeExternalEndpointStatus \ + --RegistryId tcr-nn8smeyj \ + --region ap-guangzhou \ + --output json +# 期望:Status: "Closed" +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ManageExternalEndpoint --Operation Delete` 返回 `Failed to close public network access. The current public network access status is Opening.`(RequestId: `7d5ecc9a-e754-44c0-952d-e3f7174315a0`) | `tccli tcr DescribeExternalEndpointStatus --RegistryId ` 查看当前状态 | 开启操作后立即执行关闭,状态尚在 `Opening` 过渡中,不允许关闭 | 等待状态变为 `Opened` 后再关闭。轮询 `DescribeExternalEndpointStatus` 至 `Status: "Opened"`(通常 5--10 秒) | +| `DescribeSecurityPolicies` 返回 `ResourceNotFound`:"Failed to get security group id from registry: tcr-nn8smeyj" | `tccli tcr DescribeInstances --Registryids '[""]'` 查看 `RegistryType` | 实例规格为 **basic**,basic 实例不支持安全策略(白名单)功能,未绑定安全组 | 升级至 standard 或 premium:`tccli tcr ModifyInstance --RegistryId --RegistryType standard`;或接受 basic 无白名单限制的行为(公网入口开启后无 IP 过滤) | +| `ManageExternalEndpoint` 返回 `FailedOperation` | `tccli tcr DescribeInstances --Registryids '[""]'` 查看 `Status` | 实例状态非 `Running`(如 `Deploying`、`Unhealthy`),或有其他异步操作进行中 | 等待实例状态变为 `Running` 或当前异步操作完成后重试 | +| `CreateSecurityPolicy` 返回 `InvalidParameter` | 检查 `--CidrBlock` 格式 | CIDR 格式不合法(如 `192.168.1.0/abc`)或 `--Description` 为空 | 使用合法 IPv4 地址或 CIDR(如 `192.168.1.0/24`),确保 `--Description` 非空 | +| `CreateSecurityPolicy` 返回 `OperationDenied` | `DescribeExternalEndpointStatus` 查看状态 | 公网入口未开启(状态非 `Opened`)时不允许创建白名单 | 先 `ManageExternalEndpoint --Operation Create` 开启公网入口,轮询至 `Opened` 后再创建 | +| 添加白名单后仍无法访问 | 确认客户端出口 IP | 客户端实际出口 IP 不在白名单 CIDR 范围内;或客户端有多个出口 IP | 查询客户端出口 IP(如 `curl ifconfig.me`),添加到白名单;多出口 IP 需全部添加 | +| `ModifySecurityPolicy` 返回 `InvalidParameter` | `DescribeSecurityPolicies` 确认 `PolicyIndex` | `PolicyIndex` 无效(超出范围 0 ~ N-1),或 `--CidrBlock` / `--Description` 不合法 | 使用 `DescribeSecurityPolicies` 重新获取当前策略列表,确认 `PolicyIndex` 有效 | +| `DeleteSecurityPolicy` 返回 `ResourceNotFound` | `DescribeSecurityPolicies` 确认当前策略 | 策略已被删除,或 `PolicyIndex` 不正确 | 通过 `DescribeSecurityPolicies` 重新确认当前策略列表和 `PolicyVersion` | +| `Delete` 操作后状态长期不变化 | 轮询 `DescribeExternalEndpointStatus` | 异步操作尚在处理中 | 继续轮询(通常数分钟内完成);若超过 5 分钟无变化,联系[在线支持](https://cloud.tencent.com/online-service?from=doc_1141) | +| 公网入口未开启时 `docker login` 失败 | `DescribeExternalEndpointStatus` 确认状态 | 公网入口为 `Closed` 时,公网域名不可达 | 先 `ManageExternalEndpoint --Operation Create` 开启公网入口 | +| 公网入口已开启但 `docker login` 仍失败(basic 实例) | — | basic 实例无白名单,公网入口开启后即可访问。若仍失败,检查网络连接和域名解析 | 确认 `Status: "Opened"`;`nslookup .tencentcloudcr.com` 确认域名解析正常 | + +### Open 后立即 Close 的典型排障流程 + +这是最常见的操作失误场景。正确的流程: + +``` +1. ManageExternalEndpoint --Operation Create (提交开启请求) +2. DescribeExternalEndpointStatus (轮询,5-10s/次) + → Status: "Opening" (过渡态,不可关闭) + → Status: "Opened" (终态,可以关闭) +3. ManageExternalEndpoint --Operation Delete (状态为 Opened 时才能成功) +4. DescribeExternalEndpointStatus (轮询确认 Closed) +``` + +错误流程(将被拒绝): + +``` +1. ManageExternalEndpoint --Operation Create +2. ManageExternalEndpoint --Operation Delete ← 立即关闭,返回错误 + Error: "Failed to close public network access. + The current public network access status is Opening." + RequestId: 7d5ecc9a-e754-44c0-952d-e3f7174315a0 +``` + +## 下一步 + +- [配置内网访问控制](../private-access)(page_id `41838`)— 接入私有网络实现内网免密拉取 +- [访问网络控制概述](../network-overview)(page_id `41836`)— 返回总览页 +- [基于 CAM 管理子账号权限](../../permissions/cam-subaccount) — 配置操作权限 +- [创建企业版实例](../../../create)(page_id `51110`)— 升级实例规格以启用安全策略 + +## 控制台替代 + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 选择目标企业版实例 → **访问控制** > **公网访问** → 点击开启/关闭公网访问入口 → 添加/修改/删除公网白名单。 diff --git a/src/content/docs/cli/tcr/ops/access/permissions/cam-subaccount-tcrctl.md b/src/content/docs/cli/tcr/ops/access/permissions/cam-subaccount-tcrctl.md new file mode 100644 index 0000000..818882e --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/permissions/cam-subaccount-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "基于 CAM 管理子账号权限(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[基于 CAM 管理子账号权限](https://cloud.tencent.com/document/product/1141/41417) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/access/permissions/cam-subaccount.md b/src/content/docs/cli/tcr/ops/access/permissions/cam-subaccount.md new file mode 100644 index 0000000..789ae21 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/permissions/cam-subaccount.md @@ -0,0 +1,653 @@ +--- +title: "基于 CAM 管理子账号权限(tccli)" +description: "· page_id `41417`" +--- + +> 对照官方:[基于 CAM 管理子账号权限](https://cloud.tencent.com/document/product/1141/41417) · page_id `41417` + +## 概述 + +访问管理(Cloud Access Management,CAM)是腾讯云跨产品的统一权限体系。通过 CAM 可为子账号(用户/用户组)授予 TCR 资源的最小粒度访问权限,颗粒度可达**仓库级**。 + +TCR 权限管控全部通过 `tccli cam`(非 `tccli tcr`)完成。CAM 是**账号级别**的服务——无需 `RegistryId`,无实例 tier 依赖,操作对象为策略(Policy)与子账号(User/Uin)。 + +TCR 在 CAM 中的核心概念: + +| 概念 | 说明 | 示例 | +|------|------|------| +| 预设策略(QCS) | 腾讯云预置的 TCR 权限模板,不可删除 | `QcloudTCRFullAccess`、`QcloudTCRReadOnlyAccess` | +| 自定义策略(Local) | 用户按需创建的细粒度权限策略 | 只读某个仓库、管理某个命名空间 | +| CAM Action | 可授权的 API 操作名 | `tcr:CreateInstance`、`tcr:DescribeInstances`、`tcr:DeleteInstance`、`tcr:PullRepository` | +| 资源六段式(QCS) | 授权策略中描述资源的格式 | `qcs::tcr:$region:uin/$uin:instance/$registryId` | + +**QCS 资源六段式格式**: + +``` +qcs::tcr:$region:uin/${uin}:instance/${RegistryId} +qcs::tcr:$region:uin/${uin}:repository/${RegistryId}/${Namespace}/${Repo}/* +``` + +| 段 | 含义 | 常见取值 | +|----|------|---------| +| `qcs` | QCS 协议前缀 | 固定 | +| `tcr` | 产品 ServiceType | 固定 | +| `$region` | 地域(`ap-guangzhou` 等),空值表示所有地域 | `ap-guangzhou`、`""` | +| `uin/$uin` | 主账号 UIN,空值表示创建策略的主账号 | `uin/100049208872`、`""` | +| `instance/$RegistryId` | 实例资源描述 | `instance/tcr-0e2hz15l`、`instance/*` | +| `repository/$RegistryId/$Ns/$Repo` | 仓库资源描述 | `repository/tcr-0e2hz15l/kerwinwjyan-ns/test-repo/*` | + +> 完整的 TCR CAM Action 列表参见 [企业版接入 CAM 的 API 列表](https://cloud.tencent.com/document/product/1141/41605)。常用 Action 前缀:`tcr:Create*`(写入)、`tcr:Describe*`(查询)、`tcr:Delete*`(删除)、`tcr:Modify*`(修改)、`tcr:PullRepository*`(镜像拉取)。 + +## 前置条件 + +- 已完成 [环境准备](../../index.md),安装并配置 `tccli`(默认地域 `ap-guangzhou`)。 +- 使用**主账号**或拥有以下 CAM 管理权限的子账号: + - `cam:CreatePolicy` — 创建自定义策略 + - `cam:AttachUserPolicy` — 为子账号绑定策略 + - `cam:GetPolicy` — 查看策略详情 +- 已存在待授权的子账号(Uin),可通过 `tccli cam ListUsers` 获取。 +- **此页面不依赖 TCR 实例**,无需提前创建实例或仓库。 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置 + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# cam:CreatePolicy, cam:AttachUserPolicy, cam:GetPolicy, cam:ListPolicies, cam:ListUsers +# 验证:执行 ListPolicies 和 ListUsers 确认 CAM 读权限 +tccli cam ListPolicies --Scope Local --Rp 20 --Page 1 --region ap-guangzhou --output json +# expected: exit 0,返回策略列表(可为空) + +tccli cam ListUsers --region ap-guangzhou --output json +# expected: exit 0,返回子账号列表 +``` + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 / 参数 | 幂等 | +|-----------|------------------|------| +| 查看预设策略列表 | `tccli cam ListPolicies --Scope QCS --Keyword tcr` | 是 | +| 查看策略完整 JSON | `tccli cam GetPolicy --PolicyId ` | 是 | +| 按标签搜索策略 | `tccli cam ListPolicies --Scope QCS --TagKey ` | 是 | +| 查看自定义策略 | `tccli cam ListPolicies --Scope Local` | 是 | +| 创建自定义策略(可视化) | —(控制台独有,CLI 需手写 PolicyDocument JSON) | — | +| 创建自定义策略(JSON) | `tccli cam CreatePolicy --PolicyName --PolicyDocument ''` | 否(同名报错) | +| 为子账号/用户组绑定策略 | `tccli cam AttachUserPolicy --PolicyId --AttachUin ` | 否(重复绑定幂等) | +| 为子账号/用户组解绑策略 | `tccli cam DetachUserPolicy --PolicyId --DetachUin ` | 否(重复解绑幂等) | +| 查看子账号已绑定策略 | `tccli cam ListAttachedUserPolicies --TargetUin ` | 是 | +| 删除自定义策略 | `tccli cam DeletePolicy --PolicyId '[]'` | 否 | +| 列出所有子账号 | `tccli cam ListUsers` | 是 | +| 查询子账号详情 | `tccli cam GetUser --Name ` | 是 | +| 策略生效验证 | 子账号执行 `tccli tcr DescribeInstances` 等操作 | — | + +## 操作步骤 + +### 步骤1:查看 TCR 预设策略 + +腾讯云为 TCR 提供两个预设策略(Scope=QCS),可直接绑定给子账号: + +```bash +tccli cam ListPolicies \ + --Scope QCS \ + --Keyword tcr \ + --Rp 20 \ + --Page 1 \ + --region ap-guangzhou \ + --output json +``` + +Output(仅 `ServiceType: "tcr"` 的条目): + +```json +{ + "TotalNum": 2, + "List": [ + { + "PolicyId": 30100465, + "PolicyName": "QcloudTCRFullAccess", + "AddTime": "2019-12-31 19:04:19", + "Type": 2, + "Description": "容器镜像服务(TCR)全读写权限", + "CreateMode": 2, + "Attachments": 18, + "ServiceType": "tcr", + "Deactived": 0, + "IsServiceLinkedPolicy": 0, + "AttachEntityCount": 18, + "AttachEntityBoundaryCount": 0, + "UpdateTime": "2020-09-29 11:37:49" + }, + { + "PolicyId": 30100579, + "PolicyName": "QcloudTCRReadOnlyAccess", + "AddTime": "2019-12-31 19:08:27", + "Type": 2, + "Description": "容器镜像服务(TCR)只读权限", + "CreateMode": 2, + "Attachments": 6, + "ServiceType": "tcr", + "Deactived": 0, + "IsServiceLinkedPolicy": 0, + "AttachEntityCount": 6, + "AttachEntityBoundaryCount": 0, + "UpdateTime": "2020-09-29 11:37:49" + } + ], + "RequestId": "07831f36-9765-498a-a72f-4aca037c343d" +} +``` + +| 策略名 | PolicyId | 说明 | Attachments | +|--------|----------|------|-------------| +| `QcloudTCRFullAccess` | 30100465 | TCR 全读写权限 | 18 | +| `QcloudTCRReadOnlyAccess` | 30100579 | TCR 只读权限 | 6 | + +> `--Keyword tcr` 返回所有 ServiceType 含 "tcr" 的策略(含其他产品的 TCR 关联策略)。仅 `ServiceType: "tcr"` 的条目为 TCR 专属预设策略。若只需 TCR 相关而不限定 ServiceType,去掉 `--Keyword` 过滤可获得完整预设策略列表。 + +### 步骤2:查看策略完整 JSON(PolicyDocument) + +```bash +tccli cam GetPolicy \ + --PolicyId 30100465 \ + --region ap-guangzhou \ + --output json +``` + +Output(`QcloudTCRFullAccess`): + +```json +{ + "PolicyName": "QcloudTCRFullAccess", + "Description": "容器镜像服务(TCR)全读写权限", + "Type": 2, + "AddTime": "2019-12-31 19:04:19", + "UpdateTime": "2020-09-29 11:37:49", + "PolicyDocument": "{\"statement\":[{\"action\":[\"tcr:*\"],\"effect\":\"allow\",\"resource\":\"*\"},{\"action\":[\"cam:GetRole\"],\"effect\":\"allow\",\"resource\":[\"*\"]}],\"version\":\"2.0\"}", + "PresetAlias": "", + "IsServiceLinkedRolePolicy": 0, + "RequestId": "eb493bdd-f809-44dc-aa67-1dec1ae45eff" +} +``` + +格式化后的 PolicyDocument: + +```json +{ + "version": "2.0", + "statement": [ + { + "action": ["tcr:*"], + "effect": "allow", + "resource": "*" + }, + { + "action": ["cam:GetRole"], + "effect": "allow", + "resource": ["*"] + } + ] +} +``` + +**`QcloudTCRReadOnlyAccess`** 的 PolicyDocument: + +```bash +tccli cam GetPolicy --PolicyId 30100579 --region ap-guangzhou --output json +``` + +```json +{ + "PolicyName": "QcloudTCRReadOnlyAccess", + "Description": "容器镜像服务(TCR)只读权限", + "Type": 2, + "AddTime": "2019-12-31 19:08:27", + "UpdateTime": "2020-09-29 11:37:49", + "PolicyDocument": "{\"statement\":[{\"action\":[\"tcr:Describe*\",\"tcr:PullRepository*\"],\"effect\":\"allow\",\"resource\":\"*\"},{\"action\":[\"cam:GetRole\"],\"effect\":\"allow\",\"resource\":[\"*\"]}],\"version\":\"2.0\"}", + "PresetAlias": "", + "IsServiceLinkedRolePolicy": 0, + "RequestId": "753872d4-4943-4b17-bbde-188c7a53bbb9" +} +``` + +格式化 PolicyDocument: + +```json +{ + "version": "2.0", + "statement": [ + { + "action": ["tcr:Describe*", "tcr:PullRepository*"], + "effect": "allow", + "resource": "*" + }, + { + "action": ["cam:GetRole"], + "effect": "allow", + "resource": ["*"] + } + ] +} +``` + +> **注意**:两个预设策略均包含 `cam:GetRole` 声明(Statement 第 2 条)。这是 TCR 内部所需的服务角色查询权限,自定义策略时通常不需要添加此项。 + +**两个预设策略对照**: + +| 维度 | QcloudTCRFullAccess | QcloudTCRReadOnlyAccess | +|------|--------------------|------------------------| +| TCR Actions | `tcr:*` | `tcr:Describe*` + `tcr:PullRepository*` | +| 资源范围 | `*`(全部) | `*`(全部) | +| 额外权限 | `cam:GetRole` | `cam:GetRole` | +| 适用场景 | 运维/管理员 | 开发/只读用户 | +| 能否删除 | 否 | 否 | + +> 判断标准:若预设策略满足需求,直接绑定(跳至[步骤5](#步骤5为子账号绑定策略));若需更细颗粒度(如仅授权某个仓库、某个命名空间、或限制地域),继续[步骤3](#步骤3获取子账号-uin)——[步骤4](#步骤4创建自定义策略)。 + +### 步骤3:获取子账号 Uin + +列出所有子账号: + +```bash +tccli cam ListUsers --region ap-guangzhou --output json +``` + +Output: + +```json +{ + "Data": [ + { + "Uin": 100003627333, + "Name": "foxzhong", + "Uid": 1194262, + "NickName": "foxzhong" + }, + { + "Uin": 100006594350, + "Name": "caryguo", + "Uid": 1542771, + "NickName": "caryguo" + } + ], + "RequestId": "41cc9a1b-ec77-428f-a9e5-1024a88ce355" +} +``` + +按名称精确查找: + +```bash +tccli cam GetUser --Name kerwinwjyan --region ap-guangzhou --output json +``` + +Output: + +```json +{ + "Uin": 100049208872, + "Name": "kerwinwjyan", + "Uid": 25414444, + "Remark": "kerwinwjyan-tag管控用户", + "ConsoleLogin": 1, + "CountryCode": "86", + "RecentlyLoginTime": "2026-06-09 16:51:03", + "RequestId": "91f2f52c-b760-4077-93b9-b818d825c4a9" +} +``` + +> 记录目标子账号的 `Uin`(数字),后续绑定/解绑策略时需使用。 + +### 步骤4:创建自定义策略 + +当预设策略不满足精细化需求时,创建自定义策略(Scope=Local)。核心是构造 `PolicyDocument` JSON——包含 `version` 和 `statement` 数组。 + +**PolicyDocument 结构**: + +```json +{ + "version": "2.0", + "statement": [ + { + "action": ["tcr:"], + "resource": ["qcs::tcr::uin/:/"], + "effect": "allow" + } + ] +} +``` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `version` | String | 固定 `"2.0"` | +| `statement` | Array | 声明数组,每条声明定义一组权限 | +| `statement[].action` | Array of String | CAM Action 名,格式 `tcr:`,支持 `*` 通配(如 `tcr:Describe*`) | +| `statement[].resource` | Array of String | QCS 六段式资源描述,支持 `*` 通配 | +| `statement[].effect` | String | `"allow"` 或 `"deny"` | + +#### 4a. 常用 CAM Action 速查 + +| 权限层级 | Action 示例 | 适用场景 | +|----------|------------|---------| +| 全读写 | `tcr:*` | 管理员 | +| 全部只读 | `tcr:Describe*`、`tcr:PullRepository*` | 开发/审计 | +| 实例管理 | `tcr:CreateInstance`、`tcr:DeleteInstance`、`tcr:ModifyInstance` | 实例生命周期 | +| 实例查询 | `tcr:DescribeInstances`、`tcr:DescribeInstanceStatus` | 查看实例信息 | +| 命名空间管理 | `tcr:CreateNamespace`、`tcr:DeleteNamespace` | 命名空间 CRUD | +| 仓库管理 | `tcr:CreateRepository`、`tcr:DeleteRepository`、`tcr:DescribeRepositories` | 仓库 CRUD | +| 镜像操作 | `tcr:PullRepository`、`tcr:PushRepository` | 镜像推拉 | +| 访问控制 | `tcr:ManageInternalEndpoint`、`tcr:ManageExternalEndpoint` | 网络策略 | +| Token 管理 | `tcr:CreateInstanceToken`、`tcr:DeleteInstanceToken` | 长期凭证 | +| 复制实例 | `tcr:CreateReplicationInstance`、`tcr:DescribeReplicationInstances` | 实例同步 | + +> 完整列表参见 [企业版接入 CAM 的 API 列表](https://cloud.tencent.com/document/product/1141/41605)。 + +#### 4b. 策略模板一:授权子账号管理指定实例 + +```bash +tccli cam CreatePolicy \ + --PolicyName "TCR-Manage-tcr-0e2hz15l" \ + --PolicyDocument '{"version":"2.0","statement":[{"action":["tcr:*"],"resource":["qcs::tcr:ap-guangzhou::instance/tcr-0e2hz15l","qcs::tcr:ap-guangzhou::repository/tcr-0e2hz15l/*"],"effect":"allow"}]}' \ + --Description "全读写 TCR 实例 tcr-0e2hz15l" \ + --region ap-guangzhou \ + --output json +``` + +Output: + +```json +{ + "PolicyId": 14089, + "RequestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" +} +``` + +> `resource` 中 `uin` 段留空表示创建策略的主账号。 + +#### 4c. 策略模板二:授权子账号只读某个仓库 + +```bash +tccli cam CreatePolicy \ + --PolicyName "TCR-ReadOnly-repo-demo" \ + --PolicyDocument '{"version":"2.0","statement":[{"action":["tcr:DescribeRepositories","tcr:PullRepository"],"resource":["qcs::tcr:ap-guangzhou::repository/tcr-0e2hz15l/kerwinwjyan-ns/test-repo","qcs::tcr:ap-guangzhou::repository/tcr-0e2hz15l/kerwinwjyan-ns/test-repo/*"],"effect":"allow"},{"action":["tcr:DescribeInstances","tcr:DescribeInstanceStatus","tcr:DescribeNamespaces"],"resource":["qcs::tcr:ap-guangzhou::instance/tcr-0e2hz15l","qcs::tcr:ap-guangzhou::repository/tcr-0e2hz15l/kerwinwjyan-ns"],"effect":"allow"}]}' \ + --Description "只读 kerwinwjyan-ns/test-repo 仓库" \ + --region ap-guangzhou \ + --output json +``` + +**PolicyDocument 格式化**: + +```json +{ + "version": "2.0", + "statement": [ + { + "action": ["tcr:DescribeRepositories", "tcr:PullRepository"], + "resource": [ + "qcs::tcr:ap-guangzhou::repository/tcr-0e2hz15l/kerwinwjyan-ns/test-repo", + "qcs::tcr:ap-guangzhou::repository/tcr-0e2hz15l/kerwinwjyan-ns/test-repo/*" + ], + "effect": "allow" + }, + { + "action": ["tcr:DescribeInstances", "tcr:DescribeInstanceStatus", "tcr:DescribeNamespaces"], + "resource": [ + "qcs::tcr:ap-guangzhou::instance/tcr-0e2hz15l", + "qcs::tcr:ap-guangzhou::repository/tcr-0e2hz15l/kerwinwjyan-ns" + ], + "effect": "allow" + } + ] +} +``` + +> **设计要点**:只读仓库场景需两个 Statement——第一条授予仓库/镜像的读权限;第二条授予实例和命名空间的查询权限(否则子账号进入控制台或 CLI 无法看到实例列表)。 + +#### 4d. 策略模板三:授权子账号管理指定命名空间 + +```bash +tccli cam CreatePolicy \ + --PolicyName "TCR-Manage-ns-kerwinwjyan-ns" \ + --PolicyDocument '{"version":"2.0","statement":[{"action":["tcr:*"],"resource":["qcs::tcr:ap-guangzhou::repository/tcr-0e2hz15l/kerwinwjyan-ns","qcs::tcr:ap-guangzhou::repository/tcr-0e2hz15l/kerwinwjyan-ns/*"],"effect":"allow"},{"action":["tcr:DescribeInstances","tcr:DescribeInstanceStatus"],"resource":["qcs::tcr:ap-guangzhou::instance/tcr-0e2hz15l"],"effect":"allow"}]}' \ + --Description "管理 kerwinwjyan-ns 命名空间下的全部仓库" \ + --region ap-guangzhou \ + --output json +``` + +> `repository///*` 覆盖命名空间下所有仓库及子路径(tag/artifact)。 + +#### 4e. Shell 中 PolicyDocument 传入技巧 + +| Shell | 方式 | 示例 | +|-------|------|------| +| bash/zsh | 单引号包裹整个 JSON,内部用双引号 | `--PolicyDocument '{"version":"2.0",...}'` | +| bash/zsh(长 JSON) | `--cli-input-json file://policy.json` | 见下方 | +| PowerShell | here-string `@'...'@` | `--PolicyDocument @'{"version":"2.0",...}'@` | +| PowerShell(文件) | `--cli-input-json file://policy.json` | 同 bash | + +文件模式(推荐复杂策略): + +```bash +# 将 PolicyDocument 写入文件 +cat > /tmp/tcr-readonly-repo.json << 'EOF' +{ + "version": "2.0", + "statement": [ + { + "action": ["tcr:DescribeRepositories", "tcr:PullRepository"], + "resource": [ + "qcs::tcr:ap-guangzhou::repository/tcr-0e2hz15l/kerwinwjyan-ns/test-repo", + "qcs::tcr:ap-guangzhou::repository/tcr-0e2hz15l/kerwinwjyan-ns/test-repo/*" + ], + "effect": "allow" + }, + { + "action": ["tcr:DescribeInstances", "tcr:DescribeInstanceStatus", "tcr:DescribeNamespaces"], + "resource": [ + "qcs::tcr:ap-guangzhou::instance/tcr-0e2hz15l", + "qcs::tcr:ap-guangzhou::repository/tcr-0e2hz15l/kerwinwjyan-ns" + ], + "effect": "allow" + } + ] +} +EOF + +tccli cam CreatePolicy \ + --PolicyName "TCR-ReadOnly-repo-demo" \ + --Description "只读 kerwinwjyan-ns/test-repo 仓库" \ + --cli-input-json file:///tmp/tcr-readonly-repo.json \ + --region ap-guangzhou \ + --output json +``` + +### 步骤5:为子账号绑定策略 + +将策略(预设或自定义)绑定到目标子账号: + +```bash +tccli cam AttachUserPolicy \ + --PolicyId \ + --AttachUin <子账号Uin> \ + --region ap-guangzhou \ + --output json +``` + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `PolicyId` | Integer | **是** | 策略 ID(从 `ListPolicies` 或 `CreatePolicy` 获取) | +| `AttachUin` | Integer | **是** | 子账号 Uin(从 `GetUser` / `ListUsers` 获取) | + +Output: + +```json +{ + "RequestId": "b2c3d4e5-f6a7-8901-bcde-f12345678901" +} +``` + +> **幂等**:同一策略重复绑定同一子账号不会报错,返回有效 `RequestId`。 + +### 步骤6:验证绑定结果 + +```bash +tccli cam ListAttachedUserPolicies \ + --TargetUin 100049208872 \ + --Rp 20 \ + --Page 1 \ + --region ap-guangzhou \ + --output json +``` + +Output: + +```json +{ + "TotalNum": 2, + "List": [ + { + "PolicyId": 30100465, + "PolicyName": "QcloudTCRFullAccess", + "AddTime": "2025-06-15 16:53:22", + "CreateMode": 2 + }, + { + "PolicyId": 14089, + "PolicyName": "TCR-Manage-tcr-0e2hz15l", + "AddTime": "2026-06-16 10:30:15", + "CreateMode": 1 + } + ], + "RequestId": "c3d4e5f6-a7b8-9012-cdef-123456789012" +} +``` + +> `CreateMode`:`1` = 自定义策略,`2` = 预设策略(QCS)。 + +### 步骤7:解绑策略 + +```bash +tccli cam DetachUserPolicy \ + --PolicyId \ + --DetachUin <子账号Uin> \ + --region ap-guangzhou \ + --output json +``` + +Output: + +```json +{ + "RequestId": "d4e5f6a7-b8c9-0123-defa-234567890123" +} +``` + +### 步骤8:删除自定义策略 + +```bash +tccli cam DeletePolicy \ + --PolicyId '[]' \ + --region ap-guangzhou \ + --output json +``` + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `PolicyId` | Array of Integer | **是** | 策略 ID 数组,格式 `'[14089]'`。**预设策略不可删除** | + +Output: + +```json +{ + "RequestId": "e5f6a7b8-c9d0-1234-efab-345678901234" +} +``` + +> **注意**:`--PolicyId` 在 `DeletePolicy` 中是 **数组类型**,即使删除单个策略也需传入 `'[14089]'`(而非 `14089`)。 + +## 验证 + +### Control plane (tccli) + +| 验证项 | 命令 | 期望结果 | +|--------|------|---------| +| 预设策略存在 | `tccli cam ListPolicies --Scope QCS --Keyword tcr` | `TotalNum >= 2`,含 `QcloudTCRFullAccess`、`QcloudTCRReadOnlyAccess` | +| 策略详情可查 | `tccli cam GetPolicy --PolicyId ` | 返回 `PolicyName` + 有效 `PolicyDocument` JSON | +| 自定义策略已创建 | `tccli cam ListPolicies --Scope Local` | 列表中含新创建的策略名 | +| 绑定成功 | `tccli cam ListAttachedUserPolicies --TargetUin ` | 列表含目标 `PolicyId` | +| 解绑成功 | 同上(解绑后) | 列表中不含目标 `PolicyId` | +| 策略已删除 | `tccli cam ListPolicies --Scope Local` | 列表中不含已删除的策略 | + +### Data plane(权限生效验证) + +子账号绑定策略后,用该子账号的密钥执行 TCR 操作验证权限生效: + +| 验证项 | 命令(子账号执行) | 期望结果 | +|--------|-------------------|---------| +| 只读用户可 Describe | `tccli tcr DescribeInstances --AllRegion true --region ap-guangzhou --output json` | 返回实例列表 | +| 只读用户不可 Delete | `tccli tcr DeleteInstance --RegistryId tcr-0e2hz15l` | `UnauthorizedOperation` 错误 | +| 仓库只读可 Pull | `docker pull kerwinwjyan-rewrite-001.tencentcloudcr.com/kerwinwjyan-ns/test-repo:latest` | 拉取成功 | +| 仓库只读不可 Push | `docker push kerwinwjyan-rewrite-001.tencentcloudcr.com/kerwinwjyan-ns/test-repo:latest` | `denied` 错误 | +| 命名空间管理者可创建仓库 | `tccli tcr CreateRepository --RegistryId tcr-0e2hz15l --Namespace kerwinwjyan-ns --Repository test-repo-2` | 创建成功 | + +> **注意**:CAM 策略变更有 1--2 分钟传播延迟。若刚绑定策略后子账号仍报权限拒绝,等待 1--2 分钟后重试。 + +## 清理 + +### 解绑策略 + +```bash +tccli cam DetachUserPolicy \ + --PolicyId \ + --DetachUin <子账号Uin> \ + --region ap-guangzhou \ + --output json +``` + +### 删除自定义策略 + +```bash +tccli cam DeletePolicy \ + --PolicyId '[]' \ + --region ap-guangzhou \ + --output json +``` + +> **不可逆提醒**: +> - 删除策略将立即影响所有已绑定该策略的子账号权限。 +> - 预设策略(`QcloudTCRFullAccess` / `QcloudTCRReadOnlyAccess`)不可删除,`DeletePolicy` 将返回 `FailedOperation`。 +> - 若策略仍有绑定关系,`DeletePolicy` 将失败——需先执行 `DetachUserPolicy` 解绑所有关联子账号后重试。 + +## 排障 + +| 现象 | 诊断命令 | 根因 | 修复 | +|------|---------|------|------| +| `CreatePolicy` 返回 `InvalidParameter.PolicyName` | — | 策略名重复(同一主账号下策略名必须唯一) | 更换 `PolicyName` | +| `CreatePolicy` 返回 `InvalidParameter.PolicyDocument` | 用 `python3 -m json.tool` 或 `jq` 校验 JSON | PolicyDocument JSON 格式错误(引号、逗号、数组结构) | 确保 `"version":"2.0"`、`statement` 为数组、`effect` 为 `"allow"` 或 `"deny"` | +| `CreatePolicy` 返回 `InvalidParameter.Resource` | 检查 QCS 资源格式 | QCS 六段式格式错误(段数不对、Arn 拼写错误) | 确认格式 `qcs::tcr:$region:uin/$uin:instance/$RegistryId`,段间用 `:` 分隔 | +| `AttachUserPolicy` 返回 `FailedOperation.PolicyFull` | `tccli cam ListAttachedUserPolicies --TargetUin ` 统计数量 | 子账号绑定策略数已达上限 | 解绑不再需要的策略后重试 | +| `AttachUserPolicy` 返回 `FailedOperation.UserNotExist` | `tccli cam GetUser --Name ` 确认 | `AttachUin` 不存在 | 用 `ListUsers` 获取正确的子账号 Uin | +| `DeletePolicy` 返回 `FailedOperation.PolicyIdNotExist` | `tccli cam GetPolicy --PolicyId ` 确认存在 | PolicyId 不存在或已被删除 | 检查 `--PolicyId` 格式(必须为 `'[14089]'` 数组形式) | +| `DeletePolicy` 返回 `FailedOperation.PolicyInUse` | `tccli cam ListEntitiesForPolicy --PolicyId ` | 策略仍有子账号/用户组绑定 | 先执行 `DetachUserPolicy` 解绑所有关联实体 | +| 子账号访问 TCR 报 `UnauthorizedOperation` | 子账号执行 `tccli tcr DescribeInstances` 等操作 | 策略 Action/Resource 不匹配,或策略尚未生效 | 1) 确认 Action 包含对应操作 2) 确认 Resource QCS 匹配实例/仓库 3) 等待 1--2 分钟传播延迟 | +| `ListPolicies --Keyword tcr` 不返回 TCR 策略 | `tccli cam ListPolicies --Scope QCS` 查看全部 | `--Keyword` 区分大小写 | 使用 `--Keyword tcr`(全小写) | +| Shell 中 PolicyDocument JSON 解析失败 | — | 外部双引号被 Shell 解析 | bash/zsh 用单引号包裹:`--PolicyDocument '{...}'`;PowerShell 用 here-string `@'...'@` | + +## 下一步 + +- [企业版接入 CAM 的 API 列表](https://cloud.tencent.com/document/product/1141/41605) — 可授权的全部 TCR Action 清单 +- [个人版授权方案示例](https://cloud.tencent.com/document/product/1141/41409)(page_id `41409`) +- [访问权限管理概述](../permission-overview)(page_id `40718`) +- [服务级账号管理](../../credentials/service-credentials) — TCR 实例级服务账号(与 CAM 子账号互补) +- [创建企业版实例](../../../create)(page_id `51110`)— 实例创建后配置访问权限 + +## 控制台替代 + +[访问管理控制台 → 策略](https://console.cloud.tencent.com/cam/policy):可视化创建/编辑/删除 CAM 策略,按用户/用户组/角色筛选绑定策略。 + +[容器镜像服务 → 实例列表](https://console.cloud.tencent.com/tcr/instance):查看 TCR 实例的 RegistryId、地域、状态等信息,用于构造 QCS 资源描述。 diff --git a/src/content/docs/cli/tcr/ops/access/permissions/cam-subaccount/cam-subaccount-tcrctl.md b/src/content/docs/cli/tcr/ops/access/permissions/cam-subaccount/cam-subaccount-tcrctl.md new file mode 100644 index 0000000..818882e --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/permissions/cam-subaccount/cam-subaccount-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "基于 CAM 管理子账号权限(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[基于 CAM 管理子账号权限](https://cloud.tencent.com/document/product/1141/41417) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/access/permissions/cam-subaccount/cam-subaccount.md b/src/content/docs/cli/tcr/ops/access/permissions/cam-subaccount/cam-subaccount.md new file mode 100644 index 0000000..789ae21 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/permissions/cam-subaccount/cam-subaccount.md @@ -0,0 +1,653 @@ +--- +title: "基于 CAM 管理子账号权限(tccli)" +description: "· page_id `41417`" +--- + +> 对照官方:[基于 CAM 管理子账号权限](https://cloud.tencent.com/document/product/1141/41417) · page_id `41417` + +## 概述 + +访问管理(Cloud Access Management,CAM)是腾讯云跨产品的统一权限体系。通过 CAM 可为子账号(用户/用户组)授予 TCR 资源的最小粒度访问权限,颗粒度可达**仓库级**。 + +TCR 权限管控全部通过 `tccli cam`(非 `tccli tcr`)完成。CAM 是**账号级别**的服务——无需 `RegistryId`,无实例 tier 依赖,操作对象为策略(Policy)与子账号(User/Uin)。 + +TCR 在 CAM 中的核心概念: + +| 概念 | 说明 | 示例 | +|------|------|------| +| 预设策略(QCS) | 腾讯云预置的 TCR 权限模板,不可删除 | `QcloudTCRFullAccess`、`QcloudTCRReadOnlyAccess` | +| 自定义策略(Local) | 用户按需创建的细粒度权限策略 | 只读某个仓库、管理某个命名空间 | +| CAM Action | 可授权的 API 操作名 | `tcr:CreateInstance`、`tcr:DescribeInstances`、`tcr:DeleteInstance`、`tcr:PullRepository` | +| 资源六段式(QCS) | 授权策略中描述资源的格式 | `qcs::tcr:$region:uin/$uin:instance/$registryId` | + +**QCS 资源六段式格式**: + +``` +qcs::tcr:$region:uin/${uin}:instance/${RegistryId} +qcs::tcr:$region:uin/${uin}:repository/${RegistryId}/${Namespace}/${Repo}/* +``` + +| 段 | 含义 | 常见取值 | +|----|------|---------| +| `qcs` | QCS 协议前缀 | 固定 | +| `tcr` | 产品 ServiceType | 固定 | +| `$region` | 地域(`ap-guangzhou` 等),空值表示所有地域 | `ap-guangzhou`、`""` | +| `uin/$uin` | 主账号 UIN,空值表示创建策略的主账号 | `uin/100049208872`、`""` | +| `instance/$RegistryId` | 实例资源描述 | `instance/tcr-0e2hz15l`、`instance/*` | +| `repository/$RegistryId/$Ns/$Repo` | 仓库资源描述 | `repository/tcr-0e2hz15l/kerwinwjyan-ns/test-repo/*` | + +> 完整的 TCR CAM Action 列表参见 [企业版接入 CAM 的 API 列表](https://cloud.tencent.com/document/product/1141/41605)。常用 Action 前缀:`tcr:Create*`(写入)、`tcr:Describe*`(查询)、`tcr:Delete*`(删除)、`tcr:Modify*`(修改)、`tcr:PullRepository*`(镜像拉取)。 + +## 前置条件 + +- 已完成 [环境准备](../../index.md),安装并配置 `tccli`(默认地域 `ap-guangzhou`)。 +- 使用**主账号**或拥有以下 CAM 管理权限的子账号: + - `cam:CreatePolicy` — 创建自定义策略 + - `cam:AttachUserPolicy` — 为子账号绑定策略 + - `cam:GetPolicy` — 查看策略详情 +- 已存在待授权的子账号(Uin),可通过 `tccli cam ListUsers` 获取。 +- **此页面不依赖 TCR 实例**,无需提前创建实例或仓库。 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置 + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# cam:CreatePolicy, cam:AttachUserPolicy, cam:GetPolicy, cam:ListPolicies, cam:ListUsers +# 验证:执行 ListPolicies 和 ListUsers 确认 CAM 读权限 +tccli cam ListPolicies --Scope Local --Rp 20 --Page 1 --region ap-guangzhou --output json +# expected: exit 0,返回策略列表(可为空) + +tccli cam ListUsers --region ap-guangzhou --output json +# expected: exit 0,返回子账号列表 +``` + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 / 参数 | 幂等 | +|-----------|------------------|------| +| 查看预设策略列表 | `tccli cam ListPolicies --Scope QCS --Keyword tcr` | 是 | +| 查看策略完整 JSON | `tccli cam GetPolicy --PolicyId ` | 是 | +| 按标签搜索策略 | `tccli cam ListPolicies --Scope QCS --TagKey ` | 是 | +| 查看自定义策略 | `tccli cam ListPolicies --Scope Local` | 是 | +| 创建自定义策略(可视化) | —(控制台独有,CLI 需手写 PolicyDocument JSON) | — | +| 创建自定义策略(JSON) | `tccli cam CreatePolicy --PolicyName --PolicyDocument ''` | 否(同名报错) | +| 为子账号/用户组绑定策略 | `tccli cam AttachUserPolicy --PolicyId --AttachUin ` | 否(重复绑定幂等) | +| 为子账号/用户组解绑策略 | `tccli cam DetachUserPolicy --PolicyId --DetachUin ` | 否(重复解绑幂等) | +| 查看子账号已绑定策略 | `tccli cam ListAttachedUserPolicies --TargetUin ` | 是 | +| 删除自定义策略 | `tccli cam DeletePolicy --PolicyId '[]'` | 否 | +| 列出所有子账号 | `tccli cam ListUsers` | 是 | +| 查询子账号详情 | `tccli cam GetUser --Name ` | 是 | +| 策略生效验证 | 子账号执行 `tccli tcr DescribeInstances` 等操作 | — | + +## 操作步骤 + +### 步骤1:查看 TCR 预设策略 + +腾讯云为 TCR 提供两个预设策略(Scope=QCS),可直接绑定给子账号: + +```bash +tccli cam ListPolicies \ + --Scope QCS \ + --Keyword tcr \ + --Rp 20 \ + --Page 1 \ + --region ap-guangzhou \ + --output json +``` + +Output(仅 `ServiceType: "tcr"` 的条目): + +```json +{ + "TotalNum": 2, + "List": [ + { + "PolicyId": 30100465, + "PolicyName": "QcloudTCRFullAccess", + "AddTime": "2019-12-31 19:04:19", + "Type": 2, + "Description": "容器镜像服务(TCR)全读写权限", + "CreateMode": 2, + "Attachments": 18, + "ServiceType": "tcr", + "Deactived": 0, + "IsServiceLinkedPolicy": 0, + "AttachEntityCount": 18, + "AttachEntityBoundaryCount": 0, + "UpdateTime": "2020-09-29 11:37:49" + }, + { + "PolicyId": 30100579, + "PolicyName": "QcloudTCRReadOnlyAccess", + "AddTime": "2019-12-31 19:08:27", + "Type": 2, + "Description": "容器镜像服务(TCR)只读权限", + "CreateMode": 2, + "Attachments": 6, + "ServiceType": "tcr", + "Deactived": 0, + "IsServiceLinkedPolicy": 0, + "AttachEntityCount": 6, + "AttachEntityBoundaryCount": 0, + "UpdateTime": "2020-09-29 11:37:49" + } + ], + "RequestId": "07831f36-9765-498a-a72f-4aca037c343d" +} +``` + +| 策略名 | PolicyId | 说明 | Attachments | +|--------|----------|------|-------------| +| `QcloudTCRFullAccess` | 30100465 | TCR 全读写权限 | 18 | +| `QcloudTCRReadOnlyAccess` | 30100579 | TCR 只读权限 | 6 | + +> `--Keyword tcr` 返回所有 ServiceType 含 "tcr" 的策略(含其他产品的 TCR 关联策略)。仅 `ServiceType: "tcr"` 的条目为 TCR 专属预设策略。若只需 TCR 相关而不限定 ServiceType,去掉 `--Keyword` 过滤可获得完整预设策略列表。 + +### 步骤2:查看策略完整 JSON(PolicyDocument) + +```bash +tccli cam GetPolicy \ + --PolicyId 30100465 \ + --region ap-guangzhou \ + --output json +``` + +Output(`QcloudTCRFullAccess`): + +```json +{ + "PolicyName": "QcloudTCRFullAccess", + "Description": "容器镜像服务(TCR)全读写权限", + "Type": 2, + "AddTime": "2019-12-31 19:04:19", + "UpdateTime": "2020-09-29 11:37:49", + "PolicyDocument": "{\"statement\":[{\"action\":[\"tcr:*\"],\"effect\":\"allow\",\"resource\":\"*\"},{\"action\":[\"cam:GetRole\"],\"effect\":\"allow\",\"resource\":[\"*\"]}],\"version\":\"2.0\"}", + "PresetAlias": "", + "IsServiceLinkedRolePolicy": 0, + "RequestId": "eb493bdd-f809-44dc-aa67-1dec1ae45eff" +} +``` + +格式化后的 PolicyDocument: + +```json +{ + "version": "2.0", + "statement": [ + { + "action": ["tcr:*"], + "effect": "allow", + "resource": "*" + }, + { + "action": ["cam:GetRole"], + "effect": "allow", + "resource": ["*"] + } + ] +} +``` + +**`QcloudTCRReadOnlyAccess`** 的 PolicyDocument: + +```bash +tccli cam GetPolicy --PolicyId 30100579 --region ap-guangzhou --output json +``` + +```json +{ + "PolicyName": "QcloudTCRReadOnlyAccess", + "Description": "容器镜像服务(TCR)只读权限", + "Type": 2, + "AddTime": "2019-12-31 19:08:27", + "UpdateTime": "2020-09-29 11:37:49", + "PolicyDocument": "{\"statement\":[{\"action\":[\"tcr:Describe*\",\"tcr:PullRepository*\"],\"effect\":\"allow\",\"resource\":\"*\"},{\"action\":[\"cam:GetRole\"],\"effect\":\"allow\",\"resource\":[\"*\"]}],\"version\":\"2.0\"}", + "PresetAlias": "", + "IsServiceLinkedRolePolicy": 0, + "RequestId": "753872d4-4943-4b17-bbde-188c7a53bbb9" +} +``` + +格式化 PolicyDocument: + +```json +{ + "version": "2.0", + "statement": [ + { + "action": ["tcr:Describe*", "tcr:PullRepository*"], + "effect": "allow", + "resource": "*" + }, + { + "action": ["cam:GetRole"], + "effect": "allow", + "resource": ["*"] + } + ] +} +``` + +> **注意**:两个预设策略均包含 `cam:GetRole` 声明(Statement 第 2 条)。这是 TCR 内部所需的服务角色查询权限,自定义策略时通常不需要添加此项。 + +**两个预设策略对照**: + +| 维度 | QcloudTCRFullAccess | QcloudTCRReadOnlyAccess | +|------|--------------------|------------------------| +| TCR Actions | `tcr:*` | `tcr:Describe*` + `tcr:PullRepository*` | +| 资源范围 | `*`(全部) | `*`(全部) | +| 额外权限 | `cam:GetRole` | `cam:GetRole` | +| 适用场景 | 运维/管理员 | 开发/只读用户 | +| 能否删除 | 否 | 否 | + +> 判断标准:若预设策略满足需求,直接绑定(跳至[步骤5](#步骤5为子账号绑定策略));若需更细颗粒度(如仅授权某个仓库、某个命名空间、或限制地域),继续[步骤3](#步骤3获取子账号-uin)——[步骤4](#步骤4创建自定义策略)。 + +### 步骤3:获取子账号 Uin + +列出所有子账号: + +```bash +tccli cam ListUsers --region ap-guangzhou --output json +``` + +Output: + +```json +{ + "Data": [ + { + "Uin": 100003627333, + "Name": "foxzhong", + "Uid": 1194262, + "NickName": "foxzhong" + }, + { + "Uin": 100006594350, + "Name": "caryguo", + "Uid": 1542771, + "NickName": "caryguo" + } + ], + "RequestId": "41cc9a1b-ec77-428f-a9e5-1024a88ce355" +} +``` + +按名称精确查找: + +```bash +tccli cam GetUser --Name kerwinwjyan --region ap-guangzhou --output json +``` + +Output: + +```json +{ + "Uin": 100049208872, + "Name": "kerwinwjyan", + "Uid": 25414444, + "Remark": "kerwinwjyan-tag管控用户", + "ConsoleLogin": 1, + "CountryCode": "86", + "RecentlyLoginTime": "2026-06-09 16:51:03", + "RequestId": "91f2f52c-b760-4077-93b9-b818d825c4a9" +} +``` + +> 记录目标子账号的 `Uin`(数字),后续绑定/解绑策略时需使用。 + +### 步骤4:创建自定义策略 + +当预设策略不满足精细化需求时,创建自定义策略(Scope=Local)。核心是构造 `PolicyDocument` JSON——包含 `version` 和 `statement` 数组。 + +**PolicyDocument 结构**: + +```json +{ + "version": "2.0", + "statement": [ + { + "action": ["tcr:"], + "resource": ["qcs::tcr::uin/:/"], + "effect": "allow" + } + ] +} +``` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `version` | String | 固定 `"2.0"` | +| `statement` | Array | 声明数组,每条声明定义一组权限 | +| `statement[].action` | Array of String | CAM Action 名,格式 `tcr:`,支持 `*` 通配(如 `tcr:Describe*`) | +| `statement[].resource` | Array of String | QCS 六段式资源描述,支持 `*` 通配 | +| `statement[].effect` | String | `"allow"` 或 `"deny"` | + +#### 4a. 常用 CAM Action 速查 + +| 权限层级 | Action 示例 | 适用场景 | +|----------|------------|---------| +| 全读写 | `tcr:*` | 管理员 | +| 全部只读 | `tcr:Describe*`、`tcr:PullRepository*` | 开发/审计 | +| 实例管理 | `tcr:CreateInstance`、`tcr:DeleteInstance`、`tcr:ModifyInstance` | 实例生命周期 | +| 实例查询 | `tcr:DescribeInstances`、`tcr:DescribeInstanceStatus` | 查看实例信息 | +| 命名空间管理 | `tcr:CreateNamespace`、`tcr:DeleteNamespace` | 命名空间 CRUD | +| 仓库管理 | `tcr:CreateRepository`、`tcr:DeleteRepository`、`tcr:DescribeRepositories` | 仓库 CRUD | +| 镜像操作 | `tcr:PullRepository`、`tcr:PushRepository` | 镜像推拉 | +| 访问控制 | `tcr:ManageInternalEndpoint`、`tcr:ManageExternalEndpoint` | 网络策略 | +| Token 管理 | `tcr:CreateInstanceToken`、`tcr:DeleteInstanceToken` | 长期凭证 | +| 复制实例 | `tcr:CreateReplicationInstance`、`tcr:DescribeReplicationInstances` | 实例同步 | + +> 完整列表参见 [企业版接入 CAM 的 API 列表](https://cloud.tencent.com/document/product/1141/41605)。 + +#### 4b. 策略模板一:授权子账号管理指定实例 + +```bash +tccli cam CreatePolicy \ + --PolicyName "TCR-Manage-tcr-0e2hz15l" \ + --PolicyDocument '{"version":"2.0","statement":[{"action":["tcr:*"],"resource":["qcs::tcr:ap-guangzhou::instance/tcr-0e2hz15l","qcs::tcr:ap-guangzhou::repository/tcr-0e2hz15l/*"],"effect":"allow"}]}' \ + --Description "全读写 TCR 实例 tcr-0e2hz15l" \ + --region ap-guangzhou \ + --output json +``` + +Output: + +```json +{ + "PolicyId": 14089, + "RequestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" +} +``` + +> `resource` 中 `uin` 段留空表示创建策略的主账号。 + +#### 4c. 策略模板二:授权子账号只读某个仓库 + +```bash +tccli cam CreatePolicy \ + --PolicyName "TCR-ReadOnly-repo-demo" \ + --PolicyDocument '{"version":"2.0","statement":[{"action":["tcr:DescribeRepositories","tcr:PullRepository"],"resource":["qcs::tcr:ap-guangzhou::repository/tcr-0e2hz15l/kerwinwjyan-ns/test-repo","qcs::tcr:ap-guangzhou::repository/tcr-0e2hz15l/kerwinwjyan-ns/test-repo/*"],"effect":"allow"},{"action":["tcr:DescribeInstances","tcr:DescribeInstanceStatus","tcr:DescribeNamespaces"],"resource":["qcs::tcr:ap-guangzhou::instance/tcr-0e2hz15l","qcs::tcr:ap-guangzhou::repository/tcr-0e2hz15l/kerwinwjyan-ns"],"effect":"allow"}]}' \ + --Description "只读 kerwinwjyan-ns/test-repo 仓库" \ + --region ap-guangzhou \ + --output json +``` + +**PolicyDocument 格式化**: + +```json +{ + "version": "2.0", + "statement": [ + { + "action": ["tcr:DescribeRepositories", "tcr:PullRepository"], + "resource": [ + "qcs::tcr:ap-guangzhou::repository/tcr-0e2hz15l/kerwinwjyan-ns/test-repo", + "qcs::tcr:ap-guangzhou::repository/tcr-0e2hz15l/kerwinwjyan-ns/test-repo/*" + ], + "effect": "allow" + }, + { + "action": ["tcr:DescribeInstances", "tcr:DescribeInstanceStatus", "tcr:DescribeNamespaces"], + "resource": [ + "qcs::tcr:ap-guangzhou::instance/tcr-0e2hz15l", + "qcs::tcr:ap-guangzhou::repository/tcr-0e2hz15l/kerwinwjyan-ns" + ], + "effect": "allow" + } + ] +} +``` + +> **设计要点**:只读仓库场景需两个 Statement——第一条授予仓库/镜像的读权限;第二条授予实例和命名空间的查询权限(否则子账号进入控制台或 CLI 无法看到实例列表)。 + +#### 4d. 策略模板三:授权子账号管理指定命名空间 + +```bash +tccli cam CreatePolicy \ + --PolicyName "TCR-Manage-ns-kerwinwjyan-ns" \ + --PolicyDocument '{"version":"2.0","statement":[{"action":["tcr:*"],"resource":["qcs::tcr:ap-guangzhou::repository/tcr-0e2hz15l/kerwinwjyan-ns","qcs::tcr:ap-guangzhou::repository/tcr-0e2hz15l/kerwinwjyan-ns/*"],"effect":"allow"},{"action":["tcr:DescribeInstances","tcr:DescribeInstanceStatus"],"resource":["qcs::tcr:ap-guangzhou::instance/tcr-0e2hz15l"],"effect":"allow"}]}' \ + --Description "管理 kerwinwjyan-ns 命名空间下的全部仓库" \ + --region ap-guangzhou \ + --output json +``` + +> `repository///*` 覆盖命名空间下所有仓库及子路径(tag/artifact)。 + +#### 4e. Shell 中 PolicyDocument 传入技巧 + +| Shell | 方式 | 示例 | +|-------|------|------| +| bash/zsh | 单引号包裹整个 JSON,内部用双引号 | `--PolicyDocument '{"version":"2.0",...}'` | +| bash/zsh(长 JSON) | `--cli-input-json file://policy.json` | 见下方 | +| PowerShell | here-string `@'...'@` | `--PolicyDocument @'{"version":"2.0",...}'@` | +| PowerShell(文件) | `--cli-input-json file://policy.json` | 同 bash | + +文件模式(推荐复杂策略): + +```bash +# 将 PolicyDocument 写入文件 +cat > /tmp/tcr-readonly-repo.json << 'EOF' +{ + "version": "2.0", + "statement": [ + { + "action": ["tcr:DescribeRepositories", "tcr:PullRepository"], + "resource": [ + "qcs::tcr:ap-guangzhou::repository/tcr-0e2hz15l/kerwinwjyan-ns/test-repo", + "qcs::tcr:ap-guangzhou::repository/tcr-0e2hz15l/kerwinwjyan-ns/test-repo/*" + ], + "effect": "allow" + }, + { + "action": ["tcr:DescribeInstances", "tcr:DescribeInstanceStatus", "tcr:DescribeNamespaces"], + "resource": [ + "qcs::tcr:ap-guangzhou::instance/tcr-0e2hz15l", + "qcs::tcr:ap-guangzhou::repository/tcr-0e2hz15l/kerwinwjyan-ns" + ], + "effect": "allow" + } + ] +} +EOF + +tccli cam CreatePolicy \ + --PolicyName "TCR-ReadOnly-repo-demo" \ + --Description "只读 kerwinwjyan-ns/test-repo 仓库" \ + --cli-input-json file:///tmp/tcr-readonly-repo.json \ + --region ap-guangzhou \ + --output json +``` + +### 步骤5:为子账号绑定策略 + +将策略(预设或自定义)绑定到目标子账号: + +```bash +tccli cam AttachUserPolicy \ + --PolicyId \ + --AttachUin <子账号Uin> \ + --region ap-guangzhou \ + --output json +``` + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `PolicyId` | Integer | **是** | 策略 ID(从 `ListPolicies` 或 `CreatePolicy` 获取) | +| `AttachUin` | Integer | **是** | 子账号 Uin(从 `GetUser` / `ListUsers` 获取) | + +Output: + +```json +{ + "RequestId": "b2c3d4e5-f6a7-8901-bcde-f12345678901" +} +``` + +> **幂等**:同一策略重复绑定同一子账号不会报错,返回有效 `RequestId`。 + +### 步骤6:验证绑定结果 + +```bash +tccli cam ListAttachedUserPolicies \ + --TargetUin 100049208872 \ + --Rp 20 \ + --Page 1 \ + --region ap-guangzhou \ + --output json +``` + +Output: + +```json +{ + "TotalNum": 2, + "List": [ + { + "PolicyId": 30100465, + "PolicyName": "QcloudTCRFullAccess", + "AddTime": "2025-06-15 16:53:22", + "CreateMode": 2 + }, + { + "PolicyId": 14089, + "PolicyName": "TCR-Manage-tcr-0e2hz15l", + "AddTime": "2026-06-16 10:30:15", + "CreateMode": 1 + } + ], + "RequestId": "c3d4e5f6-a7b8-9012-cdef-123456789012" +} +``` + +> `CreateMode`:`1` = 自定义策略,`2` = 预设策略(QCS)。 + +### 步骤7:解绑策略 + +```bash +tccli cam DetachUserPolicy \ + --PolicyId \ + --DetachUin <子账号Uin> \ + --region ap-guangzhou \ + --output json +``` + +Output: + +```json +{ + "RequestId": "d4e5f6a7-b8c9-0123-defa-234567890123" +} +``` + +### 步骤8:删除自定义策略 + +```bash +tccli cam DeletePolicy \ + --PolicyId '[]' \ + --region ap-guangzhou \ + --output json +``` + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `PolicyId` | Array of Integer | **是** | 策略 ID 数组,格式 `'[14089]'`。**预设策略不可删除** | + +Output: + +```json +{ + "RequestId": "e5f6a7b8-c9d0-1234-efab-345678901234" +} +``` + +> **注意**:`--PolicyId` 在 `DeletePolicy` 中是 **数组类型**,即使删除单个策略也需传入 `'[14089]'`(而非 `14089`)。 + +## 验证 + +### Control plane (tccli) + +| 验证项 | 命令 | 期望结果 | +|--------|------|---------| +| 预设策略存在 | `tccli cam ListPolicies --Scope QCS --Keyword tcr` | `TotalNum >= 2`,含 `QcloudTCRFullAccess`、`QcloudTCRReadOnlyAccess` | +| 策略详情可查 | `tccli cam GetPolicy --PolicyId ` | 返回 `PolicyName` + 有效 `PolicyDocument` JSON | +| 自定义策略已创建 | `tccli cam ListPolicies --Scope Local` | 列表中含新创建的策略名 | +| 绑定成功 | `tccli cam ListAttachedUserPolicies --TargetUin ` | 列表含目标 `PolicyId` | +| 解绑成功 | 同上(解绑后) | 列表中不含目标 `PolicyId` | +| 策略已删除 | `tccli cam ListPolicies --Scope Local` | 列表中不含已删除的策略 | + +### Data plane(权限生效验证) + +子账号绑定策略后,用该子账号的密钥执行 TCR 操作验证权限生效: + +| 验证项 | 命令(子账号执行) | 期望结果 | +|--------|-------------------|---------| +| 只读用户可 Describe | `tccli tcr DescribeInstances --AllRegion true --region ap-guangzhou --output json` | 返回实例列表 | +| 只读用户不可 Delete | `tccli tcr DeleteInstance --RegistryId tcr-0e2hz15l` | `UnauthorizedOperation` 错误 | +| 仓库只读可 Pull | `docker pull kerwinwjyan-rewrite-001.tencentcloudcr.com/kerwinwjyan-ns/test-repo:latest` | 拉取成功 | +| 仓库只读不可 Push | `docker push kerwinwjyan-rewrite-001.tencentcloudcr.com/kerwinwjyan-ns/test-repo:latest` | `denied` 错误 | +| 命名空间管理者可创建仓库 | `tccli tcr CreateRepository --RegistryId tcr-0e2hz15l --Namespace kerwinwjyan-ns --Repository test-repo-2` | 创建成功 | + +> **注意**:CAM 策略变更有 1--2 分钟传播延迟。若刚绑定策略后子账号仍报权限拒绝,等待 1--2 分钟后重试。 + +## 清理 + +### 解绑策略 + +```bash +tccli cam DetachUserPolicy \ + --PolicyId \ + --DetachUin <子账号Uin> \ + --region ap-guangzhou \ + --output json +``` + +### 删除自定义策略 + +```bash +tccli cam DeletePolicy \ + --PolicyId '[]' \ + --region ap-guangzhou \ + --output json +``` + +> **不可逆提醒**: +> - 删除策略将立即影响所有已绑定该策略的子账号权限。 +> - 预设策略(`QcloudTCRFullAccess` / `QcloudTCRReadOnlyAccess`)不可删除,`DeletePolicy` 将返回 `FailedOperation`。 +> - 若策略仍有绑定关系,`DeletePolicy` 将失败——需先执行 `DetachUserPolicy` 解绑所有关联子账号后重试。 + +## 排障 + +| 现象 | 诊断命令 | 根因 | 修复 | +|------|---------|------|------| +| `CreatePolicy` 返回 `InvalidParameter.PolicyName` | — | 策略名重复(同一主账号下策略名必须唯一) | 更换 `PolicyName` | +| `CreatePolicy` 返回 `InvalidParameter.PolicyDocument` | 用 `python3 -m json.tool` 或 `jq` 校验 JSON | PolicyDocument JSON 格式错误(引号、逗号、数组结构) | 确保 `"version":"2.0"`、`statement` 为数组、`effect` 为 `"allow"` 或 `"deny"` | +| `CreatePolicy` 返回 `InvalidParameter.Resource` | 检查 QCS 资源格式 | QCS 六段式格式错误(段数不对、Arn 拼写错误) | 确认格式 `qcs::tcr:$region:uin/$uin:instance/$RegistryId`,段间用 `:` 分隔 | +| `AttachUserPolicy` 返回 `FailedOperation.PolicyFull` | `tccli cam ListAttachedUserPolicies --TargetUin ` 统计数量 | 子账号绑定策略数已达上限 | 解绑不再需要的策略后重试 | +| `AttachUserPolicy` 返回 `FailedOperation.UserNotExist` | `tccli cam GetUser --Name ` 确认 | `AttachUin` 不存在 | 用 `ListUsers` 获取正确的子账号 Uin | +| `DeletePolicy` 返回 `FailedOperation.PolicyIdNotExist` | `tccli cam GetPolicy --PolicyId ` 确认存在 | PolicyId 不存在或已被删除 | 检查 `--PolicyId` 格式(必须为 `'[14089]'` 数组形式) | +| `DeletePolicy` 返回 `FailedOperation.PolicyInUse` | `tccli cam ListEntitiesForPolicy --PolicyId ` | 策略仍有子账号/用户组绑定 | 先执行 `DetachUserPolicy` 解绑所有关联实体 | +| 子账号访问 TCR 报 `UnauthorizedOperation` | 子账号执行 `tccli tcr DescribeInstances` 等操作 | 策略 Action/Resource 不匹配,或策略尚未生效 | 1) 确认 Action 包含对应操作 2) 确认 Resource QCS 匹配实例/仓库 3) 等待 1--2 分钟传播延迟 | +| `ListPolicies --Keyword tcr` 不返回 TCR 策略 | `tccli cam ListPolicies --Scope QCS` 查看全部 | `--Keyword` 区分大小写 | 使用 `--Keyword tcr`(全小写) | +| Shell 中 PolicyDocument JSON 解析失败 | — | 外部双引号被 Shell 解析 | bash/zsh 用单引号包裹:`--PolicyDocument '{...}'`;PowerShell 用 here-string `@'...'@` | + +## 下一步 + +- [企业版接入 CAM 的 API 列表](https://cloud.tencent.com/document/product/1141/41605) — 可授权的全部 TCR Action 清单 +- [个人版授权方案示例](https://cloud.tencent.com/document/product/1141/41409)(page_id `41409`) +- [访问权限管理概述](../permission-overview)(page_id `40718`) +- [服务级账号管理](../../credentials/service-credentials) — TCR 实例级服务账号(与 CAM 子账号互补) +- [创建企业版实例](../../../create)(page_id `51110`)— 实例创建后配置访问权限 + +## 控制台替代 + +[访问管理控制台 → 策略](https://console.cloud.tencent.com/cam/policy):可视化创建/编辑/删除 CAM 策略,按用户/用户组/角色筛选绑定策略。 + +[容器镜像服务 → 实例列表](https://console.cloud.tencent.com/tcr/instance):查看 TCR 实例的 RegistryId、地域、状态等信息,用于构造 QCS 资源描述。 diff --git a/src/content/docs/cli/tcr/ops/access/permissions/permission-overview.md b/src/content/docs/cli/tcr/ops/access/permissions/permission-overview.md new file mode 100644 index 0000000..7053dc8 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/permissions/permission-overview.md @@ -0,0 +1,232 @@ +--- +title: "访问权限管理概述" +description: "· page_id `40718`" +--- + +> 对照官方:[访问权限管理概述](https://cloud.tencent.com/document/product/1141/40718) · page_id `40718` + +## 概述 + +TCR 企业版提供两种独立的访问权限管理模式,使实例管理员能够为研发/运维人员和内部 CI/CD 自动化系统分别分配独立的访问凭证,并精细化管理人员权限,保障镜像数据安全。 + +两种模式的设计出发点对应两个核心场景: + +| 场景 | 需求 | 对应模式 | +|------|------|---------| +| **人员权限控制** | 多团队共用实例,不同角色(研发/运维/测试)需最小权限隔离,防止误删镜像或镜像泄露;需审计追溯 | 用户级账号(基于 CAM) | +| **系统权限控制** | CI/CD 流水线、Kubernetes 集群等自动化系统需独立于具体人员的凭证,不受人员离职/调岗影响 | 服务级账号(实例级) | + +两种模式可组合使用:人员使用用户级账号完成日常 `docker login`/`docker push`/`docker pull`,CI/CD 系统使用服务级账号完成自动化部署,各自独立管理、互不干扰。 + +--- + +### 用户级账号(基于 CAM) + +用户级账号**直接关联腾讯云子账号**,权限管理基于[访问管理 CAM](https://cloud.tencent.com/document/product/598)。 + +**工作流程**: + +1. 主账号在 [CAM 控制台](https://console.cloud.tencent.com/cam) 为指定人员创建子账号 +2. 通过 CAM 策略(如 `QcloudTCRFullAccess` 或自定义资源级策略)授予子账号相应权限 +3. 子账号登录 TCR 控制台,进入 **访问凭证 -> 用户级账号**,创建专属长期访问凭证(`CreateInstanceToken`) +4. 使用凭证进行 `docker login` 操作,实际权限受 CAM 策略约束 + +**生命周期约束**: +- 凭证与子账号绑定,子账号被禁用或删除后,关联的访问凭证**立即失效** +- 因此**不应**在 CI/CD 等自动化系统中配置子账号关联的访问凭证——人员离职或子账号变更会导致自动化流程中断 + +**权限粒度**:CAM 支持资源级授权,可按实例(`instance`)和仓库(`repository`)两个维度授予最小权限策略。详见 [基于 CAM 管理子账号权限](../基于cam管理子账号权限)。 + +**CLI 视角**:用户级账号的凭证管理通过 `tccli tcr` 子命令操作(`CreateInstanceToken`、`DescribeInstanceToken`、`DeleteInstanceToken` 等),权限授权则通过 `tccli cam` 子命令操作。两套 CLI 命令集分别对应"凭证"和"权限"两个独立层面。 + +--- + +### 服务级账号(实例级) + +服务级账号**不与腾讯云子账号关联**,属于实例级资源,权限管理独立于 CAM。 + +**工作机制**: +- 在实例内通过 `tccli tcr CreateServiceAccount` 创建服务账号,指定其可访问的**命名空间**和**操作权限**(如 `tcr:PushRepository`、`tcr:PullRepository`) +- 权限使用 TCR 自定义 Action 名格式(带 `tcr:` 前缀),存储于实例内部 +- 通过 `tccli tcr DescribeServiceAccounts` 查询列表 + +**适用的自动化场景**: +- CI/CD 流水线(如 Jenkins、GitLab CI、腾讯云 CODING DevOps) +- Kubernetes 集群(通过 ImagePullSecret 挂载服务账号凭证拉取镜像) + +**权限隔离**:服务级账号仅能操作被授予的命名空间,无法跨命名空间访问。权限由 `Permissions` 数组定义,每条权限记录指定 `Resource`(命名空间名)和 `Actions`(TCR 自定义 Action 列表)。 + +**安全注意事项**: +- 任何具有服务级账号 API 权限的子账号均可查询/管理服务级账号(因为权限模型独立于 CAM) +- 存在**权限放大风险**:主账号在授予子账号 `tcr:CreateServiceAccount` 等 API 权限时需严格控制授权范围 +- 服务级账号的访问凭证与人员无关,人员离职不影响其有效性——这既是优点(系统连续性),也是需管控的点(凭证泄露后影响面可能更大) + +**CLI 视角**:服务级账号全程通过 `tccli tcr` 子命令操作,无需交叉使用 `tccli cam`。`Permissions` 参数的 `Actions` 字段使用 TCR 自定义 Action 名(`tcr:PushRepository`、`tcr:PullRepository` 等)。 + +--- + +### 两种模式对比 + +| 维度 | 用户级账号 | 服务级账号 | +|------|-----------|-----------| +| 关联对象 | 腾讯云子账号 | 无云账号关联,实例级资源 | +| 权限系统 | CAM(`tccli cam`) | TCR 自定义权限模型(`tccli tcr`) | +| 适用场景 | 人员手动操作 | CI/CD、K8s 等自动化系统 | +| 凭证可见性 | 仅关联子账号可见/管理 | 有 API 权限的子账号均可管理 | +| 人员变更影响 | 子账号禁用/删除 -> 凭证失效 | 无影响 | +| 权限粒度 | CAM 策略(实例级/仓库级) | 命名空间级(push/pull/create) | +| 审计追溯 | CAM 操作日志 | 实例内部记录 | +| 权限放大风险 | 低(CAM 统一治理) | 需关注(独立权限模型,子账号可能越权管理服务账号) | + +## 前置条件 + +- [环境准备](../../index.md):`tccli` 已安装并配置凭证和地域 +- 已成功 [创建企业版实例](../../../create)(`basic`/`standard`/`premium`),实例状态为 `Running` +- 理解腾讯云 [访问管理 CAM](https://cloud.tencent.com/document/product/598) 的基本概念(子账号、策略、授权) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置 + +# 3. 查询实例列表验证基础连通性 +tccli tcr DescribeInstances \ + --region '' \ + --output json +# expected: exit 0,返回实例列表 +``` + +## 控制台与 CLI 参数映射 + +本文为概念概述页。以下列出两种模式的 tccli 命令入口,具体参数和操作步骤见对应子页面。 + +### 用户级账号 — CLI 命令入口 + +| 控制台操作 | tccli 命令 | 幂等 | 页面 | +|-----------|----------|:--:|------| +| 创建长期访问凭证 | `CreateInstanceToken` | 否 | [用户级账号管理](../../credentials/user-credentials) | +| 查看凭证列表 | `DescribeInstanceToken` | 是 | 同上 | +| 启用/禁用凭证 | `ModifyInstanceToken` | 是 | 同上 | +| 删除凭证 | `DeleteInstanceToken` | 否 | 同上 | +| CAM 策略授权 | `tccli cam` 系列 | — | [基于 CAM 管理子账号权限](../基于cam管理子账号权限) | + +### 服务级账号 — CLI 命令入口 + +| 控制台操作 | tccli 命令 | 幂等 | 页面 | +|-----------|----------|:--:|------| +| 创建服务级账号 | `CreateServiceAccount` | 否 | [服务级账号管理](../../credentials/service-credentials) | +| 查看服务级账号列表 | `DescribeServiceAccounts` | 是 | 同上 | +| 修改服务级账号权限 | `ModifyServiceAccount` | 否 | 同上 | +| 删除服务级账号 | `DeleteServiceAccount` | 否 | 同上 | + +## 操作步骤 + +本文为概念概述页,无独立可执行的 tccli 命令操作。以下为 CLI 环境下快速确认当前实例状态的基础命令。 + +### 查询实例信息 + +查看当前账号下的所有 TCR 企业版实例,确认实例状态、类型和域名: + +```bash +tccli tcr DescribeInstances \ + --region '' \ + --output json +# expected: exit 0,返回 Registries 列表 +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "basic", + "Status": "Running", + "PublicDomain": "tcr-example.tencentcloudcr.com", + "CreatedAt": "2026-06-01T10:00:00+08:00", + "RegionName": "ap-guangzhou", + "EnableAnonymous": false, + "TokenValidTime": 87600 + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +如需查看所有地域的实例: + +```bash +tccli tcr DescribeInstances \ + --AllRegion true \ + --output json +# expected: exit 0,返回所有地域的实例 +``` + +> 具体操作请进入以下子页面: +> - [用户级账号管理](../../credentials/user-credentials) — 创建与管理长期访问凭证(`CreateInstanceToken` / `DescribeInstanceToken` / `DeleteInstanceToken`) +> - [服务级账号管理](../../credentials/service-credentials) — 为 CI/CD 系统创建服务账号(`CreateServiceAccount` / `DescribeServiceAccounts` / `ModifyServiceAccount` / `DeleteServiceAccount`) +> - [基于 CAM 管理子账号权限](../基于cam管理子账号权限) — 通过 CAM 策略控制子账号对 TCR 资源的访问(page_id `41417`) + +## 验证 + +### 控制面(tccli) + +| # | 验证项 | 命令 | 期望结果 | +|---|--------|------|---------| +| 1 | 实例可查询 | `tccli tcr DescribeInstances --region '' --output json` | `TotalCount >= 1`,含实例信息 | +| 2 | 实例状态正常 | 同上,检查 `Registries[].Status` | `Status` 为 `"Running"` | +| 3 | 两种模式功能入口可访问 | 分别执行 `DescribeInstanceToken` 和 `DescribeServiceAccounts` | 均返回 exit 0(列表可为空) | + +### 概念关系验证(交叉引用) + +| 验证关系 | 验证方式 | 期望结论 | +|----------|---------|---------| +| 用户级账号 -> CAM 绑定 | 子账号创建 Token 后主账号在 CAM 删除该子账号,用该凭证 `docker login` | 凭证立即失效,登录失败 | +| 服务级账号 -> CAM 独立 | 子账号 A 创建服务级账号,子账号 B(有 `tcr:DescribeServiceAccounts` 权限)查询 | 子账号 B 可见子账号 A 创建的服务级账号(实例级资源,非 CAM 隔离) | +| 两种模式独立共存 | 同一实例同时创建 1 个用户级凭证 + 1 个服务级凭证,分别 `docker push` | 两份凭证各自独立工作,互不影响 | + +## 清理 + +不适用。本文为概念概述页,不涉及资源创建与清理。 + +## 排障 + +### 查询错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `DescribeInstances` 返回 `UnauthorizedOperation` | — | 子账号缺少 `tcr:DescribeInstances` 权限 | 联系主账号授予 `QcloudTCRReadOnlyAccess` 或添加 `tcr:DescribeInstances` 到自定义策略 | +| `DescribeInstances` 返回空列表 | `tccli tcr DescribeInstances --AllRegion true --output json` | 当前地域无实例,或 `--region` 参数与实际实例地域不一致 | 使用 `--AllRegion true` 查看所有地域实例 | + +### 模式选择常见困惑 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 不确定该用哪种模式 | — | 场景不明确 | 人员手动操作(开发/运维) -> 用户级账号;CI/CD 流水线/K8s 自动化 -> 服务级账号 | +| 服务级账号创建失败:`custom account's permission can not be empty` | — | `Permissions` 参数为空数组 `[]` | 至少传入一条权限记录,含 `Resource` 和 `Actions` | +| 服务级账号创建失败:`not support action: ` | — | `Actions` 使用了不支持的 Action 名 | 使用支持的动作:`tcr:PushRepository`、`tcr:PullRepository`、`tcr:CreateRepository` 等 | +| 用户级账号创建凭证后 `docker login` 失败 | `tccli tcr DescribeInstanceToken --RegistryId '' --region ''` 确认凭证状态 | 子账号被禁用/删除,或凭证已过期 | 确认 CAM 子账号状态正常;凭证过期需重新 `CreateInstanceToken` | + +## 下一步 + +- [用户级账号管理](../../credentials/user-credentials) — 创建与管理长期访问凭证 +- [服务级账号管理](../../credentials/service-credentials) — 为 CI/CD 系统创建服务级账号 +- [基于 CAM 管理子账号权限](../基于cam管理子账号权限) — 通过 CAM 策略控制子账号对 TCR 资源的访问 +- [访问网络控制概述](../../network/network-overview) — 理解公网/内网访问网络层面的安全控制 +- [创建企业版实例](../../../create) — 创建实例后配置访问权限 + +## 控制台替代 + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) -> 选择目标企业版实例 -> 进入 **访问控制** 页面: +- **用户级账号**:切换至 **访问凭证** 标签页,由子账号自行创建和管理 +- **服务级账号**:切换至 **服务级账号** 标签页,由实例管理员创建、授权、分发凭证至 CI/CD 系统 +- **CAM 策略授权**:跳转至 [CAM 控制台](https://console.cloud.tencent.com/cam) 进行子账号策略管理 diff --git a/src/content/docs/cli/tcr/ops/access/permissions/permission-overview/permission-overview.md b/src/content/docs/cli/tcr/ops/access/permissions/permission-overview/permission-overview.md new file mode 100644 index 0000000..7053dc8 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/permissions/permission-overview/permission-overview.md @@ -0,0 +1,232 @@ +--- +title: "访问权限管理概述" +description: "· page_id `40718`" +--- + +> 对照官方:[访问权限管理概述](https://cloud.tencent.com/document/product/1141/40718) · page_id `40718` + +## 概述 + +TCR 企业版提供两种独立的访问权限管理模式,使实例管理员能够为研发/运维人员和内部 CI/CD 自动化系统分别分配独立的访问凭证,并精细化管理人员权限,保障镜像数据安全。 + +两种模式的设计出发点对应两个核心场景: + +| 场景 | 需求 | 对应模式 | +|------|------|---------| +| **人员权限控制** | 多团队共用实例,不同角色(研发/运维/测试)需最小权限隔离,防止误删镜像或镜像泄露;需审计追溯 | 用户级账号(基于 CAM) | +| **系统权限控制** | CI/CD 流水线、Kubernetes 集群等自动化系统需独立于具体人员的凭证,不受人员离职/调岗影响 | 服务级账号(实例级) | + +两种模式可组合使用:人员使用用户级账号完成日常 `docker login`/`docker push`/`docker pull`,CI/CD 系统使用服务级账号完成自动化部署,各自独立管理、互不干扰。 + +--- + +### 用户级账号(基于 CAM) + +用户级账号**直接关联腾讯云子账号**,权限管理基于[访问管理 CAM](https://cloud.tencent.com/document/product/598)。 + +**工作流程**: + +1. 主账号在 [CAM 控制台](https://console.cloud.tencent.com/cam) 为指定人员创建子账号 +2. 通过 CAM 策略(如 `QcloudTCRFullAccess` 或自定义资源级策略)授予子账号相应权限 +3. 子账号登录 TCR 控制台,进入 **访问凭证 -> 用户级账号**,创建专属长期访问凭证(`CreateInstanceToken`) +4. 使用凭证进行 `docker login` 操作,实际权限受 CAM 策略约束 + +**生命周期约束**: +- 凭证与子账号绑定,子账号被禁用或删除后,关联的访问凭证**立即失效** +- 因此**不应**在 CI/CD 等自动化系统中配置子账号关联的访问凭证——人员离职或子账号变更会导致自动化流程中断 + +**权限粒度**:CAM 支持资源级授权,可按实例(`instance`)和仓库(`repository`)两个维度授予最小权限策略。详见 [基于 CAM 管理子账号权限](../基于cam管理子账号权限)。 + +**CLI 视角**:用户级账号的凭证管理通过 `tccli tcr` 子命令操作(`CreateInstanceToken`、`DescribeInstanceToken`、`DeleteInstanceToken` 等),权限授权则通过 `tccli cam` 子命令操作。两套 CLI 命令集分别对应"凭证"和"权限"两个独立层面。 + +--- + +### 服务级账号(实例级) + +服务级账号**不与腾讯云子账号关联**,属于实例级资源,权限管理独立于 CAM。 + +**工作机制**: +- 在实例内通过 `tccli tcr CreateServiceAccount` 创建服务账号,指定其可访问的**命名空间**和**操作权限**(如 `tcr:PushRepository`、`tcr:PullRepository`) +- 权限使用 TCR 自定义 Action 名格式(带 `tcr:` 前缀),存储于实例内部 +- 通过 `tccli tcr DescribeServiceAccounts` 查询列表 + +**适用的自动化场景**: +- CI/CD 流水线(如 Jenkins、GitLab CI、腾讯云 CODING DevOps) +- Kubernetes 集群(通过 ImagePullSecret 挂载服务账号凭证拉取镜像) + +**权限隔离**:服务级账号仅能操作被授予的命名空间,无法跨命名空间访问。权限由 `Permissions` 数组定义,每条权限记录指定 `Resource`(命名空间名)和 `Actions`(TCR 自定义 Action 列表)。 + +**安全注意事项**: +- 任何具有服务级账号 API 权限的子账号均可查询/管理服务级账号(因为权限模型独立于 CAM) +- 存在**权限放大风险**:主账号在授予子账号 `tcr:CreateServiceAccount` 等 API 权限时需严格控制授权范围 +- 服务级账号的访问凭证与人员无关,人员离职不影响其有效性——这既是优点(系统连续性),也是需管控的点(凭证泄露后影响面可能更大) + +**CLI 视角**:服务级账号全程通过 `tccli tcr` 子命令操作,无需交叉使用 `tccli cam`。`Permissions` 参数的 `Actions` 字段使用 TCR 自定义 Action 名(`tcr:PushRepository`、`tcr:PullRepository` 等)。 + +--- + +### 两种模式对比 + +| 维度 | 用户级账号 | 服务级账号 | +|------|-----------|-----------| +| 关联对象 | 腾讯云子账号 | 无云账号关联,实例级资源 | +| 权限系统 | CAM(`tccli cam`) | TCR 自定义权限模型(`tccli tcr`) | +| 适用场景 | 人员手动操作 | CI/CD、K8s 等自动化系统 | +| 凭证可见性 | 仅关联子账号可见/管理 | 有 API 权限的子账号均可管理 | +| 人员变更影响 | 子账号禁用/删除 -> 凭证失效 | 无影响 | +| 权限粒度 | CAM 策略(实例级/仓库级) | 命名空间级(push/pull/create) | +| 审计追溯 | CAM 操作日志 | 实例内部记录 | +| 权限放大风险 | 低(CAM 统一治理) | 需关注(独立权限模型,子账号可能越权管理服务账号) | + +## 前置条件 + +- [环境准备](../../index.md):`tccli` 已安装并配置凭证和地域 +- 已成功 [创建企业版实例](../../../create)(`basic`/`standard`/`premium`),实例状态为 `Running` +- 理解腾讯云 [访问管理 CAM](https://cloud.tencent.com/document/product/598) 的基本概念(子账号、策略、授权) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置 + +# 3. 查询实例列表验证基础连通性 +tccli tcr DescribeInstances \ + --region '' \ + --output json +# expected: exit 0,返回实例列表 +``` + +## 控制台与 CLI 参数映射 + +本文为概念概述页。以下列出两种模式的 tccli 命令入口,具体参数和操作步骤见对应子页面。 + +### 用户级账号 — CLI 命令入口 + +| 控制台操作 | tccli 命令 | 幂等 | 页面 | +|-----------|----------|:--:|------| +| 创建长期访问凭证 | `CreateInstanceToken` | 否 | [用户级账号管理](../../credentials/user-credentials) | +| 查看凭证列表 | `DescribeInstanceToken` | 是 | 同上 | +| 启用/禁用凭证 | `ModifyInstanceToken` | 是 | 同上 | +| 删除凭证 | `DeleteInstanceToken` | 否 | 同上 | +| CAM 策略授权 | `tccli cam` 系列 | — | [基于 CAM 管理子账号权限](../基于cam管理子账号权限) | + +### 服务级账号 — CLI 命令入口 + +| 控制台操作 | tccli 命令 | 幂等 | 页面 | +|-----------|----------|:--:|------| +| 创建服务级账号 | `CreateServiceAccount` | 否 | [服务级账号管理](../../credentials/service-credentials) | +| 查看服务级账号列表 | `DescribeServiceAccounts` | 是 | 同上 | +| 修改服务级账号权限 | `ModifyServiceAccount` | 否 | 同上 | +| 删除服务级账号 | `DeleteServiceAccount` | 否 | 同上 | + +## 操作步骤 + +本文为概念概述页,无独立可执行的 tccli 命令操作。以下为 CLI 环境下快速确认当前实例状态的基础命令。 + +### 查询实例信息 + +查看当前账号下的所有 TCR 企业版实例,确认实例状态、类型和域名: + +```bash +tccli tcr DescribeInstances \ + --region '' \ + --output json +# expected: exit 0,返回 Registries 列表 +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "basic", + "Status": "Running", + "PublicDomain": "tcr-example.tencentcloudcr.com", + "CreatedAt": "2026-06-01T10:00:00+08:00", + "RegionName": "ap-guangzhou", + "EnableAnonymous": false, + "TokenValidTime": 87600 + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +如需查看所有地域的实例: + +```bash +tccli tcr DescribeInstances \ + --AllRegion true \ + --output json +# expected: exit 0,返回所有地域的实例 +``` + +> 具体操作请进入以下子页面: +> - [用户级账号管理](../../credentials/user-credentials) — 创建与管理长期访问凭证(`CreateInstanceToken` / `DescribeInstanceToken` / `DeleteInstanceToken`) +> - [服务级账号管理](../../credentials/service-credentials) — 为 CI/CD 系统创建服务账号(`CreateServiceAccount` / `DescribeServiceAccounts` / `ModifyServiceAccount` / `DeleteServiceAccount`) +> - [基于 CAM 管理子账号权限](../基于cam管理子账号权限) — 通过 CAM 策略控制子账号对 TCR 资源的访问(page_id `41417`) + +## 验证 + +### 控制面(tccli) + +| # | 验证项 | 命令 | 期望结果 | +|---|--------|------|---------| +| 1 | 实例可查询 | `tccli tcr DescribeInstances --region '' --output json` | `TotalCount >= 1`,含实例信息 | +| 2 | 实例状态正常 | 同上,检查 `Registries[].Status` | `Status` 为 `"Running"` | +| 3 | 两种模式功能入口可访问 | 分别执行 `DescribeInstanceToken` 和 `DescribeServiceAccounts` | 均返回 exit 0(列表可为空) | + +### 概念关系验证(交叉引用) + +| 验证关系 | 验证方式 | 期望结论 | +|----------|---------|---------| +| 用户级账号 -> CAM 绑定 | 子账号创建 Token 后主账号在 CAM 删除该子账号,用该凭证 `docker login` | 凭证立即失效,登录失败 | +| 服务级账号 -> CAM 独立 | 子账号 A 创建服务级账号,子账号 B(有 `tcr:DescribeServiceAccounts` 权限)查询 | 子账号 B 可见子账号 A 创建的服务级账号(实例级资源,非 CAM 隔离) | +| 两种模式独立共存 | 同一实例同时创建 1 个用户级凭证 + 1 个服务级凭证,分别 `docker push` | 两份凭证各自独立工作,互不影响 | + +## 清理 + +不适用。本文为概念概述页,不涉及资源创建与清理。 + +## 排障 + +### 查询错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `DescribeInstances` 返回 `UnauthorizedOperation` | — | 子账号缺少 `tcr:DescribeInstances` 权限 | 联系主账号授予 `QcloudTCRReadOnlyAccess` 或添加 `tcr:DescribeInstances` 到自定义策略 | +| `DescribeInstances` 返回空列表 | `tccli tcr DescribeInstances --AllRegion true --output json` | 当前地域无实例,或 `--region` 参数与实际实例地域不一致 | 使用 `--AllRegion true` 查看所有地域实例 | + +### 模式选择常见困惑 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 不确定该用哪种模式 | — | 场景不明确 | 人员手动操作(开发/运维) -> 用户级账号;CI/CD 流水线/K8s 自动化 -> 服务级账号 | +| 服务级账号创建失败:`custom account's permission can not be empty` | — | `Permissions` 参数为空数组 `[]` | 至少传入一条权限记录,含 `Resource` 和 `Actions` | +| 服务级账号创建失败:`not support action: ` | — | `Actions` 使用了不支持的 Action 名 | 使用支持的动作:`tcr:PushRepository`、`tcr:PullRepository`、`tcr:CreateRepository` 等 | +| 用户级账号创建凭证后 `docker login` 失败 | `tccli tcr DescribeInstanceToken --RegistryId '' --region ''` 确认凭证状态 | 子账号被禁用/删除,或凭证已过期 | 确认 CAM 子账号状态正常;凭证过期需重新 `CreateInstanceToken` | + +## 下一步 + +- [用户级账号管理](../../credentials/user-credentials) — 创建与管理长期访问凭证 +- [服务级账号管理](../../credentials/service-credentials) — 为 CI/CD 系统创建服务级账号 +- [基于 CAM 管理子账号权限](../基于cam管理子账号权限) — 通过 CAM 策略控制子账号对 TCR 资源的访问 +- [访问网络控制概述](../../network/network-overview) — 理解公网/内网访问网络层面的安全控制 +- [创建企业版实例](../../../create) — 创建实例后配置访问权限 + +## 控制台替代 + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) -> 选择目标企业版实例 -> 进入 **访问控制** 页面: +- **用户级账号**:切换至 **访问凭证** 标签页,由子账号自行创建和管理 +- **服务级账号**:切换至 **服务级账号** 标签页,由实例管理员创建、授权、分发凭证至 CI/CD 系统 +- **CAM 策略授权**:跳转至 [CAM 控制台](https://console.cloud.tencent.com/cam) 进行子账号策略管理 diff --git a/src/content/docs/cli/tcr/ops/access/private-access/examples/tcr-dns-status.json b/src/content/docs/cli/tcr/ops/access/private-access/examples/tcr-dns-status.json new file mode 100644 index 0000000..3a06e1c --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/private-access/examples/tcr-dns-status.json @@ -0,0 +1,11 @@ +{ + "VpcSet": [ + { + "InstanceId": "tcr-nn8smeyj", + "VpcId": "vpc-of73262z", + "EniLBIp": "10.0.0.5", + "UsePublicDomain": true, + "RegionName": "ap-guangzhou" + } + ] +} diff --git a/src/content/docs/cli/tcr/ops/access/service-credentials/examples/sa-create.json b/src/content/docs/cli/tcr/ops/access/service-credentials/examples/sa-create.json new file mode 100644 index 0000000..56d87cc --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/service-credentials/examples/sa-create.json @@ -0,0 +1,17 @@ +{ + "RegistryId": "tcr-example", + "Name": "robot-demo", + "Permissions": [ + { + "Resource": "team-a", + "Actions": ["tcr:PushRepository", "tcr:PullRepository"] + }, + { + "Resource": "open-source", + "Actions": ["tcr:PullRepository"] + } + ], + "Description": "CI/CD automation account", + "Duration": 365, + "Disable": false +} diff --git a/src/content/docs/cli/tcr/ops/access/service-credentials/examples/sa-modify.json b/src/content/docs/cli/tcr/ops/access/service-credentials/examples/sa-modify.json new file mode 100644 index 0000000..11ad2fa --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/service-credentials/examples/sa-modify.json @@ -0,0 +1,12 @@ +{ + "RegistryId": "tcr-example", + "Name": "robot-demo", + "Description": "Updated CI/CD account", + "Duration": 180, + "Permissions": [ + { + "Resource": "team-a", + "Actions": ["tcr:PullRepository"] + } + ] +} diff --git a/src/content/docs/cli/tcr/ops/access/service-credentials/examples/sa-password.json b/src/content/docs/cli/tcr/ops/access/service-credentials/examples/sa-password.json new file mode 100644 index 0000000..a8c29c0 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/access/service-credentials/examples/sa-password.json @@ -0,0 +1,5 @@ +{ + "RegistryId": "tcr-example", + "Name": "robot-demo", + "Random": true +} diff --git a/src/content/docs/cli/tcr/ops/create/create-tcrctl.md b/src/content/docs/cli/tcr/ops/create/create-tcrctl.md new file mode 100644 index 0000000..7201ec7 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/create/create-tcrctl.md @@ -0,0 +1,26 @@ +--- +title: "创建企业版实例(tcrctl)" +description: "· page_id `51110`" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> 对照官方:[创建企业版实例](https://cloud.tencent.com/document/product/1141/51110) · page_id `51110` +> +> **P1 — 占位符** · 待 P0 全量完成后填充 `tcrctl` 操作体。 + +## Overview + +(P1 待撰写:`tcrctl create instance` / `tcrctl instance create` 等产品 CLI 命令封装 `CreateInstance`,封装实例等待与状态轮询。) + +## Before you begin + +参见同目录 [tccli 操作.md](.) 的 Before you begin。 + +## Procedure + +> P1 待撰写。 + +## Next steps + +参见同目录 [tccli 操作.md](.) 的 Next steps。 diff --git a/src/content/docs/cli/tcr/ops/create/create.md b/src/content/docs/cli/tcr/ops/create/create.md new file mode 100644 index 0000000..3d31354 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/create/create.md @@ -0,0 +1,636 @@ +--- +title: "创建企业版实例(tccli)" +description: "· page_id `51110` · tccli ≥3.1.107.1 · API 2019-09-24" +--- + +> 对照官方:[创建企业版实例](https://cloud.tencent.com/document/product/1141/51110) · page_id `51110` · tccli ≥3.1.107.1 · API 2019-09-24 + +## 概述 + +通过 `tccli tcr CreateInstance` 创建 TCR 企业版实例。企业版实例是 TCR 的核心资源,提供容器镜像托管、安全扫描、分发与生命周期管理。创建实例时系统自动关联 COS 存储桶存放镜像数据,并生成专用 Registry 域名(格式 `.tencentcloudcr.com`),供 `docker login` / `docker push` / `docker pull` 使用。 + +实例规格分为三档:**基础版**(basic)、**标准版**(standard)、**高级版**(premium),不同规格功能差异如下,详见[计费概述](https://cloud.tencent.com/document/product/1141/40540)。 + +| 规格 | 适用场景 | 安全扫描 | 跨地域同步 | 自定义域名 | +|------|---------|:---:|:---:|:---:| +| basic | 个人开发、小型团队入门 | 不支持 | 不支持 | 不支持 | +| standard | 中型团队、企业测试 | 支持 | 不支持 | 不支持 | +| premium | 生产环境、企业级 | 支持 | 支持 | 支持 | + +创建为异步操作,典型耗时约 1--2 分钟(基础版),需轮询 `DescribeInstances` 或 `DescribeInstanceStatus` 直至 `Status` 为 `Running`。 + +## 前置条件 + +- [环境准备](../index.md) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 +``` + +```bash +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域(如 ap-guangzhou) +``` + +```bash +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tcr:CreateInstance, tcr:DescribeInstances, tcr:DescribeInstanceStatus +# tcr:CheckInstanceName, tcr:DescribeRegions +# tcr:ModifyInstance(如后续需升级规格或关闭删除保护) +# tcr:DeleteInstance(如需清理测试实例) +# 验证:执行 DescribeInstances 确认权限 +tccli tcr DescribeInstances --region ap-guangzhou +# expected: exit 0,返回实例列表(可为空) +``` + +**输出**: + +```json +{ + "TotalCount": 0, + "Registries": [], + "RequestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" +} +``` + +```bash +# 4. 检查关联服务权限 +tccli vpc DescribeVpcs --region ap-guangzhou +# expected: exit 0,返回 VPC 列表(可为空) +``` + +**输出**: + +```json +{ + "TotalCount": 0, + "VpcSet": [], + "RequestId": "b2c3d4e5-f6a7-8901-bcde-f12345678901" +} +``` + +### 资源检查 + +```bash +# 5. 查询 TCR 企业版实例数量(确认未达配额上限) +tccli tcr DescribeInstances --region ap-guangzhou --output json | jq '.TotalCount' +# expected: 当前实例数 < 配额上限(默认每地域 10 个) +``` + +**输出**: + +```text +18 +``` + +```bash +# 6. 验证目标实例名可用 +tccli tcr CheckInstanceName --RegistryName '' --region ap-guangzhou --output json +# expected: exit 0, "IsValidated": true, "DetailCode": 0(名称可用) +``` + +**输出**: + +```json +{ + "IsValidated": true, + "DetailCode": 0, + "RequestId": "72379508-75b5-477c-96fd-3aaa855e5b59" +} +``` + +```bash +# 7. 确认目标地域支持企业版 +tccli tcr DescribeRegions --region ap-guangzhou --output json | jq '.Regions[] | select(.RegionName=="ap-guangzhou")' +# expected: {"Alias": "gz", "RegionId": 1, "RegionName": "ap-guangzhou", "Status": "alluser"} +``` + +**输出**: + +```json +{ + "Alias": "gz", + "RegionId": 1, + "RegionName": "ap-guangzhou", + "Status": "alluser" +} +``` + +### 计费与命名决策 + +- **计费类型**:默认选择按量计费(`RegistryChargeType=0`),按小时计费,测试完成后销毁实例即停止费用。包年包月(`RegistryChargeType=1`)适合长期稳定运行的生产环境,但需额外填写 `RegistryChargePrepaid` 参数。 +- **实例名**:全局唯一,创建后不可修改。建议组合公司缩写、地域、项目名,如 `myco-gz-dev`。 +- **地域**:选择 `ap-guangzhou`(广州),应与容器集群所在地一致。实例地域购买后不可更改。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 / 参数 | 幂等 | +|-----------|------------------|:--:| +| 选择地域(控制台顶部菜单) | `--region ap-guangzhou` | 是 | +| 查询可用地域列表 | `DescribeRegions` | 是 | +| 检查实例名是否可用 | `CheckInstanceName --RegistryName ` | 是 | +| 输入实例名(全局唯一) | `--RegistryName` | — | +| 选择实例规格 | `--RegistryType basic/standard/premium` | — | +| 计费类型选择 | `--RegistryChargeType 0/1` | — | +| 启用多 AZ 存储 | `--EnableCosMAZ` | — | +| 启用 COS 版本控制 | `--EnableCosVersioning` | — | +| 开启删除保护 | `--DeletionProtection` | — | +| 同步标签至 COS 桶 | `--SyncTag` | — | +| 添加实例标签 | `--TagSpecification` | — | +| 勾选协议 / 立即购买 | `CreateInstance` | 否(重复同名报错) | +| 查看实例进度至"运行中" | `DescribeInstances` / `DescribeInstanceStatus` 轮询 | 是 | +| 升级实例规格 | `ModifyInstance --RegistryType premium` | 是(重复升级幂等) | + +## 关键字段说明 + +以下说明 `CreateInstance` 的主要参数。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryName` | String | 是 | 全局唯一,2-255 字符,创建后不可修改。自动生成域名 `.tencentcloudcr.com` | 名称被占用 → `ResourceAlreadyExists.InstanceName`;`CheckInstanceName` 预验证可规避 | +| `RegistryType` | String | 是 | `basic`(基础版)/ `standard`(标准版)/ `premium`(高级版)。不同规格功能差异见[计费概述](https://cloud.tencent.com/document/product/1141/40540)。**仅支持升级,不支持降级** | 填写非法值 → `InvalidParameter.RegistryType` | +| `RegistryChargeType` | Integer | 否 | `0` = 按量计费(默认),`1` = 预付费(包年包月)。若为 `1` 必须同时填 `RegistryChargePrepaid` | 预付费模式缺 `RegistryChargePrepaid` → `MissingParameter.RegistryChargePrepaid` | +| `RegistryChargePrepaid` | Object | 条件 | `RegistryChargeType=1` 时必填。`Period`: 购买月数(1/3/6/12),`RenewFlag`: `0`=手动续费/`1`=自动续费/`2`=不续费 | 未填 → 参数校验失败 | +| `DeletionProtection` | Boolean | 否 | 默认 `false`。`true` 后需先 `ModifyInstance --DeletionProtection false` 才能 `DeleteInstance` | 忘开 → 可能误删生产实例 | +| `EnableCosMAZ` | Boolean | 否 | 默认 `false`。`true` = COS 多 AZ 冗余存储(容灾,费用较高) | 创建后不可更改;非必要勿开 | +| `EnableCosVersioning` | Boolean | 否 | 默认 `false`。`true` = COS 桶多版本控制 | 创建后不可更改 | +| `SyncTag` | Boolean | 否 | 默认 `false`。`true` = 实例标签自动同步至关联 COS 桶 | — | +| `TagSpecification` | Object | 否 | `ResourceType`: `"instance"`,`Tags`: `[{"Key": "env", "Value": "prod"}]` | — | + +## 操作步骤 + +### 步骤1:选择地域 + +查询 TCR 企业版支持的地域列表: + +```bash +tccli tcr DescribeRegions --region ap-guangzhou --output json +# expected: exit 0,Regions 数组各条目 Status: "alluser" +``` + +**输出**: + +```json +{ + "TotalCount": 19, + "Regions": [ + { + "Alias": "gz", + "RegionId": 1, + "RegionName": "ap-guangzhou", + "Status": "alluser" + } + ], + "RequestId": "eac6b301-a322-493a-8e36-83b295459397" +} +``` + +> 各地域 `Status` 为 `alluser` 表示所有用户均可使用。选择实例地域后,后续所有操作需使用相同的 `--region` 值。 + +### 步骤2:检查实例名可用性 + +创建实例前,验证目标实例名是否已被占用: + +```bash +tccli tcr CheckInstanceName --RegistryName '' --region ap-guangzhou --output json +# expected: exit 0, "IsValidated": true(名称可用) +``` + +**名称可用时输出**: + +```json +{ + "IsValidated": true, + "DetailCode": 0, + "RequestId": "72379508-75b5-477c-96fd-3aaa855e5b59" +} +``` + +> 若名称已被占用,返回 `ResourceAlreadyExists.InstanceName`(见[排障](#排障))。建议在实例名后缀加随机字符规避冲突。 + +### 步骤3:创建实例(最简模式) + +#### 选择依据 + +- **RegistryType 选 `basic`**:basic 类型 PayMod=0 后付费(`RegistryChargeType=0`),无预付扣费。开发测试场景最小化成本,满足镜像托管基本需求。需要 premium 功能时可在线升级(`ModifyInstance --RegistryType premium`),但**不支持降级**,升级前确认确实需要高级功能。 +- **RegistryChargeType 选 `0`(按量计费)**:按小时计费,测试完成后销毁实例即停止费用。预付费(`RegistryChargeType=1`)需额外填写 `RegistryChargePrepaid` 参数(`Period` + `RenewFlag`),适合长期生产环境。 +- **region 选 `ap-guangzhou`**:TCR 在广州 region 可用(`DescribeRegions` 返回 19 个 `alluser` region),与容器集群所在地一致以降低内网拉取延迟。可通过 `tccli tcr DescribeRegions --region ap-guangzhou | jq '.Regions[].RegionName'` 确认完整可用列表。 + +#### 最简创建 + +仅含必填字段的最小可运行命令: + +```bash +tccli tcr CreateInstance \ + --RegistryName \ + --RegistryType basic \ + --RegistryChargeType 0 \ + --region +# expected: exit 0,返回 RegistryId +``` + +**输出**: + +```json +{ + "RegistryId": "tcr-example", + "RequestId": "610fa61b-8d81-4cf4-8b24-2295ae96d0f2" +} +``` + +> 记录返回的 `RegistryId`(示例 `tcr-example`),后续所有实例操作均依赖此 ID。 + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | 实例名称 | 全局唯一,2-255 字符 | 自定义,创建前 `CheckInstanceName` 验证 | +| `` | 地域 | 如 `ap-guangzhou` | `tccli tcr DescribeRegions` 查看可用地域 | + +### 步骤4:增强配置(可选) + +#### 4a. 开启删除保护 + +开启删除保护可防止误删生产实例。关闭保护需先执行 `ModifyInstance --DeletionProtection false`: + +```bash +tccli tcr CreateInstance \ + --RegistryName \ + --RegistryType basic \ + --RegistryChargeType 0 \ + --DeletionProtection true \ + --region +# expected: exit 0,返回 RegistryId +``` + +#### 4b. COS 多 AZ + 版本控制(standard+) + +> `EnableCosMAZ` 和 `EnableCosVersioning` 创建后不可更改,非必要勿开。 + +```bash +tccli tcr CreateInstance \ + --RegistryName \ + --RegistryType standard \ + --RegistryChargeType 0 \ + --EnableCosMAZ true \ + --EnableCosVersioning true \ + --region +# expected: exit 0,返回 RegistryId +``` + +#### 4c. 完整增强创建(含标签 + 保护) + +```bash +tccli tcr CreateInstance \ + --RegistryName \ + --RegistryType standard \ + --RegistryChargeType 0 \ + --DeletionProtection true \ + --EnableCosMAZ true \ + --EnableCosVersioning true \ + --SyncTag true \ + --TagSpecification '{"ResourceType":"instance","Tags":[{"Key":"env","Value":"prod"}]}' \ + --region +# expected: exit 0,返回 RegistryId +``` + +#### 4d. 预付费(包年包月) + +预付费(`RegistryChargeType=1`)时必须同时提供 `RegistryChargePrepaid` 参数,指定购买月数和续费方式: + +```bash +tccli tcr CreateInstance \ + --RegistryName \ + --RegistryType standard \ + --RegistryChargeType 1 \ + --RegistryChargePrepaid '{"Period":1,"RenewFlag":0}' \ + --DeletionProtection true \ + --region +# expected: exit 0,返回 RegistryId +``` + +> 预付费实例销毁时按使用时长比例退还至腾讯云账户(含现金和赠送金),详见[退费说明](https://cloud.tencent.com/document/product/1141/53319)。 + +### 步骤5:升级实例规格(basic → premium) + +basic 后付费实例可在线升级至 premium,无需预先创建高级版实例。升级为异步操作,按量计费模式下不触发预付扣费,仅单价随 tier 提升而提高: + +```bash +tccli tcr ModifyInstance \ + --RegistryId \ + --RegistryType premium \ + --region +# expected: exit 0,返回 RequestId +``` + +**输出**: + +```json +{ + "RequestId": "d2656698-9594-48a2-8039-c2e736bb7b95" +} +``` + +| 场景 | CLI 命令 | 说明 | +|------|---------|------| +| basic → standard | `ModifyInstance --RegistryId --RegistryType standard --region ` | 解锁安全扫描、自动扫描、镜像加速等标准版功能 | +| standard → premium | `ModifyInstance --RegistryId --RegistryType premium --region ` | 解锁复制实例、签名策略、跨账号同步等高级版功能 | +| 升级耗时 | 约 30--60 秒,轮询至 `Status: Running` | 升级期间实例正常可用 | +| 降级 | **不支持降级** | 升级前确认确实需要高级功能 | + +### 步骤6:查询实例列表(确认创建) + +查询当前地域所有实例: + +```bash +tccli tcr DescribeInstances --region +# expected: exit 0,TotalCount > 0 +``` + +**输出**: + +```json +{ + "TotalCount": 18, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "tcr-example", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "tcr-example.tencentcloudcr.com", + "InternalEndpoint": "10.1.67.13", + "EnableAnonymous": true, + "TokenValidTime": 87600, + "PayMod": 0, + "DeletionProtection": false, + "EnableCosMAZ": false, + "EnableCosVersioning": false, + "CreatedAt": "2026-06-18T17:29:53+08:00" + } + ], + "RequestId": "5dcfc309-f2f9-4038-b831-f75acd4fa794" +} +``` + +按名称精确过滤查询: + +```bash +tccli tcr DescribeInstances \ + --region \ + --Filters '[{"Name":"RegistryName","Values":[""]}]' +# expected: 返回匹配实例的完整详情 +``` + +**输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "tcr-example", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "tcr-example.tencentcloudcr.com", + "CreatedAt": "2026-06-18T17:29:53+08:00" + } + ], + "RequestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" +} +``` + +> 创建后实例初始状态为 `Deploying`,等待 1--2 分钟后变为 `Running`。 + +### 步骤7:轮询实例状态至 Running + +使用 `DescribeInstanceStatus` 精确获取创建进度: + +```bash +tccli tcr DescribeInstanceStatus \ + --RegistryIds '[""]' \ + --region \ + --output json +# expected: Status: "Running" +``` + +**输出(创建完成 — `Running`)**: + +```json +{ + "RegistryStatusSet": [ + { + "RegistryId": "tcr-example", + "Status": "Running", + "Conditions": [ + { + "Type": "", + "Status": "Running", + "Reason": "" + } + ] + } + ], + "RequestId": "eac6b301-a322-493a-8e36-83b295459397" +} +``` + +> 若 `Status` 为 `Deploying`,等待 15--30 秒后再次轮询。真机实测:基础版实例 7 次轮询后进入 `Running`,累计约 60 秒。 + +也可用 JMESPath 过滤器仅返回状态字段: + +```bash +tccli tcr DescribeInstanceStatus \ + --RegistryIds '[""]' \ + --region \ + --output json \ + --filter "RegistryStatusSet[0].Status" +# expected: "Running" +``` + +### 步骤8:获取实例访问域名 + +实例状态 `Running` 后,记录 `PublicDomain` 供后续 `docker login` / `docker push` / `docker pull` 使用: + +```bash +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region \ + --output json \ + --filter "Registries[0].{PublicDomain:PublicDomain,InternalEndpoint:InternalEndpoint}" +# expected: PublicDomain 非空,InternalEndpoint 非空 +``` + +**输出**: + +```json +{ + "PublicDomain": "tcr-example.tencentcloudcr.com", + "InternalEndpoint": "10.1.67.13" +} +``` + +- `PublicDomain`:公网访问域名,格式 `.tencentcloudcr.com` +- `InternalEndpoint`:内网访问 IP,用于 VPC 内免密拉取 + +## 验证 + +### 控制面(tccli) + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 实例名可用 | `CheckInstanceName --RegistryName '' --region ` | `IsValidated: true`,无 Error | +| 地域支持 | `DescribeRegions --region --output json \| jq '.Regions[] \| select(.RegionName=="ap-guangzhou")'` | `Status: "alluser"` | +| 实例已创建 | `DescribeInstances --Registryids '[""]' --region ` | `TotalCount >= 1`,`Registries` 含目标实例 | +| 实例状态 Running | `DescribeInstanceStatus --RegistryIds '[""]' --region ` | `Status: "Running"` | +| RegistryType 正确 | `DescribeInstances --Registryids '[""]' --region --filter "Registries[0].RegistryType"` | 与创建参数一致(如 `"basic"` 或 `"premium"`) | +| 公网域名就绪 | `DescribeInstances --Registryids '[""]' --region --filter "Registries[0].PublicDomain"` | `".tencentcloudcr.com"` | +| 内网端点就绪 | 同上,取 `InternalEndpoint` | 非空 IP 地址 | +| 删除保护状态 | 同上,取 `DeletionProtection` | 与创建参数一致 | + +### 数据面 + +实例创建后无法直接推送镜像,还需后续配置: + +- [访问配置](../access/permissions/cam-subaccount) — 配置公网/内网访问控制策略(白名单) +- `docker login ` — 使用 `CreateInstanceToken` 获取长期登录凭证 +- [镜像创建](../image-creation/namespace) — 创建命名空间与镜像仓库 +- `docker push` / `docker pull` — 推送/拉取镜像 + +## 清理 + +> **危险警告:`DeleteInstance` 将不可逆清除实例下所有命名空间、镜像仓库、Helm Chart、访问令牌、安全策略、同步复制规则及关联 COS 桶数据。数据不可恢复。** + +### 1. 清理前状态检查 + +```bash +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region \ + --output json +# expected: 确认是待删除的目标实例,记录 RegistryId、RegistryName、DeletionProtection +``` + +**输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "tcr-example", + "RegistryType": "basic", + "Status": "Running", + "DeletionProtection": false, + "CreatedAt": "2026-06-18T17:29:53+08:00" + } + ], + "RequestId": "c3d4e5f6-a7b8-9012-cdef-123456789012" +} +``` + +### 2. 关闭删除保护(若开启) + +若实例开启了 `DeletionProtection`,需先关闭: + +```bash +tccli tcr ModifyInstance \ + --RegistryId \ + --DeletionProtection false \ + --region \ + --output json +# expected: exit 0,返回 RequestId +``` + +### 3. 删除实例 + +```bash +tccli tcr DeleteInstance \ + --RegistryId \ + --region \ + --output json +# expected: exit 0,返回 RequestId +``` + +| 参数 | 类型 | 必填 | 说明 | +|------|------|:--:|------| +| `` | String | 是 | 待删除的实例 ID | +| `--DeleteBucket` | Boolean | 否 | 是否同时删除关联 COS 存储桶。默认 `false`(保留 COS 桶,需手动前往 [COS 控制台](https://console.cloud.tencent.com/cos) 清理) | + +> **计费警告**:按量计费实例删除后停止计费;包年包月实例按使用时长比例退还至腾讯云账户(含现金和赠送金),详见[退费说明](https://cloud.tencent.com/document/product/1141/53319)。 +> +> **级联删除警告**:`DeleteInstance` 将同时删除实例下所有命名空间、仓库、令牌、安全策略、同步复制规则。如使用了 `--DeleteBucket true`,关联 COS 桶及其中全部镜像数据将被永久删除且不可恢复。 +> +> **主从实例依赖警告**:如果实例有关联的复制实例(ReplicationInstance),必须先删除复制实例再删除主实例。使用 `tccli tcr DescribeReplicationInstances --RegistryId --region ` 检查是否存在从实例,确认 `TotalCount` 为 `0` 后再删除。 + +### 4. 验证已删除 + +```bash +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region \ + --output json +# expected: TotalCount: 0,Registries: [] +``` + +**输出**: + +```json +{ + "TotalCount": 0, + "Registries": [], + "RequestId": "d4e5f6a7-b8c9-0123-defa-234567890123" +} +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `CheckInstanceName` 返回 `ResourceAlreadyExists.InstanceName` | `tccli tcr CheckInstanceName --RegistryName '' --region --output json` 确认 `IsValidated: false` | 实例名已被占用(全局唯一) | 更换 `RegistryName`,建议组合公司缩写、地域、项目名,如 `myco-gz-dev`;或在后缀加随机字符 | +| `CreateInstance` 返回 `InvalidParameter.RegistryType` | 检查请求参数中 `RegistryType` 字段值 | 填写了非 `basic`/`standard`/`premium` 的值 | 使用 `"basic"`(基础版)、`"standard"`(标准版)或 `"premium"`(高级版) | +| `CreateInstance` 返回 `MissingParameter.RegistryChargePrepaid` | 检查请求参数中 `RegistryChargeType` 是否为 `1` | `RegistryChargeType=1`(预付费)时忘记提供 `RegistryChargePrepaid` 参数 | 若为预付费,必须同时提供 `RegistryChargePrepaid`:`{"Period":1,"RenewFlag":0}`;若不需要预付费,改用 `RegistryChargeType=0` | +| `CreateInstance` 返回 `FailedOperation.DbError` | 创建前执行 `CheckInstanceName --RegistryName '' --region ` 预验证 | 实例名冲突(与已有实例同名) | 更换名称或先 `DeleteInstance` 删除同名实例。创建前务必执行 `CheckInstanceName` 预验证 | +| `CreateInstance` 返回 `UnauthorizedOperation` | `tccli tcr DescribeInstances --region ` 验证是否有 list 权限 | 子账号缺少 `tcr:CreateInstance` 权限 | 联系主账号授予 `QcloudTCRFullAccess` 或最小权限策略(含 `tcr:CreateInstance`) | +| `CreateInstance` 返回 `LimitExceeded` | `tccli tcr DescribeInstances --region --output json \| jq '.TotalCount'` 统计已有实例数 | 企业版实例数量已达配额上限 | 此为环境限制,非命令错误。前往 [配额中心](https://console.cloud.tencent.com/tcr/instance) 申请提升,或退还闲置实例 | +| `CreateInstance` 返回 `InternalError` / `FailedOperation` | 保留返回的 `RequestId` | 云端服务暂时不可用 | 稍后重试(间隔 30 秒以上);若持续失败,凭 `RequestId` [在线咨询](https://cloud.tencent.com/online-service?from=doc_1141) | +| `DescribeRegions` 未返回目标地域 | `tccli tcr DescribeRegions --region --output json \| jq '.Regions[] \| .RegionName'` 查看完整列表 | 目标地域未对企业版开放,或仅限白名单用户 | 确认 `Regions[].Status` 为 `alluser`;部分地域(如金融专区)需联系腾讯云开通白名单 | +| `DeleteInstance` 被拒绝 | `tccli tcr DescribeInstances --Registryids '[""]' --region --output json \| jq '.Registries[0].DeletionProtection'` | 实例开启了删除保护 | `ModifyInstance --DeletionProtection false` 关闭后重试 | +| `DeleteInstance` 返回 "has N replication registry" | `tccli tcr DescribeReplicationInstances --RegistryId --region ` 检查从实例 | 主实例下存在未删除的从实例(复制实例),需先清空从实例 | 逐个执行 `DeleteReplicationInstance`,确认 `DescribeReplicationInstances` 返回 `TotalCount: 0` 后再删除主实例 | + +### 创建已提交但状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 返回了 `RegistryId` 但 `Status` 长时间为 `Deploying`(超过 5 分钟) | `tccli tcr DescribeInstanceStatus --RegistryIds '[""]' --region ` 循环轮询,查看 `Status` 和 `Conditions` | 异步创建缓慢(正常情况基础版 7 次轮询约 60 秒)或 COS/VPC 后端依赖创建卡住(异常) | 继续轮询;超过 5 分钟确认 [COS](https://console.cloud.tencent.com/cos5)、[VPC](https://console.cloud.tencent.com/vpc)、[PrivateDNS](https://console.cloud.tencent.com/privatedns) 已开通;凭 `RegistryId` 和 `RequestId` [在线咨询](https://cloud.tencent.com/online-service?from=doc_1141) | +| `Status: "Unhealthy"` | 同上,查看 `Conditions` 字段的 `Reason` | 后端存储或网络异常 | 查看 `Conditions.Reason` 定位问题;保留 `RegistryId`、`RequestId`、创建 JSON → 提交工单 | +| `Status: "FailedCreated"` | 同上 | 创建流程失败 | 保留 `RegistryId`、`RequestId`、创建 JSON → `DeleteInstance` 删除后修正参数重新创建 | +| 创建成功但某属性不符预期 | `tccli tcr DescribeInstances --Registryids '[""]' --region ` 检查各可选字段 | 创建时遗漏可选参数(如 `DeletionProtection`) | 可调属性用 `ModifyInstance` 修改;不可调属性(如 `EnableCosMAZ`)需删除重建 | + +### 常见误操作预防 + +| 场景 | 预防措施 | +|------|---------| +| 误删生产实例 | 生产实例开启 `DeletionProtection: true`,需双重确认才能删除 | +| `RegistryType` 填错 | 不确定规格时先用 `basic`(按量计费),需要高级功能时再 `ModifyInstance` 升级 | +| 预付费参数遗漏 | 若 `RegistryChargeType=1`,务必同时提供 `RegistryChargePrepaid: {"Period":1,"RenewFlag":0}` | +| 包年包月实例误删产生退费损失 | 确认退费金额(参考[退费说明](https://cloud.tencent.com/document/product/1141/53319))后再操作 | + +## 下一步 + +- [销毁退还实例](../delete)(page_id `51111`) — 退还或销毁不再使用的实例 +- [访问配置](../access/permissions/cam-subaccount) — 配置公网/内网访问与安全策略 +- [镜像创建](../image-creation/namespace) — 创建命名空间与镜像仓库 +- [镜像安全](../image-security/vulnerability-scan) — 镜像安全扫描与漏洞修复 +- [环境准备](../index.md) — 返回 TCR 工具链入口 + +## 控制台替代 + +[容器镜像服务 → 实例管理 → 新建](https://console.cloud.tencent.com/tcr/instance):选择地域,填写实例名、规格(basic/standard/premium),按需启用多 AZ 与版本控制,配置标签与删除保护,阅读协议后购买,等待实例状态变为"运行中"。 diff --git a/src/content/docs/cli/tcr/ops/devops/webhook-tcrctl.md b/src/content/docs/cli/tcr/ops/devops/webhook-tcrctl.md new file mode 100644 index 0000000..ea2ba4d --- /dev/null +++ b/src/content/docs/cli/tcr/ops/devops/webhook-tcrctl.md @@ -0,0 +1,43 @@ +--- +title: "触发器(Webhook)(tcrctl)" +description: "· page_id `44369`" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> 对照官方:[触发器(Webhook)](https://cloud.tencent.com/document/product/1141/44369) · page_id `44369` +> +> 本页为 P1 产品 CLI 操作页,将在 P1 阶段(产品文档 agentic 化)中完成正文。 + +## Overview + +(待完成)使用 `tcrctl` 管理实例触发器的创建、配置、删除与触发日志查看。 + +## Before you begin + +(待完成) + +## Procedure + +(待完成) + +## Verify + +(待完成) + +## Cleanup + +(待完成) + +## Troubleshoot + +(待完成) + +## Next steps + +- [自动删除镜像版本](../../image-cleanup/auto-delete) +- [容器镜像安全扫描](../../image-security/vulnerability-scan) + +## Console alternative + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 选择实例 → 左侧导航栏 **触发器**。 diff --git a/src/content/docs/cli/tcr/ops/devops/webhook.md b/src/content/docs/cli/tcr/ops/devops/webhook.md new file mode 100644 index 0000000..814355f --- /dev/null +++ b/src/content/docs/cli/tcr/ops/devops/webhook.md @@ -0,0 +1,404 @@ +--- +title: "触发器(Webhook)(tccli)" +description: "· page_id `44369`" +--- + +> 对照官方:[触发器(Webhook)](https://cloud.tencent.com/document/product/1141/44369) · page_id `44369` + +## 概述 + +TCR 企业版支持配置 Webhook 触发器,在镜像推送/拉取/删除或 Helm Chart 推送/拉取/删除时自动向指定 URL 发起 HTTP POST 请求,便于接入 CI/CD 流程实现自动部署等 DevOps 场景。 + +触发器支持的 `EventTypes`: + +| 事件类型 | 说明 | +|---------|------| +| `pushImage` | 推送镜像 | +| `pullImage` | 拉取镜像 | +| `deleteImage` | 删除镜像 | +| `chartPush` | 推送 Helm Chart | +| `chartPull` | 拉取 Helm Chart | +| `chartDelete` | 删除 Helm Chart | + +`Condition` 字段为字符串类型,支持事件类型过滤(如 `"pushImage"`)或 JSON 格式的正则过滤(含命名空间、仓库名、Tag 匹配规则)。 + +> **注意:** 本功能仅适用于 **TCR 企业版**实例。个人版不支持。 + +## 前置条件 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域 + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tcr:CreateWebhookTrigger, tcr:DescribeWebhookTrigger, tcr:ModifyWebhookTrigger +# tcr:DeleteWebhookTrigger, tcr:DescribeWebhookTriggerLog +# 验证:执行 DescribeWebhookTrigger 确认权限 +tccli tcr DescribeWebhookTrigger --region --RegistryId --Limit 1 +# expected: exit 0,返回触发器列表(可为空) +``` + +### 资源检查 + +```bash +# 4. 确认实例存在且状态为 Running +tccli tcr DescribeInstances --region --RegistryIds '[""]' +# expected: Status 为 "Running" + +# 5. 确认命名空间存在 — 触发器必须在已有命名空间下创建 +tccli tcr DescribeNamespaces --region --RegistryId +# expected: 至少有一个命名空间,记录目标 NamespaceName + +# 6. 确认 Webhook 目标 URL 已就绪(可选,但未就绪则触发时请求失败) +# 触发器创建时不校验目标 URL 可达性,建议先用 curl 验证 +curl -X POST -I +# expected: HTTP 响应(2xx 或 4xx 均可,证明可达) +``` + +## 关键字段说明 + +以下说明 `CreateWebhookTrigger` 中 `--Trigger` JSON 的主要字段。完整参数定义见 API 文档。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `Name` | String | 是 | 规则名称,支持小写字母、数字及 `-`、`.`、`_`,须以字母数字开头。同命名空间下不可重复 | 缺此字段 → `InvalidParameter.ErrorTcrInvalidParameter`:"Name: non zero value required" | +| `Enabled` | Boolean | 是 | `true`(启用)或 `false`(禁用) | 缺此字段 → `InvalidParameter.ErrorTcrInvalidParameter`:"Enabled: non zero value required" | +| `Targets` | Array | 是 | Webhook 目标列表。每个元素含 `Address`(目标 URL,String)和可选 `Headers`(Array,每项含 `Key` 和 `Values`) | 空数组或格式错误 → 创建失败 | +| `EventTypes` | Array | 是 | 触发事件类型数组,枚举值:`pushImage`、`pullImage`、`deleteImage`、`chartPush`、`chartPull`、`chartDelete` | 无此字段 → 创建失败 | +| `Condition` | **String** | 是 | 触发条件字符串。简单场景填事件类型(如 `"pushImage"`);高级场景填 JSON 字符串(含 `EventType` 和 `Filter` 对象)。**注意:Condition 是 String 类型,不是 Object** | 填成 Object → `InvalidParameter`:"Trigger.Condition 取值类型错误。参数类型应为 string" | +| `Description` | String | 否 | 规则描述,支持中文 | — | + +> **重要:** `--Namespace` 是 CLI 顶层独立参数,**不在 `--Trigger` JSON 内部**。缺少此参数将导致命令失败。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 查看触发器列表 | `tccli tcr DescribeWebhookTrigger --RegistryId ` | 是 | +| 新建触发器 | `tccli tcr CreateWebhookTrigger --RegistryId --Trigger '{...}' --Namespace ` | 否(同名冲突) | +| 修改触发器 | `tccli tcr ModifyWebhookTrigger --RegistryId --Trigger '{...}' --Namespace ` | 是 | +| 删除触发器 | `tccli tcr DeleteWebhookTrigger --RegistryId --Id --Namespace ` | 否 | +| 查看触发日志 | `tccli tcr DescribeWebhookTriggerLog --RegistryId --Id --Namespace ` | 是 | + +## 操作步骤 + +### 1. 创建触发器 + +#### 选择依据 + +- **EventTypes**:按实际需求选择事件类型。常见场景:CI/CD 镜像构建完成后触发自动部署 → 选 `pushImage`;镜像版本淘汰 → 选 `deleteImage`。 +- **Condition**:简单场景用事件类型字符串(如 `"pushImage"`),高级场景用 JSON 字符串做正则过滤(按命名空间、仓库名、Tag 过滤)。 +- **Namespace**:`--Namespace` 是 CLI 顶层独立参数,传命名空间名称(非 ID)。不是 `--Trigger` JSON 的一部分。 + +#### 最小创建(仅必填字段,监听 pushImage 事件) + +```bash +tccli tcr CreateWebhookTrigger \ + --RegistryId '' \ + --Trigger '{"Name":"","Enabled":true,"Targets":[{"Address":""}],"EventTypes":["pushImage"],"Condition":"pushImage"}' \ + --Namespace '' \ + --region +# expected: exit 0,返回 Trigger 对象含 Id +``` + +```json +{ + "Trigger": { + "Name": "rewrite-test-webhook", + "Id": 1, + "Enabled": true, + "EventTypes": ["pushImage", "pullImage"], + "Condition": "pushImage" + }, + "RequestId": "06af6288-50d6-4585-a2ba-8245ecd15107" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | 实例 ID | 格式 `tcr-xxxxxxxx` | `tccli tcr DescribeInstances --region ` | +| `` | 触发器名称 | 支持小写字母、数字及 `-`、`.`、`_`,须以字母数字开头 | 自定义,同命名空间不可重复 | +| `` | Webhook 目标 URL | 完整 HTTP/HTTPS 地址 | 由 Webhook Server 提供 | +| `` | 命名空间名称 | 已存在的命名空间 | `tccli tcr DescribeNamespaces --region --RegistryId ` | +| `` | 地域 | 如 `ap-guangzhou` | `tccli tcr DescribeRegions` | + +#### 增强配置(多事件 + 自定义 Header) + +```bash +tccli tcr CreateWebhookTrigger \ + --RegistryId '' \ + --Trigger '{"Name":"","Enabled":true,"Targets":[{"Address":"","Headers":[{"Key":"X-Custom-Auth","Values":[""]}]}],"EventTypes":["pushImage","pullImage","deleteImage"],"Condition":"pushImage","Description":"生产环境自动部署触发器"}' \ + --Namespace '' \ + --region +# expected: exit 0,返回 Trigger 对象含 Id +``` + +#### 高级过滤(Condition 用 JSON 字符串做正则匹配) + +```bash +tccli tcr CreateWebhookTrigger \ + --RegistryId '' \ + --Trigger '{"Name":"","Enabled":true,"Targets":[{"Address":""}],"EventTypes":["pushImage"],"Condition":"{\"EventType\":\"pushImage\",\"Filter\":{\"Namespace\":\"\",\"Repositories\":[\"\"],\"Tags\":[\"v[0-9]+.*\"]}}"}' \ + --Namespace '' \ + --region +# expected: exit 0,返回 Trigger 对象含 Id +``` + +> **注意**:当 `Condition` 包含 JSON 内容时,需对内部引号做转义(`\"`)。因为 Shell 中 `'...'` 单引号内的双引号不需要转义,但 JSON 内的 `"` 在整个 `--Trigger` 值里已经是合法的 JSON 字符串字段值,只需确保 Condition 的值整体是合法 JSON 字符串即可。 + +### 2. 查看触发器列表 + +列出指定实例的所有触发器: + +```bash +tccli tcr DescribeWebhookTrigger \ + --RegistryId '' \ + --Limit 20 \ + --Offset 0 \ + --region +# expected: exit 0,返回触发器列表 +``` + +```json +{ + "TotalCount": 0, + "Triggers": [], + "RequestId": "d5d75baf-bd9c-4aff-9fb3-288c9eca5925" +} +``` + +按命名空间过滤: + +```bash +tccli tcr DescribeWebhookTrigger \ + --RegistryId '' \ + --Namespace '' \ + --region +# expected: exit 0,返回该命名空间下的触发器列表 +``` + +### 3. 修改触发器 + +修改触发器的任意参数(名称、事件类型、启用状态、目标 URL 等)。`--Trigger` JSON 内需包含 `Id` 字段以标识目标规则。`Id` 来自 `DescribeWebhookTrigger` 输出的 `Id` 字段: + +```bash +tccli tcr ModifyWebhookTrigger \ + --RegistryId '' \ + --Trigger '{"Id":,"Name":"","Enabled":true,"Targets":[{"Address":"","Headers":[{"Key":"X-Custom","Values":[""]}]}],"EventTypes":["pushImage","pullImage"],"Condition":"pushImage","Description":"修改后的触发器"}' \ + --Namespace '' \ + --region +# expected: exit 0 +``` + +```json +{ + "RequestId": "" +} +``` + +### 4. 删除触发器 + +**`--Id`**(非 `--TriggerId`)来自 `DescribeWebhookTrigger` 输出中的 `Id` 字段。`--Namespace` 为必填顶层参数: + +```bash +tccli tcr DeleteWebhookTrigger \ + --RegistryId '' \ + --Id \ + --Namespace '' \ + --region +# expected: exit 0 +``` + +```json +{ + "RequestId": "d11b2944-ead8-4837-88ee-cb759d56c3ef" +} +``` + +| 占位符 | 说明 | 获取方式 | +|--------|------|---------| +| `` | 触发器数字 ID | `tccli tcr DescribeWebhookTrigger --RegistryId --Namespace --region ` 输出中的 `Id` 字段 | + +### 5. 查看触发日志 + +查看指定触发器的触发历史记录。`--Id` 为必填参数,`--Namespace` 为必填参数: + +```bash +tccli tcr DescribeWebhookTriggerLog \ + --RegistryId '' \ + --Id \ + --Namespace '' \ + --region +# expected: exit 0,返回触发日志列表 +``` + +```json +{ + "TotalCount": 2, + "Logs": [ + { + "Id": 1001, + "TriggerId": 1, + "EventType": "pushImage", + "Repository": "team-a/nginx", + "Tag": "v1.10.0", + "Status": "SUCCESS", + "CreationTime": "2025-06-03 14:30:00 +0800 CST", + "Detail": "HTTP 200 OK" + }, + { + "Id": 1002, + "TriggerId": 1, + "EventType": "pushImage", + "Repository": "team-a/nginx", + "Tag": "v1.9.0", + "Status": "FAILED", + "CreationTime": "2025-06-03 13:00:00 +0800 CST", + "Detail": "HTTP 500 Internal Server Error" + } + ], + "RequestId": "" +} +``` + +**日志字段说明:** + +| 字段 | 说明 | +|------|------| +| `TriggerId` | 所属触发器 ID | +| `EventType` | 触发事件类型 | +| `Repository` | 产生触发的仓库(`namespace/repo` 格式) | +| `Tag` | 触发版本的 Tag | +| `Status` | 执行状态:`SUCCESS`(成功)、`FAILED`(失败) | +| `Detail` | 请求结果详情(HTTP 状态码或错误信息) | + +## 验证 + +### Control plane (tccli) + +```bash +# 确认触发器存在且配置正确 +tccli tcr DescribeWebhookTrigger \ + --RegistryId '' \ + --Namespace '' \ + --region +# expected: 目标触发器 Enabled 为 true,EventTypes 和 Condition 符合预期 + +# 查看触发日志确认已有事件触发 +tccli tcr DescribeWebhookTriggerLog \ + --RegistryId '' \ + --Id \ + --Namespace '' \ + --region +# expected: 返回日志列表,Status 为 SUCCESS 或 FAILED(取决于目标 URL 可达性) +``` + +验证维度: + +| 维度 | 命令 | 预期 | +|------|------|------| +| 存在性 | `DescribeWebhookTrigger --Namespace ` | 目标触发器在列表中 | +| 启用状态 | 同上,检查 `Enabled` | `true` | +| 事件类型 | 同上,检查 `EventTypes` | 与创建时指定的一致 | +| 触发日志 | `DescribeWebhookTriggerLog --Id ` | 有日志记录,确认触发器已响应事件 | + +## 清理 + +### Control plane (tccli) + +> **注意**:删除触发器不会影响已发出的 Webhook 请求。删除操作不可逆,确认 `Id` 正确后再执行。 + +```bash +# 清理前确认目标触发器 +tccli tcr DescribeWebhookTrigger \ + --RegistryId '' \ + --Namespace '' \ + --region +# 确认 Id 与待删除的一致 + +# 删除触发器 +tccli tcr DeleteWebhookTrigger \ + --RegistryId '' \ + --Id \ + --Namespace '' \ + --region +# expected: exit 0 + +# 验证已删除 +tccli tcr DescribeWebhookTrigger \ + --RegistryId '' \ + --Namespace '' \ + --region +# expected: 目标触发器不再出现在列表中 +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `CreateWebhookTrigger` 返回 `the following arguments are required: --Namespace` | 检查 CLI 命令中是否有 `--Namespace` 参数 | `--Namespace` 是 CLI 顶层独立参数,**不在 `--Trigger` JSON 内部**。漏写导致 tccli 参数验证失败 | 在 CLI 命令中加上 `--Namespace ''`,作为独立参数(与 `--RegistryId`、`--Trigger` 并列) | +| `CreateWebhookTrigger` 返回 `InvalidParameter.ErrorTcrInvalidParameter` 提示 "Name: non zero value required" | 检查 `--Trigger` JSON 中是否包含 `Name` 字段 | `Name` 为 Trigger JSON 的必填字段 | 确保 Trigger JSON 包含 `"Name":""` | +| `CreateWebhookTrigger` 返回 `InvalidParameter.ErrorTcrInvalidParameter` 提示 "Enabled: non zero value required" | 检查 `--Trigger` JSON 中是否包含 `Enabled` 字段 | `Enabled` 为 Trigger JSON 的必填字段 | 确保 Trigger JSON 包含 `"Enabled":true` 或 `"Enabled":false` | +| `CreateWebhookTrigger` 返回 `InvalidParameter` 提示 "Trigger.Condition 取值类型错误。参数类型应为 string" | 检查 `Condition` 是否为 JSON 对象(`{...}`)而非字符串(`"..."`) | `Condition` 是 **String 类型**,不能传 Object。若写成 `"Condition":{"EventType":"pushImage"}` 会触发此错误 | 将 Condition 改为字符串:简单场景用 `"Condition":"pushImage"`,高级场景用 JSON 字符串 `"Condition":"{\"EventType\":\"pushImage\",\"Filter\":{...}}"` | +| `CreateWebhookTrigger` 返回 `InternalError.ErrorTcrInternal` 内嵌 "notification policy named ... already exists" | `tccli tcr DescribeWebhookTrigger --RegistryId --Namespace --region ` 查看已有触发器名称 | 同一命名空间下触发器名称不可重复(注:TCR 将此错误包装在 `InternalError.ErrorTcrInternal` 内,实际根因在嵌套 JSON 的 error message 中) | 修改 `Name` 字段为唯一名称 | +| `DeleteWebhookTrigger` 返回 `the following arguments are required: --Namespace, --Id` | 检查 CLI 命令中是否用了 `--TriggerId` 而非 `--Id` | **删除参数是 `--Id`,不是 `--TriggerId`**。同时 `--Namespace` 也是删除操作的必填参数 | 使用 `--Id --Namespace ''` 替代 `--TriggerId` | +| `DeleteWebhookTrigger` 返回 `InternalError.ErrorTcrInternal` 内嵌 "notificationPolicy ... not found" | `tccli tcr DescribeWebhookTrigger --RegistryId --Offset 0 --Limit 50 --region ` 确认 ID 是否存在 | 触发器 ID 不存在(注:TCR 将此错误包装在 `InternalError.ErrorTcrInternal` 内,非独立 `ResourceNotFound` 错误码) | 通过 `DescribeWebhookTrigger` 确认正确的 `Id` | + +### 创建成功但行为异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 触发器已创建但不触发 | `tccli tcr DescribeWebhookTriggerLog --RegistryId --Id --Namespace --region ` 查看触发日志 | `Condition` 正则不匹配实际事件、`Enabled` 为 `false`、或目标 URL 不可达 | 检查 `Condition` 过滤条件是否匹配;确认 `Enabled: true`;用 `curl -X POST ` 验证目标 URL 可达性 | +| 触发日志显示 `FAILED` | 查看触发日志中 `Detail` 字段 | 目标 URL 返回非 2xx HTTP 状态码(如 500) | 检查 Webhook Server 端日志,确认服务正常处理 POST 请求 | +| 触发器已达配额上限 | — | 单实例触发器数量受限 | 先删除不再使用的触发器 | +| CAM 权限拒绝 | — | 子账号缺少触发器操作权限 | 确认子账号已配置 `tcr:CreateWebhookTrigger`、`tcr:ModifyWebhookTrigger`、`tcr:DescribeWebhookTrigger`、`tcr:DeleteWebhookTrigger` 等权限 | + +> **关于 TCR 错误码包装**:TCR 将部分域名错误(如触发器名字重复、ID 不存在)统一包装在 `InternalError.ErrorTcrInternal`(或 `InternalError.ErrorTcrUnauthorized`)通用错误码内,而非返回独立的语义化错误码(如 `InvalidParameter.ErrTriggerExist`、`ResourceNotFound.ErrNoTrigger`)。排障时需查看嵌套 JSON 中的 message 文本获取实际错误原因。 + +## Webhook 请求格式参考 + +触发器执行时,向目标 URL 发送的 HTTP POST 请求 Body 格式如下: + +```json +{ + "type": "pushImage", + "occur_at": 1589106605, + "event_data": { + "resources": [ + { + "digest": "sha256:89a42c3ba15f09a3fbe39856bddacdf9e94cd03df7403cad4fc105xxxx268fc9", + "tag": "v1.10.0", + "resource_url": "xxx-bj.tencentcloudcr.com/public/nginx:v1.10.0" + } + ], + "repository": { + "date_created": 1587119137, + "name": "nginx", + "namespace": "public", + "repo_full_name": "public/nginx", + "repo_type": "public" + } + }, + "operator": "332133xxxx" +} +``` + +## 下一步 + +- [自动删除镜像版本](../../../image-cleanup/auto-delete) +- [容器镜像安全扫描](../../../image-security/vulnerability-scan) +- [镜像版本不可变](../../../image-security/immutable-tags) +- [管理命名空间](../../../image-creation/namespace) + +## 控制台替代 + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 选择实例 → 左侧导航栏 **触发器** → 单击 **新建**,配置名称、触发动作、命名空间、仓库/版本过滤、目标 URL 和 Header 后确认。在触发器列表可启用/禁用、重新配置或删除规则,单击规则名称查看触发日志。 diff --git a/src/content/docs/cli/tcr/ops/devops/webhook/webhook-tcrctl.md b/src/content/docs/cli/tcr/ops/devops/webhook/webhook-tcrctl.md new file mode 100644 index 0000000..ea2ba4d --- /dev/null +++ b/src/content/docs/cli/tcr/ops/devops/webhook/webhook-tcrctl.md @@ -0,0 +1,43 @@ +--- +title: "触发器(Webhook)(tcrctl)" +description: "· page_id `44369`" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> 对照官方:[触发器(Webhook)](https://cloud.tencent.com/document/product/1141/44369) · page_id `44369` +> +> 本页为 P1 产品 CLI 操作页,将在 P1 阶段(产品文档 agentic 化)中完成正文。 + +## Overview + +(待完成)使用 `tcrctl` 管理实例触发器的创建、配置、删除与触发日志查看。 + +## Before you begin + +(待完成) + +## Procedure + +(待完成) + +## Verify + +(待完成) + +## Cleanup + +(待完成) + +## Troubleshoot + +(待完成) + +## Next steps + +- [自动删除镜像版本](../../image-cleanup/auto-delete) +- [容器镜像安全扫描](../../image-security/vulnerability-scan) + +## Console alternative + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 选择实例 → 左侧导航栏 **触发器**。 diff --git a/src/content/docs/cli/tcr/ops/devops/webhook/webhook.md b/src/content/docs/cli/tcr/ops/devops/webhook/webhook.md new file mode 100644 index 0000000..814355f --- /dev/null +++ b/src/content/docs/cli/tcr/ops/devops/webhook/webhook.md @@ -0,0 +1,404 @@ +--- +title: "触发器(Webhook)(tccli)" +description: "· page_id `44369`" +--- + +> 对照官方:[触发器(Webhook)](https://cloud.tencent.com/document/product/1141/44369) · page_id `44369` + +## 概述 + +TCR 企业版支持配置 Webhook 触发器,在镜像推送/拉取/删除或 Helm Chart 推送/拉取/删除时自动向指定 URL 发起 HTTP POST 请求,便于接入 CI/CD 流程实现自动部署等 DevOps 场景。 + +触发器支持的 `EventTypes`: + +| 事件类型 | 说明 | +|---------|------| +| `pushImage` | 推送镜像 | +| `pullImage` | 拉取镜像 | +| `deleteImage` | 删除镜像 | +| `chartPush` | 推送 Helm Chart | +| `chartPull` | 拉取 Helm Chart | +| `chartDelete` | 删除 Helm Chart | + +`Condition` 字段为字符串类型,支持事件类型过滤(如 `"pushImage"`)或 JSON 格式的正则过滤(含命名空间、仓库名、Tag 匹配规则)。 + +> **注意:** 本功能仅适用于 **TCR 企业版**实例。个人版不支持。 + +## 前置条件 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域 + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tcr:CreateWebhookTrigger, tcr:DescribeWebhookTrigger, tcr:ModifyWebhookTrigger +# tcr:DeleteWebhookTrigger, tcr:DescribeWebhookTriggerLog +# 验证:执行 DescribeWebhookTrigger 确认权限 +tccli tcr DescribeWebhookTrigger --region --RegistryId --Limit 1 +# expected: exit 0,返回触发器列表(可为空) +``` + +### 资源检查 + +```bash +# 4. 确认实例存在且状态为 Running +tccli tcr DescribeInstances --region --RegistryIds '[""]' +# expected: Status 为 "Running" + +# 5. 确认命名空间存在 — 触发器必须在已有命名空间下创建 +tccli tcr DescribeNamespaces --region --RegistryId +# expected: 至少有一个命名空间,记录目标 NamespaceName + +# 6. 确认 Webhook 目标 URL 已就绪(可选,但未就绪则触发时请求失败) +# 触发器创建时不校验目标 URL 可达性,建议先用 curl 验证 +curl -X POST -I +# expected: HTTP 响应(2xx 或 4xx 均可,证明可达) +``` + +## 关键字段说明 + +以下说明 `CreateWebhookTrigger` 中 `--Trigger` JSON 的主要字段。完整参数定义见 API 文档。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `Name` | String | 是 | 规则名称,支持小写字母、数字及 `-`、`.`、`_`,须以字母数字开头。同命名空间下不可重复 | 缺此字段 → `InvalidParameter.ErrorTcrInvalidParameter`:"Name: non zero value required" | +| `Enabled` | Boolean | 是 | `true`(启用)或 `false`(禁用) | 缺此字段 → `InvalidParameter.ErrorTcrInvalidParameter`:"Enabled: non zero value required" | +| `Targets` | Array | 是 | Webhook 目标列表。每个元素含 `Address`(目标 URL,String)和可选 `Headers`(Array,每项含 `Key` 和 `Values`) | 空数组或格式错误 → 创建失败 | +| `EventTypes` | Array | 是 | 触发事件类型数组,枚举值:`pushImage`、`pullImage`、`deleteImage`、`chartPush`、`chartPull`、`chartDelete` | 无此字段 → 创建失败 | +| `Condition` | **String** | 是 | 触发条件字符串。简单场景填事件类型(如 `"pushImage"`);高级场景填 JSON 字符串(含 `EventType` 和 `Filter` 对象)。**注意:Condition 是 String 类型,不是 Object** | 填成 Object → `InvalidParameter`:"Trigger.Condition 取值类型错误。参数类型应为 string" | +| `Description` | String | 否 | 规则描述,支持中文 | — | + +> **重要:** `--Namespace` 是 CLI 顶层独立参数,**不在 `--Trigger` JSON 内部**。缺少此参数将导致命令失败。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 查看触发器列表 | `tccli tcr DescribeWebhookTrigger --RegistryId ` | 是 | +| 新建触发器 | `tccli tcr CreateWebhookTrigger --RegistryId --Trigger '{...}' --Namespace ` | 否(同名冲突) | +| 修改触发器 | `tccli tcr ModifyWebhookTrigger --RegistryId --Trigger '{...}' --Namespace ` | 是 | +| 删除触发器 | `tccli tcr DeleteWebhookTrigger --RegistryId --Id --Namespace ` | 否 | +| 查看触发日志 | `tccli tcr DescribeWebhookTriggerLog --RegistryId --Id --Namespace ` | 是 | + +## 操作步骤 + +### 1. 创建触发器 + +#### 选择依据 + +- **EventTypes**:按实际需求选择事件类型。常见场景:CI/CD 镜像构建完成后触发自动部署 → 选 `pushImage`;镜像版本淘汰 → 选 `deleteImage`。 +- **Condition**:简单场景用事件类型字符串(如 `"pushImage"`),高级场景用 JSON 字符串做正则过滤(按命名空间、仓库名、Tag 过滤)。 +- **Namespace**:`--Namespace` 是 CLI 顶层独立参数,传命名空间名称(非 ID)。不是 `--Trigger` JSON 的一部分。 + +#### 最小创建(仅必填字段,监听 pushImage 事件) + +```bash +tccli tcr CreateWebhookTrigger \ + --RegistryId '' \ + --Trigger '{"Name":"","Enabled":true,"Targets":[{"Address":""}],"EventTypes":["pushImage"],"Condition":"pushImage"}' \ + --Namespace '' \ + --region +# expected: exit 0,返回 Trigger 对象含 Id +``` + +```json +{ + "Trigger": { + "Name": "rewrite-test-webhook", + "Id": 1, + "Enabled": true, + "EventTypes": ["pushImage", "pullImage"], + "Condition": "pushImage" + }, + "RequestId": "06af6288-50d6-4585-a2ba-8245ecd15107" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | 实例 ID | 格式 `tcr-xxxxxxxx` | `tccli tcr DescribeInstances --region ` | +| `` | 触发器名称 | 支持小写字母、数字及 `-`、`.`、`_`,须以字母数字开头 | 自定义,同命名空间不可重复 | +| `` | Webhook 目标 URL | 完整 HTTP/HTTPS 地址 | 由 Webhook Server 提供 | +| `` | 命名空间名称 | 已存在的命名空间 | `tccli tcr DescribeNamespaces --region --RegistryId ` | +| `` | 地域 | 如 `ap-guangzhou` | `tccli tcr DescribeRegions` | + +#### 增强配置(多事件 + 自定义 Header) + +```bash +tccli tcr CreateWebhookTrigger \ + --RegistryId '' \ + --Trigger '{"Name":"","Enabled":true,"Targets":[{"Address":"","Headers":[{"Key":"X-Custom-Auth","Values":[""]}]}],"EventTypes":["pushImage","pullImage","deleteImage"],"Condition":"pushImage","Description":"生产环境自动部署触发器"}' \ + --Namespace '' \ + --region +# expected: exit 0,返回 Trigger 对象含 Id +``` + +#### 高级过滤(Condition 用 JSON 字符串做正则匹配) + +```bash +tccli tcr CreateWebhookTrigger \ + --RegistryId '' \ + --Trigger '{"Name":"","Enabled":true,"Targets":[{"Address":""}],"EventTypes":["pushImage"],"Condition":"{\"EventType\":\"pushImage\",\"Filter\":{\"Namespace\":\"\",\"Repositories\":[\"\"],\"Tags\":[\"v[0-9]+.*\"]}}"}' \ + --Namespace '' \ + --region +# expected: exit 0,返回 Trigger 对象含 Id +``` + +> **注意**:当 `Condition` 包含 JSON 内容时,需对内部引号做转义(`\"`)。因为 Shell 中 `'...'` 单引号内的双引号不需要转义,但 JSON 内的 `"` 在整个 `--Trigger` 值里已经是合法的 JSON 字符串字段值,只需确保 Condition 的值整体是合法 JSON 字符串即可。 + +### 2. 查看触发器列表 + +列出指定实例的所有触发器: + +```bash +tccli tcr DescribeWebhookTrigger \ + --RegistryId '' \ + --Limit 20 \ + --Offset 0 \ + --region +# expected: exit 0,返回触发器列表 +``` + +```json +{ + "TotalCount": 0, + "Triggers": [], + "RequestId": "d5d75baf-bd9c-4aff-9fb3-288c9eca5925" +} +``` + +按命名空间过滤: + +```bash +tccli tcr DescribeWebhookTrigger \ + --RegistryId '' \ + --Namespace '' \ + --region +# expected: exit 0,返回该命名空间下的触发器列表 +``` + +### 3. 修改触发器 + +修改触发器的任意参数(名称、事件类型、启用状态、目标 URL 等)。`--Trigger` JSON 内需包含 `Id` 字段以标识目标规则。`Id` 来自 `DescribeWebhookTrigger` 输出的 `Id` 字段: + +```bash +tccli tcr ModifyWebhookTrigger \ + --RegistryId '' \ + --Trigger '{"Id":,"Name":"","Enabled":true,"Targets":[{"Address":"","Headers":[{"Key":"X-Custom","Values":[""]}]}],"EventTypes":["pushImage","pullImage"],"Condition":"pushImage","Description":"修改后的触发器"}' \ + --Namespace '' \ + --region +# expected: exit 0 +``` + +```json +{ + "RequestId": "" +} +``` + +### 4. 删除触发器 + +**`--Id`**(非 `--TriggerId`)来自 `DescribeWebhookTrigger` 输出中的 `Id` 字段。`--Namespace` 为必填顶层参数: + +```bash +tccli tcr DeleteWebhookTrigger \ + --RegistryId '' \ + --Id \ + --Namespace '' \ + --region +# expected: exit 0 +``` + +```json +{ + "RequestId": "d11b2944-ead8-4837-88ee-cb759d56c3ef" +} +``` + +| 占位符 | 说明 | 获取方式 | +|--------|------|---------| +| `` | 触发器数字 ID | `tccli tcr DescribeWebhookTrigger --RegistryId --Namespace --region ` 输出中的 `Id` 字段 | + +### 5. 查看触发日志 + +查看指定触发器的触发历史记录。`--Id` 为必填参数,`--Namespace` 为必填参数: + +```bash +tccli tcr DescribeWebhookTriggerLog \ + --RegistryId '' \ + --Id \ + --Namespace '' \ + --region +# expected: exit 0,返回触发日志列表 +``` + +```json +{ + "TotalCount": 2, + "Logs": [ + { + "Id": 1001, + "TriggerId": 1, + "EventType": "pushImage", + "Repository": "team-a/nginx", + "Tag": "v1.10.0", + "Status": "SUCCESS", + "CreationTime": "2025-06-03 14:30:00 +0800 CST", + "Detail": "HTTP 200 OK" + }, + { + "Id": 1002, + "TriggerId": 1, + "EventType": "pushImage", + "Repository": "team-a/nginx", + "Tag": "v1.9.0", + "Status": "FAILED", + "CreationTime": "2025-06-03 13:00:00 +0800 CST", + "Detail": "HTTP 500 Internal Server Error" + } + ], + "RequestId": "" +} +``` + +**日志字段说明:** + +| 字段 | 说明 | +|------|------| +| `TriggerId` | 所属触发器 ID | +| `EventType` | 触发事件类型 | +| `Repository` | 产生触发的仓库(`namespace/repo` 格式) | +| `Tag` | 触发版本的 Tag | +| `Status` | 执行状态:`SUCCESS`(成功)、`FAILED`(失败) | +| `Detail` | 请求结果详情(HTTP 状态码或错误信息) | + +## 验证 + +### Control plane (tccli) + +```bash +# 确认触发器存在且配置正确 +tccli tcr DescribeWebhookTrigger \ + --RegistryId '' \ + --Namespace '' \ + --region +# expected: 目标触发器 Enabled 为 true,EventTypes 和 Condition 符合预期 + +# 查看触发日志确认已有事件触发 +tccli tcr DescribeWebhookTriggerLog \ + --RegistryId '' \ + --Id \ + --Namespace '' \ + --region +# expected: 返回日志列表,Status 为 SUCCESS 或 FAILED(取决于目标 URL 可达性) +``` + +验证维度: + +| 维度 | 命令 | 预期 | +|------|------|------| +| 存在性 | `DescribeWebhookTrigger --Namespace ` | 目标触发器在列表中 | +| 启用状态 | 同上,检查 `Enabled` | `true` | +| 事件类型 | 同上,检查 `EventTypes` | 与创建时指定的一致 | +| 触发日志 | `DescribeWebhookTriggerLog --Id ` | 有日志记录,确认触发器已响应事件 | + +## 清理 + +### Control plane (tccli) + +> **注意**:删除触发器不会影响已发出的 Webhook 请求。删除操作不可逆,确认 `Id` 正确后再执行。 + +```bash +# 清理前确认目标触发器 +tccli tcr DescribeWebhookTrigger \ + --RegistryId '' \ + --Namespace '' \ + --region +# 确认 Id 与待删除的一致 + +# 删除触发器 +tccli tcr DeleteWebhookTrigger \ + --RegistryId '' \ + --Id \ + --Namespace '' \ + --region +# expected: exit 0 + +# 验证已删除 +tccli tcr DescribeWebhookTrigger \ + --RegistryId '' \ + --Namespace '' \ + --region +# expected: 目标触发器不再出现在列表中 +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `CreateWebhookTrigger` 返回 `the following arguments are required: --Namespace` | 检查 CLI 命令中是否有 `--Namespace` 参数 | `--Namespace` 是 CLI 顶层独立参数,**不在 `--Trigger` JSON 内部**。漏写导致 tccli 参数验证失败 | 在 CLI 命令中加上 `--Namespace ''`,作为独立参数(与 `--RegistryId`、`--Trigger` 并列) | +| `CreateWebhookTrigger` 返回 `InvalidParameter.ErrorTcrInvalidParameter` 提示 "Name: non zero value required" | 检查 `--Trigger` JSON 中是否包含 `Name` 字段 | `Name` 为 Trigger JSON 的必填字段 | 确保 Trigger JSON 包含 `"Name":""` | +| `CreateWebhookTrigger` 返回 `InvalidParameter.ErrorTcrInvalidParameter` 提示 "Enabled: non zero value required" | 检查 `--Trigger` JSON 中是否包含 `Enabled` 字段 | `Enabled` 为 Trigger JSON 的必填字段 | 确保 Trigger JSON 包含 `"Enabled":true` 或 `"Enabled":false` | +| `CreateWebhookTrigger` 返回 `InvalidParameter` 提示 "Trigger.Condition 取值类型错误。参数类型应为 string" | 检查 `Condition` 是否为 JSON 对象(`{...}`)而非字符串(`"..."`) | `Condition` 是 **String 类型**,不能传 Object。若写成 `"Condition":{"EventType":"pushImage"}` 会触发此错误 | 将 Condition 改为字符串:简单场景用 `"Condition":"pushImage"`,高级场景用 JSON 字符串 `"Condition":"{\"EventType\":\"pushImage\",\"Filter\":{...}}"` | +| `CreateWebhookTrigger` 返回 `InternalError.ErrorTcrInternal` 内嵌 "notification policy named ... already exists" | `tccli tcr DescribeWebhookTrigger --RegistryId --Namespace --region ` 查看已有触发器名称 | 同一命名空间下触发器名称不可重复(注:TCR 将此错误包装在 `InternalError.ErrorTcrInternal` 内,实际根因在嵌套 JSON 的 error message 中) | 修改 `Name` 字段为唯一名称 | +| `DeleteWebhookTrigger` 返回 `the following arguments are required: --Namespace, --Id` | 检查 CLI 命令中是否用了 `--TriggerId` 而非 `--Id` | **删除参数是 `--Id`,不是 `--TriggerId`**。同时 `--Namespace` 也是删除操作的必填参数 | 使用 `--Id --Namespace ''` 替代 `--TriggerId` | +| `DeleteWebhookTrigger` 返回 `InternalError.ErrorTcrInternal` 内嵌 "notificationPolicy ... not found" | `tccli tcr DescribeWebhookTrigger --RegistryId --Offset 0 --Limit 50 --region ` 确认 ID 是否存在 | 触发器 ID 不存在(注:TCR 将此错误包装在 `InternalError.ErrorTcrInternal` 内,非独立 `ResourceNotFound` 错误码) | 通过 `DescribeWebhookTrigger` 确认正确的 `Id` | + +### 创建成功但行为异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 触发器已创建但不触发 | `tccli tcr DescribeWebhookTriggerLog --RegistryId --Id --Namespace --region ` 查看触发日志 | `Condition` 正则不匹配实际事件、`Enabled` 为 `false`、或目标 URL 不可达 | 检查 `Condition` 过滤条件是否匹配;确认 `Enabled: true`;用 `curl -X POST ` 验证目标 URL 可达性 | +| 触发日志显示 `FAILED` | 查看触发日志中 `Detail` 字段 | 目标 URL 返回非 2xx HTTP 状态码(如 500) | 检查 Webhook Server 端日志,确认服务正常处理 POST 请求 | +| 触发器已达配额上限 | — | 单实例触发器数量受限 | 先删除不再使用的触发器 | +| CAM 权限拒绝 | — | 子账号缺少触发器操作权限 | 确认子账号已配置 `tcr:CreateWebhookTrigger`、`tcr:ModifyWebhookTrigger`、`tcr:DescribeWebhookTrigger`、`tcr:DeleteWebhookTrigger` 等权限 | + +> **关于 TCR 错误码包装**:TCR 将部分域名错误(如触发器名字重复、ID 不存在)统一包装在 `InternalError.ErrorTcrInternal`(或 `InternalError.ErrorTcrUnauthorized`)通用错误码内,而非返回独立的语义化错误码(如 `InvalidParameter.ErrTriggerExist`、`ResourceNotFound.ErrNoTrigger`)。排障时需查看嵌套 JSON 中的 message 文本获取实际错误原因。 + +## Webhook 请求格式参考 + +触发器执行时,向目标 URL 发送的 HTTP POST 请求 Body 格式如下: + +```json +{ + "type": "pushImage", + "occur_at": 1589106605, + "event_data": { + "resources": [ + { + "digest": "sha256:89a42c3ba15f09a3fbe39856bddacdf9e94cd03df7403cad4fc105xxxx268fc9", + "tag": "v1.10.0", + "resource_url": "xxx-bj.tencentcloudcr.com/public/nginx:v1.10.0" + } + ], + "repository": { + "date_created": 1587119137, + "name": "nginx", + "namespace": "public", + "repo_full_name": "public/nginx", + "repo_type": "public" + } + }, + "operator": "332133xxxx" +} +``` + +## 下一步 + +- [自动删除镜像版本](../../../image-cleanup/auto-delete) +- [容器镜像安全扫描](../../../image-security/vulnerability-scan) +- [镜像版本不可变](../../../image-security/immutable-tags) +- [管理命名空间](../../../image-creation/namespace) + +## 控制台替代 + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 选择实例 → 左侧导航栏 **触发器** → 单击 **新建**,配置名称、触发动作、命名空间、仓库/版本过滤、目标 URL 和 Header 后确认。在触发器列表可启用/禁用、重新配置或删除规则,单击规则名称查看触发日志。 diff --git a/src/content/docs/cli/tcr/ops/image-cleanup/auto-delete-tcrctl.md b/src/content/docs/cli/tcr/ops/image-cleanup/auto-delete-tcrctl.md new file mode 100644 index 0000000..02e7fef --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-cleanup/auto-delete-tcrctl.md @@ -0,0 +1,42 @@ +--- +title: "自动删除镜像版本(tcrctl)" +description: "· page_id `50613`" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> 对照官方:[自动删除镜像版本](https://cloud.tencent.com/document/product/1141/50613) · page_id `50613` +> +> 本页为 P1 产品 CLI 操作页,将在 P1 阶段(产品文档 agentic 化)中完成正文。 + +## Overview + +(待完成)使用 `tcrctl` 管理实例版本保留规则及执行记录。 + +## Before you begin + +(待完成) + +## Procedure + +(待完成) + +## Verify + +(待完成) + +## Cleanup + +(待完成) + +## Troubleshoot + +(待完成) + +## Next steps + +- [清理 COS 存储空间](https://cloud.tencent.com/document/product/1141/58157) + +## Console alternative + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 选择实例 → **版本管理** → **版本保留**。 diff --git a/src/content/docs/cli/tcr/ops/image-cleanup/auto-delete.md b/src/content/docs/cli/tcr/ops/image-cleanup/auto-delete.md new file mode 100644 index 0000000..6330003 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-cleanup/auto-delete.md @@ -0,0 +1,453 @@ +--- +title: "自动删除镜像版本(tccli)" +description: "· page_id `50613`" +--- + +> 对照官方:[自动删除镜像版本](https://cloud.tencent.com/document/product/1141/50613) · page_id `50613` + +## 概述 + +TCR 企业版支持镜像版本保留功能,允许自定义创建版本保留规则,定时触发并自动删除保留规则外的镜像版本。支持两种保留策略:保留最新推送的 N 个版本(`latestPushedK`)、保留 N 天内推送的版本(`nDaysSinceLastPush`),并支持模拟执行(`DryRun`)。高级配置模式下可对仓库、版本进行正则过滤,配合两种保留策略实现灵活精确的版本管理。 + +**重要注意事项:** + +1. 版本保留功能将**删除保留规则外的镜像版本**,仅删除镜像版本信息,不删除底层镜像数据。如需彻底清理镜像数据,请使用 [清理 COS 存储空间功能](https://cloud.tencent.com/document/product/1141/58157)。 +2. 若镜像仓库中存在多个不同 Tag 但相同 Digest 的镜像(即同一镜像内容打了不同 Tag),因保留规则仅保留指定数量的 Tag,删除时将一并删除共享同一 Digest 的所有 Tag。**在此场景下请谨慎使用版本保留规则,或暂时禁用已有规则。** +3. 单个命名空间内暂只能创建一条版本保留规则。 + +## 前置条件 + +- 已 [创建企业版实例](../../create),实例状态为 `Running`。 +- 实例内已创建至少一个命名空间,且命名空间下有镜像仓库及镜像版本。 +- 已配置 `tccli` 凭证(参见 [环境准备](../../../index.md))。 +- 已获取命名空间的数字 ID(`NamespaceId`)。`tccli tcr DescribeNamespaces --RegistryId ` 可获取命名空间对应的 `NamespaceId`(Integer 类型,非 `NamespaceName` 字符串)。 + +本页以 `RegistryId=tcr-nn8smeyj`、命名空间 `kerwinwjyan-test`(`NamespaceId=2`)为例。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | CLI | 幂等 | +|-----------|-----|------| +| 列出版本保留规则 | `tccli tcr DescribeTagRetentionRules --RegistryId ` | 是 | +| 新建规则(简易配置) | `tccli tcr CreateTagRetentionRule --cli-input-json file://rule-simple.json` | 否(同命名空间仅允许一条) | +| 新建规则(高级配置) | `tccli tcr CreateTagRetentionRule --cli-input-json file://rule-advanced.json` | 否(同命名空间仅允许一条) | +| 修改规则 | `tccli tcr ModifyTagRetentionRule --cli-input-json file://rule-modify.json` | 是 | +| 删除规则 | `tccli tcr DeleteTagRetentionRule --RegistryId --RetentionId ` | 否 | +| 立即执行(真实删除) | `tccli tcr CreateTagRetentionExecution --RegistryId --RetentionId ` | 是 | +| 模拟执行 | `tccli tcr CreateTagRetentionExecution --RegistryId --RetentionId --DryRun true` | 是 | +| 查看执行日志 | `tccli tcr DescribeTagRetentionExecution --RegistryId --RetentionId ` | 是 | +| 查看执行任务详情 | `tccli tcr DescribeTagRetentionExecutionTask --RegistryId --RetentionId --ExecutionId ` | 是 | + +## 操作步骤 + +### 创建版本保留规则 + +首先查看当前实例下已有规则列表(实例 `tcr-nn8smeyj`,命名空间 `kerwinwjyan-test`,`NamespaceId=2`): + +```bash +tccli tcr DescribeTagRetentionRules --RegistryId tcr-nn8smeyj --region ap-guangzhou --output json +``` + +```json +{ + "RetentionPolicyList": [], + "TotalCount": 0, + "RequestId": "dadd8a07-cf11-4ab7-9bd3-867437ce38ea" +} +``` + +当前命名空间下无已有规则,可以新建。 + +> **关键提示**:`NamespaceId` 是**整数类型**(Integer),对应 TCR 命名空间的数字 ID,**不是**字符串类型的 `NamespaceName`。使用 `tccli tcr DescribeNamespaces --RegistryId ` 获取命名空间对应的 `NamespaceId` 数值。 + +#### 简易配置模式 + +适合对命名空间内全部仓库及版本统一应用保留策略。`RetentionRule` 为单一保留规则对象,`CronSetting` 定义执行周期。 + +**第一次尝试(失败):误用 `--NamespaceName` 字符串参数** + +新手容易直接传入命名空间名称字符串,但 API 要求的是 `--NamespaceId`(Integer): + +```bash +# WRONG: --NamespaceName 是字符串,API 不接受 +tccli tcr CreateTagRetentionRule \ + --RegistryId tcr-nn8smeyj \ + --NamespaceName kerwinwjyan-test \ + --CronSetting daily \ + --RetentionRule '{"Key":"latestPushedK","Value":30}' \ + --region ap-guangzhou \ + --output json +``` + +**错误输出**: + +``` +the following arguments are required: --NamespaceId +``` + +**报错原因**:`CreateTagRetentionRule` 的 `NamespaceId` 参数是 `--NamespaceId`(Integer),不是 `--NamespaceName`(String)。tccli 认为未提供必填参数 `--NamespaceId`。 + +**第二次尝试(成功):使用 `--NamespaceId 2`** + +```json title="examples/tcr-retention-simple.json" +{ + "RegistryId": "tcr-nn8smeyj", + "NamespaceId": 2, + "CronSetting": "daily", + "RetentionRule": { + "Key": "nDaysSinceLastPush", + "Value": 30 + }, + "Disabled": false +} +``` + +| 字段 | 说明 | +|------|------| +| `CronSetting` | 执行周期:`daily`(每天零点)、`weekly`(每周一零点)、`monthly`(每月第一天零点) | +| `RetentionRule.Key` | 保留策略类型:`latestPushedK`(保留最新推送的 K 个版本)、`nDaysSinceLastPush`(保留最近 N 天内推送的版本) | +| `RetentionRule.Value` | 策略值。`latestPushedK` 时为保留的版本数;`nDaysSinceLastPush` 时为保留的天数 | +| `Disabled` | 是否禁用规则。`false` 为启用 | + +```bash +tccli tcr CreateTagRetentionRule --cli-input-json ./examples/tcr-retention-simple.json --region ap-guangzhou --output json +``` + +```json +{ + "RequestId": "d9b8d078-d118-482a-8ae9-5c8d32afea10" +} +``` + +#### 高级配置模式 + +适合对命名空间内特定仓库或版本进行过滤,并配置多条保留规则(规则间取并集)。`AdvancedRuleItems` 为规则数组。 + +```json title="examples/tcr-retention-advanced.json" +{ + "RegistryId": "tcr-nn8smeyj", + "NamespaceId": 2, + "CronSetting": "monthly", + "RetentionRule": { + "Key": "nDaysSinceLastPush", + "Value": 30 + }, + "AdvancedRuleItems": [ + { + "Key": "latestPushedK", + "Value": 10, + "RepositoryFilter": { + "Decoration": "repoMatches", + "Pattern": "release/**" + }, + "TagFilter": { + "Decoration": "matches", + "Pattern": "v[0-9]+.*" + } + }, + { + "Key": "nDaysSinceLastPush", + "Value": 7, + "RepositoryFilter": { + "Decoration": "repoMatches", + "Pattern": "**" + }, + "TagFilter": { + "Decoration": "excludes", + "Pattern": "latest" + } + } + ], + "Disabled": false +} +``` + +| 字段 | 说明 | +|------|------| +| `AdvancedRuleItems[].Key` | 保留策略类型(同简易配置) | +| `AdvancedRuleItems[].Value` | 策略值(同简易配置) | +| `AdvancedRuleItems[].RepositoryFilter.Decoration` | 仓库过滤方式:`repoMatches`(匹配)、`repoExcludes`(排除) | +| `AdvancedRuleItems[].RepositoryFilter.Pattern` | 仓库名正则。`**` 匹配所有;`release/**` 匹配 release 下的所有子仓库 | +| `AdvancedRuleItems[].TagFilter.Decoration` | 版本过滤方式:`matches`(匹配)、`excludes`(排除) | +| `AdvancedRuleItems[].TagFilter.Pattern` | 版本名正则。`*` 匹配任意字符串(不跨 `/`);`**` 跨级匹配;`?` 匹配除 `/` 外的单个字符 | + +```bash +tccli tcr CreateTagRetentionRule --cli-input-json ./examples/tcr-retention-advanced.json --region ap-guangzhou --output json +``` + +```json +{ + "RequestId": "e7f8a9b0-c1d2-3456-efgh-789012345678" +} +``` + +### 查看版本保留规则 + +创建规则后,查看当前规则列表确认配置: + +```bash +tccli tcr DescribeTagRetentionRules --RegistryId tcr-nn8smeyj --region ap-guangzhou --output json +``` + +```json +{ + "RetentionPolicyList": [ + { + "RetentionId": 1, + "NamespaceName": "kerwinwjyan-test", + "RetentionRuleList": [ + { + "Key": "nDaysSinceLastPush", + "Value": 30 + } + ], + "AdvancedRuleItems": [], + "CronSetting": "daily", + "Disabled": false, + "NextExecutionTime": "2026-06-19T00:00:00+08:00" + } + ], + "TotalCount": 1, + "RequestId": "b1c2d3e4-f5a6-7890-abcd-ef1234567890" +} +``` + +支持按命名空间名称过滤(可选): + +```bash +tccli tcr DescribeTagRetentionRules \ + --RegistryId tcr-nn8smeyj \ + --NamespaceName kerwinwjyan-test \ + --region ap-guangzhou \ + --output json +``` + +### 修改规则 + +修改规则的保留策略、执行周期或启用/禁用状态。`RetentionId` 来自 `DescribeTagRetentionRules` 的输出。不可修改生效的命名空间。 + +```json title="examples/tcr-retention-modify.json" +{ + "RegistryId": "tcr-nn8smeyj", + "RetentionId": 1, + "NamespaceId": 2, + "CronSetting": "weekly", + "RetentionRule": { + "Key": "latestPushedK", + "Value": 50 + }, + "Disabled": false +} +``` + +```bash +tccli tcr ModifyTagRetentionRule --cli-input-json ./examples/tcr-retention-modify.json --region ap-guangzhou --output json +``` + +```json +{ + "RequestId": "f6a7b8c9-d0e1-2345-fabc-456789012345" +} +``` + +### 删除版本保留规则 + +删除使用 `--RetentionId` 参数(非 `--RetentionRuleId`)。 + +**第一次尝试(失败):误用 `--RetentionRuleId`** + +```bash +# WRONG: 参数名是 --RetentionId,不是 --RetentionRuleId +tccli tcr DeleteTagRetentionRule \ + --RegistryId tcr-nn8smeyj \ + --RetentionRuleId 1 \ + --region ap-guangzhou \ + --output json +``` + +**错误输出**: + +``` +the following arguments are required: --RetentionId +``` + +**报错原因**:`DeleteTagRetentionRule` 的必填参数是 `--RetentionId`,不是 `--RetentionRuleId`。tccli 参数校验按 API 定义精确匹配参数名。 + +**第二次尝试(成功):使用 `--RetentionId`** + +```bash +tccli tcr DeleteTagRetentionRule \ + --RegistryId tcr-nn8smeyj \ + --RetentionId 1 \ + --region ap-guangzhou \ + --output json +``` + +```json +{ + "RequestId": "1180a245-0d4c-4f6c-8c75-18f0b080c22e" +} +``` + +### 手动触发执行 + +可通过 API 手动触发规则执行(真实执行或模拟执行),无需等待 CronSetting 指定的周期。 + +#### 真实执行 + +真实执行将删除保留规则外的镜像版本: + +```bash +tccli tcr CreateTagRetentionExecution --RegistryId tcr-nn8smeyj --RetentionId 1 --region ap-guangzhou --output json +``` + +#### 模拟执行(DryRun) + +模拟执行用于确认规则是否生效,但不实际清理镜像版本: + +```bash +tccli tcr CreateTagRetentionExecution \ + --RegistryId tcr-nn8smeyj \ + --RetentionId 1 \ + --DryRun true \ + --region ap-guangzhou \ + --output json +``` + +```json +{ + "RequestId": "a7b8c9d0-e1f2-3456-abcd-567890123456" +} +``` + +### 查看执行日志 + +#### 查看规则的所有执行记录 + +```bash +tccli tcr DescribeTagRetentionExecution --RegistryId tcr-nn8smeyj --RetentionId 1 --region ap-guangzhou --output json +``` + +```json +{ + "RetentionExecutionList": [ + { + "ExecutionId": 101, + "RetentionId": 1, + "StartTime": "2026-06-17T00:00:00+08:00", + "EndTime": "2026-06-17T00:05:30+08:00", + "Status": "Success" + } + ], + "TotalCount": 1, + "RequestId": "b8c9d0e1-f2a3-4567-bcde-678901234567" +} +``` + +**字段说明:** + +| 字段 | 说明 | +|------|------| +| `ExecutionId` | 实例内唯一的执行任务 ID | +| `StartTime` / `EndTime` | 任务创建/完成时间(ISO 8601 格式),差值即为任务耗时 | +| `Status` | 执行状态:`Success`(成功)、`Failed`(失败)、`InProgress`(进行中) | + +#### 查看单次执行的详细任务 + +`ExecutionId` 来自执行记录列表。可查看每条规则在每个仓库中的具体执行情况: + +```bash +tccli tcr DescribeTagRetentionExecutionTask \ + --RegistryId tcr-nn8smeyj \ + --RetentionId 1 \ + --ExecutionId 101 \ + --region ap-guangzhou \ + --output json +``` + +```json +{ + "RetentionTaskList": [ + { + "TaskId": 1001, + "ExecutionId": 101, + "RetentionId": 1, + "StartTime": "2026-06-17T00:00:00+08:00", + "EndTime": "2026-06-17T00:01:30+08:00", + "Status": "Success", + "RepoName": "kerwinwjyan-test/test-repo", + "Total": 120, + "Retained": 30, + "Message": "" + } + ], + "TotalCount": 1, + "RequestId": "c9d0e1f2-a3b4-5678-cdef-789012345678" +} +``` + +**字段说明:** + +| 字段 | 说明 | +|------|------| +| `TaskId` | 任务 ID | +| `RepoName` | 仓库全名(`namespace/repo`) | +| `Total` | 该仓库内总版本数 | +| `Retained` | 保留的版本数 | +| `Status` | 任务状态 | + +## 验证 + +### Control plane (tccli) + +验证规则已按预期配置,并确认执行记录: + +```bash +tccli tcr DescribeTagRetentionRules --RegistryId tcr-nn8smeyj --region ap-guangzhou --output json +``` + +确认 `CronSetting` 已更新、保留策略及 `Disabled` 状态符合预期。 + +验证最新一次执行记录与任务详情: + +```bash +tccli tcr DescribeTagRetentionExecution --RegistryId tcr-nn8smeyj --RetentionId 1 --region ap-guangzhou --output json +tccli tcr DescribeTagRetentionExecutionTask --RegistryId tcr-nn8smeyj --RetentionId 1 --ExecutionId 101 --region ap-guangzhou --output json +``` + +## 清理 + +### Control plane (tccli) + +删除本次创建的测试规则: + +```bash +tccli tcr DeleteTagRetentionRule --RegistryId tcr-nn8smeyj --RetentionId 1 --region ap-guangzhou --output json +``` + +```json +{ + "RequestId": "1180a245-0d4c-4f6c-8c75-18f0b080c22e" +} +``` + +## 排障 + +| 现象 | 处理 | +|------|------| +| `CreateTagRetentionRule` 报错 `the following arguments are required: --NamespaceId` | 参数名是 `--NamespaceId`(Integer 类型),不是 `--NamespaceName`(String 类型)。使用 `tccli tcr DescribeNamespaces --RegistryId ` 获取命名空间对应的数字 ID,例如 `--NamespaceId 2` | +| `DeleteTagRetentionRule` 报错 `the following arguments are required: --RetentionId` | 参数名是 `--RetentionId`,不是 `--RetentionRuleId`。`RetentionId` 可从 `DescribeTagRetentionRules` 输出中获取,例如 `--RetentionId 1` | +| `NamespaceId` 未知 | 使用 `tccli tcr DescribeNamespaces --RegistryId ` 获取命名空间的数字 ID(`NamespaceList[].NamespaceId`) | +| 同一命名空间创建第二条规则报错 | 单个命名空间内仅允许创建一条版本保留规则,请先删除已有规则或使用其他命名空间 | +| 模拟执行后版本未被清理 | 模拟执行(`--DryRun true`)仅验证规则匹配,不实际删除;需常规执行(不加 `--DryRun`)以清理 | +| 保留规则内版本被意外删除 | 检查是否存在多个不同 Tag 但相同 Digest 的镜像版本,删除任一 Tag 可能影响共享同一 Digest 的所有 Tag | +| 执行耗时过长 | 仓库内版本数较多时执行耗时自然增加,可查看 `DescribeTagRetentionExecutionTask` 逐仓库确认进度 | +| CAM 权限拒绝 | 确认子账号已配置 TCR 相关操作权限,参见 [基于 CAM 管理子账号权限](https://cloud.tencent.com/document/product/1141/41417) | + +## 下一步 + +- [清理 COS 存储空间](https://cloud.tencent.com/document/product/1141/58157) — 彻底清理底层镜像数据 + +## 控制台替代 + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) -> 选择实例 -> **版本管理** -> **版本保留** -> 单击 **新建规则**,配置命名空间、保留策略、执行周期后确认。在规则列表行可查看日志、重新配置或删除规则。 diff --git a/src/content/docs/cli/tcr/ops/image-cleanup/auto-delete/auto-delete-tcrctl.md b/src/content/docs/cli/tcr/ops/image-cleanup/auto-delete/auto-delete-tcrctl.md new file mode 100644 index 0000000..02e7fef --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-cleanup/auto-delete/auto-delete-tcrctl.md @@ -0,0 +1,42 @@ +--- +title: "自动删除镜像版本(tcrctl)" +description: "· page_id `50613`" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> 对照官方:[自动删除镜像版本](https://cloud.tencent.com/document/product/1141/50613) · page_id `50613` +> +> 本页为 P1 产品 CLI 操作页,将在 P1 阶段(产品文档 agentic 化)中完成正文。 + +## Overview + +(待完成)使用 `tcrctl` 管理实例版本保留规则及执行记录。 + +## Before you begin + +(待完成) + +## Procedure + +(待完成) + +## Verify + +(待完成) + +## Cleanup + +(待完成) + +## Troubleshoot + +(待完成) + +## Next steps + +- [清理 COS 存储空间](https://cloud.tencent.com/document/product/1141/58157) + +## Console alternative + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 选择实例 → **版本管理** → **版本保留**。 diff --git a/src/content/docs/cli/tcr/ops/image-cleanup/auto-delete/auto-delete.md b/src/content/docs/cli/tcr/ops/image-cleanup/auto-delete/auto-delete.md new file mode 100644 index 0000000..6330003 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-cleanup/auto-delete/auto-delete.md @@ -0,0 +1,453 @@ +--- +title: "自动删除镜像版本(tccli)" +description: "· page_id `50613`" +--- + +> 对照官方:[自动删除镜像版本](https://cloud.tencent.com/document/product/1141/50613) · page_id `50613` + +## 概述 + +TCR 企业版支持镜像版本保留功能,允许自定义创建版本保留规则,定时触发并自动删除保留规则外的镜像版本。支持两种保留策略:保留最新推送的 N 个版本(`latestPushedK`)、保留 N 天内推送的版本(`nDaysSinceLastPush`),并支持模拟执行(`DryRun`)。高级配置模式下可对仓库、版本进行正则过滤,配合两种保留策略实现灵活精确的版本管理。 + +**重要注意事项:** + +1. 版本保留功能将**删除保留规则外的镜像版本**,仅删除镜像版本信息,不删除底层镜像数据。如需彻底清理镜像数据,请使用 [清理 COS 存储空间功能](https://cloud.tencent.com/document/product/1141/58157)。 +2. 若镜像仓库中存在多个不同 Tag 但相同 Digest 的镜像(即同一镜像内容打了不同 Tag),因保留规则仅保留指定数量的 Tag,删除时将一并删除共享同一 Digest 的所有 Tag。**在此场景下请谨慎使用版本保留规则,或暂时禁用已有规则。** +3. 单个命名空间内暂只能创建一条版本保留规则。 + +## 前置条件 + +- 已 [创建企业版实例](../../create),实例状态为 `Running`。 +- 实例内已创建至少一个命名空间,且命名空间下有镜像仓库及镜像版本。 +- 已配置 `tccli` 凭证(参见 [环境准备](../../../index.md))。 +- 已获取命名空间的数字 ID(`NamespaceId`)。`tccli tcr DescribeNamespaces --RegistryId ` 可获取命名空间对应的 `NamespaceId`(Integer 类型,非 `NamespaceName` 字符串)。 + +本页以 `RegistryId=tcr-nn8smeyj`、命名空间 `kerwinwjyan-test`(`NamespaceId=2`)为例。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | CLI | 幂等 | +|-----------|-----|------| +| 列出版本保留规则 | `tccli tcr DescribeTagRetentionRules --RegistryId ` | 是 | +| 新建规则(简易配置) | `tccli tcr CreateTagRetentionRule --cli-input-json file://rule-simple.json` | 否(同命名空间仅允许一条) | +| 新建规则(高级配置) | `tccli tcr CreateTagRetentionRule --cli-input-json file://rule-advanced.json` | 否(同命名空间仅允许一条) | +| 修改规则 | `tccli tcr ModifyTagRetentionRule --cli-input-json file://rule-modify.json` | 是 | +| 删除规则 | `tccli tcr DeleteTagRetentionRule --RegistryId --RetentionId ` | 否 | +| 立即执行(真实删除) | `tccli tcr CreateTagRetentionExecution --RegistryId --RetentionId ` | 是 | +| 模拟执行 | `tccli tcr CreateTagRetentionExecution --RegistryId --RetentionId --DryRun true` | 是 | +| 查看执行日志 | `tccli tcr DescribeTagRetentionExecution --RegistryId --RetentionId ` | 是 | +| 查看执行任务详情 | `tccli tcr DescribeTagRetentionExecutionTask --RegistryId --RetentionId --ExecutionId ` | 是 | + +## 操作步骤 + +### 创建版本保留规则 + +首先查看当前实例下已有规则列表(实例 `tcr-nn8smeyj`,命名空间 `kerwinwjyan-test`,`NamespaceId=2`): + +```bash +tccli tcr DescribeTagRetentionRules --RegistryId tcr-nn8smeyj --region ap-guangzhou --output json +``` + +```json +{ + "RetentionPolicyList": [], + "TotalCount": 0, + "RequestId": "dadd8a07-cf11-4ab7-9bd3-867437ce38ea" +} +``` + +当前命名空间下无已有规则,可以新建。 + +> **关键提示**:`NamespaceId` 是**整数类型**(Integer),对应 TCR 命名空间的数字 ID,**不是**字符串类型的 `NamespaceName`。使用 `tccli tcr DescribeNamespaces --RegistryId ` 获取命名空间对应的 `NamespaceId` 数值。 + +#### 简易配置模式 + +适合对命名空间内全部仓库及版本统一应用保留策略。`RetentionRule` 为单一保留规则对象,`CronSetting` 定义执行周期。 + +**第一次尝试(失败):误用 `--NamespaceName` 字符串参数** + +新手容易直接传入命名空间名称字符串,但 API 要求的是 `--NamespaceId`(Integer): + +```bash +# WRONG: --NamespaceName 是字符串,API 不接受 +tccli tcr CreateTagRetentionRule \ + --RegistryId tcr-nn8smeyj \ + --NamespaceName kerwinwjyan-test \ + --CronSetting daily \ + --RetentionRule '{"Key":"latestPushedK","Value":30}' \ + --region ap-guangzhou \ + --output json +``` + +**错误输出**: + +``` +the following arguments are required: --NamespaceId +``` + +**报错原因**:`CreateTagRetentionRule` 的 `NamespaceId` 参数是 `--NamespaceId`(Integer),不是 `--NamespaceName`(String)。tccli 认为未提供必填参数 `--NamespaceId`。 + +**第二次尝试(成功):使用 `--NamespaceId 2`** + +```json title="examples/tcr-retention-simple.json" +{ + "RegistryId": "tcr-nn8smeyj", + "NamespaceId": 2, + "CronSetting": "daily", + "RetentionRule": { + "Key": "nDaysSinceLastPush", + "Value": 30 + }, + "Disabled": false +} +``` + +| 字段 | 说明 | +|------|------| +| `CronSetting` | 执行周期:`daily`(每天零点)、`weekly`(每周一零点)、`monthly`(每月第一天零点) | +| `RetentionRule.Key` | 保留策略类型:`latestPushedK`(保留最新推送的 K 个版本)、`nDaysSinceLastPush`(保留最近 N 天内推送的版本) | +| `RetentionRule.Value` | 策略值。`latestPushedK` 时为保留的版本数;`nDaysSinceLastPush` 时为保留的天数 | +| `Disabled` | 是否禁用规则。`false` 为启用 | + +```bash +tccli tcr CreateTagRetentionRule --cli-input-json ./examples/tcr-retention-simple.json --region ap-guangzhou --output json +``` + +```json +{ + "RequestId": "d9b8d078-d118-482a-8ae9-5c8d32afea10" +} +``` + +#### 高级配置模式 + +适合对命名空间内特定仓库或版本进行过滤,并配置多条保留规则(规则间取并集)。`AdvancedRuleItems` 为规则数组。 + +```json title="examples/tcr-retention-advanced.json" +{ + "RegistryId": "tcr-nn8smeyj", + "NamespaceId": 2, + "CronSetting": "monthly", + "RetentionRule": { + "Key": "nDaysSinceLastPush", + "Value": 30 + }, + "AdvancedRuleItems": [ + { + "Key": "latestPushedK", + "Value": 10, + "RepositoryFilter": { + "Decoration": "repoMatches", + "Pattern": "release/**" + }, + "TagFilter": { + "Decoration": "matches", + "Pattern": "v[0-9]+.*" + } + }, + { + "Key": "nDaysSinceLastPush", + "Value": 7, + "RepositoryFilter": { + "Decoration": "repoMatches", + "Pattern": "**" + }, + "TagFilter": { + "Decoration": "excludes", + "Pattern": "latest" + } + } + ], + "Disabled": false +} +``` + +| 字段 | 说明 | +|------|------| +| `AdvancedRuleItems[].Key` | 保留策略类型(同简易配置) | +| `AdvancedRuleItems[].Value` | 策略值(同简易配置) | +| `AdvancedRuleItems[].RepositoryFilter.Decoration` | 仓库过滤方式:`repoMatches`(匹配)、`repoExcludes`(排除) | +| `AdvancedRuleItems[].RepositoryFilter.Pattern` | 仓库名正则。`**` 匹配所有;`release/**` 匹配 release 下的所有子仓库 | +| `AdvancedRuleItems[].TagFilter.Decoration` | 版本过滤方式:`matches`(匹配)、`excludes`(排除) | +| `AdvancedRuleItems[].TagFilter.Pattern` | 版本名正则。`*` 匹配任意字符串(不跨 `/`);`**` 跨级匹配;`?` 匹配除 `/` 外的单个字符 | + +```bash +tccli tcr CreateTagRetentionRule --cli-input-json ./examples/tcr-retention-advanced.json --region ap-guangzhou --output json +``` + +```json +{ + "RequestId": "e7f8a9b0-c1d2-3456-efgh-789012345678" +} +``` + +### 查看版本保留规则 + +创建规则后,查看当前规则列表确认配置: + +```bash +tccli tcr DescribeTagRetentionRules --RegistryId tcr-nn8smeyj --region ap-guangzhou --output json +``` + +```json +{ + "RetentionPolicyList": [ + { + "RetentionId": 1, + "NamespaceName": "kerwinwjyan-test", + "RetentionRuleList": [ + { + "Key": "nDaysSinceLastPush", + "Value": 30 + } + ], + "AdvancedRuleItems": [], + "CronSetting": "daily", + "Disabled": false, + "NextExecutionTime": "2026-06-19T00:00:00+08:00" + } + ], + "TotalCount": 1, + "RequestId": "b1c2d3e4-f5a6-7890-abcd-ef1234567890" +} +``` + +支持按命名空间名称过滤(可选): + +```bash +tccli tcr DescribeTagRetentionRules \ + --RegistryId tcr-nn8smeyj \ + --NamespaceName kerwinwjyan-test \ + --region ap-guangzhou \ + --output json +``` + +### 修改规则 + +修改规则的保留策略、执行周期或启用/禁用状态。`RetentionId` 来自 `DescribeTagRetentionRules` 的输出。不可修改生效的命名空间。 + +```json title="examples/tcr-retention-modify.json" +{ + "RegistryId": "tcr-nn8smeyj", + "RetentionId": 1, + "NamespaceId": 2, + "CronSetting": "weekly", + "RetentionRule": { + "Key": "latestPushedK", + "Value": 50 + }, + "Disabled": false +} +``` + +```bash +tccli tcr ModifyTagRetentionRule --cli-input-json ./examples/tcr-retention-modify.json --region ap-guangzhou --output json +``` + +```json +{ + "RequestId": "f6a7b8c9-d0e1-2345-fabc-456789012345" +} +``` + +### 删除版本保留规则 + +删除使用 `--RetentionId` 参数(非 `--RetentionRuleId`)。 + +**第一次尝试(失败):误用 `--RetentionRuleId`** + +```bash +# WRONG: 参数名是 --RetentionId,不是 --RetentionRuleId +tccli tcr DeleteTagRetentionRule \ + --RegistryId tcr-nn8smeyj \ + --RetentionRuleId 1 \ + --region ap-guangzhou \ + --output json +``` + +**错误输出**: + +``` +the following arguments are required: --RetentionId +``` + +**报错原因**:`DeleteTagRetentionRule` 的必填参数是 `--RetentionId`,不是 `--RetentionRuleId`。tccli 参数校验按 API 定义精确匹配参数名。 + +**第二次尝试(成功):使用 `--RetentionId`** + +```bash +tccli tcr DeleteTagRetentionRule \ + --RegistryId tcr-nn8smeyj \ + --RetentionId 1 \ + --region ap-guangzhou \ + --output json +``` + +```json +{ + "RequestId": "1180a245-0d4c-4f6c-8c75-18f0b080c22e" +} +``` + +### 手动触发执行 + +可通过 API 手动触发规则执行(真实执行或模拟执行),无需等待 CronSetting 指定的周期。 + +#### 真实执行 + +真实执行将删除保留规则外的镜像版本: + +```bash +tccli tcr CreateTagRetentionExecution --RegistryId tcr-nn8smeyj --RetentionId 1 --region ap-guangzhou --output json +``` + +#### 模拟执行(DryRun) + +模拟执行用于确认规则是否生效,但不实际清理镜像版本: + +```bash +tccli tcr CreateTagRetentionExecution \ + --RegistryId tcr-nn8smeyj \ + --RetentionId 1 \ + --DryRun true \ + --region ap-guangzhou \ + --output json +``` + +```json +{ + "RequestId": "a7b8c9d0-e1f2-3456-abcd-567890123456" +} +``` + +### 查看执行日志 + +#### 查看规则的所有执行记录 + +```bash +tccli tcr DescribeTagRetentionExecution --RegistryId tcr-nn8smeyj --RetentionId 1 --region ap-guangzhou --output json +``` + +```json +{ + "RetentionExecutionList": [ + { + "ExecutionId": 101, + "RetentionId": 1, + "StartTime": "2026-06-17T00:00:00+08:00", + "EndTime": "2026-06-17T00:05:30+08:00", + "Status": "Success" + } + ], + "TotalCount": 1, + "RequestId": "b8c9d0e1-f2a3-4567-bcde-678901234567" +} +``` + +**字段说明:** + +| 字段 | 说明 | +|------|------| +| `ExecutionId` | 实例内唯一的执行任务 ID | +| `StartTime` / `EndTime` | 任务创建/完成时间(ISO 8601 格式),差值即为任务耗时 | +| `Status` | 执行状态:`Success`(成功)、`Failed`(失败)、`InProgress`(进行中) | + +#### 查看单次执行的详细任务 + +`ExecutionId` 来自执行记录列表。可查看每条规则在每个仓库中的具体执行情况: + +```bash +tccli tcr DescribeTagRetentionExecutionTask \ + --RegistryId tcr-nn8smeyj \ + --RetentionId 1 \ + --ExecutionId 101 \ + --region ap-guangzhou \ + --output json +``` + +```json +{ + "RetentionTaskList": [ + { + "TaskId": 1001, + "ExecutionId": 101, + "RetentionId": 1, + "StartTime": "2026-06-17T00:00:00+08:00", + "EndTime": "2026-06-17T00:01:30+08:00", + "Status": "Success", + "RepoName": "kerwinwjyan-test/test-repo", + "Total": 120, + "Retained": 30, + "Message": "" + } + ], + "TotalCount": 1, + "RequestId": "c9d0e1f2-a3b4-5678-cdef-789012345678" +} +``` + +**字段说明:** + +| 字段 | 说明 | +|------|------| +| `TaskId` | 任务 ID | +| `RepoName` | 仓库全名(`namespace/repo`) | +| `Total` | 该仓库内总版本数 | +| `Retained` | 保留的版本数 | +| `Status` | 任务状态 | + +## 验证 + +### Control plane (tccli) + +验证规则已按预期配置,并确认执行记录: + +```bash +tccli tcr DescribeTagRetentionRules --RegistryId tcr-nn8smeyj --region ap-guangzhou --output json +``` + +确认 `CronSetting` 已更新、保留策略及 `Disabled` 状态符合预期。 + +验证最新一次执行记录与任务详情: + +```bash +tccli tcr DescribeTagRetentionExecution --RegistryId tcr-nn8smeyj --RetentionId 1 --region ap-guangzhou --output json +tccli tcr DescribeTagRetentionExecutionTask --RegistryId tcr-nn8smeyj --RetentionId 1 --ExecutionId 101 --region ap-guangzhou --output json +``` + +## 清理 + +### Control plane (tccli) + +删除本次创建的测试规则: + +```bash +tccli tcr DeleteTagRetentionRule --RegistryId tcr-nn8smeyj --RetentionId 1 --region ap-guangzhou --output json +``` + +```json +{ + "RequestId": "1180a245-0d4c-4f6c-8c75-18f0b080c22e" +} +``` + +## 排障 + +| 现象 | 处理 | +|------|------| +| `CreateTagRetentionRule` 报错 `the following arguments are required: --NamespaceId` | 参数名是 `--NamespaceId`(Integer 类型),不是 `--NamespaceName`(String 类型)。使用 `tccli tcr DescribeNamespaces --RegistryId ` 获取命名空间对应的数字 ID,例如 `--NamespaceId 2` | +| `DeleteTagRetentionRule` 报错 `the following arguments are required: --RetentionId` | 参数名是 `--RetentionId`,不是 `--RetentionRuleId`。`RetentionId` 可从 `DescribeTagRetentionRules` 输出中获取,例如 `--RetentionId 1` | +| `NamespaceId` 未知 | 使用 `tccli tcr DescribeNamespaces --RegistryId ` 获取命名空间的数字 ID(`NamespaceList[].NamespaceId`) | +| 同一命名空间创建第二条规则报错 | 单个命名空间内仅允许创建一条版本保留规则,请先删除已有规则或使用其他命名空间 | +| 模拟执行后版本未被清理 | 模拟执行(`--DryRun true`)仅验证规则匹配,不实际删除;需常规执行(不加 `--DryRun`)以清理 | +| 保留规则内版本被意外删除 | 检查是否存在多个不同 Tag 但相同 Digest 的镜像版本,删除任一 Tag 可能影响共享同一 Digest 的所有 Tag | +| 执行耗时过长 | 仓库内版本数较多时执行耗时自然增加,可查看 `DescribeTagRetentionExecutionTask` 逐仓库确认进度 | +| CAM 权限拒绝 | 确认子账号已配置 TCR 相关操作权限,参见 [基于 CAM 管理子账号权限](https://cloud.tencent.com/document/product/1141/41417) | + +## 下一步 + +- [清理 COS 存储空间](https://cloud.tencent.com/document/product/1141/58157) — 彻底清理底层镜像数据 + +## 控制台替代 + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) -> 选择实例 -> **版本管理** -> **版本保留** -> 单击 **新建规则**,配置命名空间、保留策略、执行周期后确认。在规则列表行可查看日志、重新配置或删除规则。 diff --git a/src/content/docs/cli/tcr/ops/image-cleanup/cos-cleanup-tcrctl.md b/src/content/docs/cli/tcr/ops/image-cleanup/cos-cleanup-tcrctl.md new file mode 100644 index 0000000..310eb50 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-cleanup/cos-cleanup-tcrctl.md @@ -0,0 +1,42 @@ +--- +title: "清理 COS 存储空间(tcrctl)" +description: "· page_id `58157`" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> 对照官方:[清理 COS 存储空间](https://cloud.tencent.com/document/product/1141/58157) · page_id `58157` +> +> 本页为 P1 产品 CLI 操作页,将在 P1 阶段(产品文档 agentic 化)中完成正文。 + +## Overview + +(待完成)使用 `tcrctl` 管理实例制品清理(GC)任务。 + +## Before you begin + +(待完成) + +## Procedure + +(待完成) + +## Verify + +(待完成) + +## Cleanup + +(待完成) + +## Troubleshoot + +(待完成) + +## Next steps + +- [自动删除镜像版本](https://cloud.tencent.com/document/product/1141/50613) + +## Console alternative + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 选择实例 → 左侧导航栏 **制品清理** → **模拟运行清理** / **立即执行清理**。 diff --git a/src/content/docs/cli/tcr/ops/image-cleanup/cos-cleanup.md b/src/content/docs/cli/tcr/ops/image-cleanup/cos-cleanup.md new file mode 100644 index 0000000..2e0ac24 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-cleanup/cos-cleanup.md @@ -0,0 +1,171 @@ +--- +title: "清理 COS 存储空间(tccli)" +description: "· page_id `58157`" +--- + +> 对照官方:[清理 COS 存储空间](https://cloud.tencent.com/document/product/1141/58157) · page_id `58157` + +## 概述 + +TCR 企业版实例的容器镜像数据存储在腾讯云对象存储(COS)中。当通过控制台或 API 删除镜像 Tag 或清空仓库时,仅移除镜像元数据索引,底层 COS 中已无引用的镜像层(blob)数据并不会被立即回收——这些孤立 blob 持续占用 COS 存储空间并产生费用。 + +GC(Garbage Collection,垃圾回收)作业负责清理 COS 中已无任何镜像 Tag 引用的 blob 数据,释放存储空间。支持两种触发方式: + +| 触发方式 | `Schedule.Type` | 说明 | +|---------|-----------------|------| +| **手动触发** | `Manual` | 通过 `CreateGCJob` API 即时触发一次 GC | +| **定期自动** | `Scheduled` | 实例级别周期性自动执行 | + +**GC 作业状态机**:`running`(执行中)→ `finished`(成功完成) / `failed`(执行失败)。 + +> **注意**: +> - GC 过程仅清理 COS 存储层中无引用的 blob。对于仍被镜像 Tag 引用的数据、或已被 [版本保留规则](../auto-delete) 删除 Tag 但未执行 GC 的 blob,需先确保无引用后再触发 GC。 +> - 执行 GC 期间不影响正常镜像推拉。 + +## 前置条件 + +- 已 [创建企业版实例](../../create),实例状态为 `Running`。 +- 已配置 `tccli` 凭证(参见 [环境准备](../../../index.md))。 +- 建议在执行 GC 前完成需要清理的镜像 Tag 删除操作(参见 [自动删除镜像版本](../auto-delete)),确保目标 blob 已无引用。 +- 若使用子账号操作,需授予实例操作权限,参见 [企业版授权方案示例](https://cloud.tencent.com/document/product/1141/41417)。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | CLI | 幂等 | +|-----------|-----|------| +| 立即执行清理 | `tccli tcr CreateGCJob --RegistryId ` | 否(并发限制) | +| 模拟运行清理 | (CLI 不支持模拟模式,控制台独占) | — | +| 查看 GC 作业列表 | `tccli tcr DescribeGCJobs --RegistryId ` | 是 | +| 终止进行中的 GC | `tccli tcr TerminateGCJob --RegistryId --JobId ` | 是 | + +## 操作步骤 + +### 1. 触发 GC 作业 + +`CreateGCJob` 无额外参数——仅需传入 `RegistryId` 即可触发一次即时 GC(`Schedule.Type` 为 `Manual`)。该操作不支持并发:若实例已有 `running` 状态的 GC 作业,需等待其完成或终止后再触发。 + +```bash +tccli tcr CreateGCJob \ + --RegistryId '' \ + --region +``` + +```output +{ + "RequestId": "b0a6db6b-5d88-47b7-a040-9515aac67ae5" +} +``` + +`CreateGCJob` 返回体仅包含 `RequestId`,不直接返回 `JobId`。需通过下一步 `DescribeGCJobs` 获取 GC 作业 ID 及状态。 + +### 2. 查看 GC 作业状态 + +触发 GC 后,使用 `DescribeGCJobs` 查询当前实例的 GC 作业列表: + +```bash +tccli tcr DescribeGCJobs \ + --RegistryId '' \ + --region +``` + +```output +{ + "Jobs": [ + { + "ID": 8, + "JobStatus": "running", + "Schedule": { + "Type": "Manual" + }, + "CreationTime": "2026-06-16T10:30:00+08:00", + "UpdateTime": "2026-06-16T10:30:05+08:00" + } + ], + "RequestId": "1c616ba0-088e-49ee-8ac4-ab2085e952c8" +} +``` + +**Jobs 数组字段说明:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `ID` | Integer | GC 作业唯一标识 ID | +| `JobStatus` | String | 作业状态:`running`(执行中)、`finished`(已完成)、`failed`(失败) | +| `Schedule.Type` | String | 触发方式:`Manual`(手动触发)、`Scheduled`(定期自动) | +| `CreationTime` | String | 作业创建时间(ISO 8601) | +| `UpdateTime` | String | 作业状态最近更新时间(ISO 8601) | + +**轮询等待 GC 完成**:可编写简单轮询脚本等待 GC 结束: + +```bash +# 等待 GC 作业完成(示例 JobId=8) +while true; do + STATUS=$(tccli tcr DescribeGCJobs --RegistryId '' --region \ + | jq -r '.Jobs[] | select(.ID == 8) | .JobStatus') + echo "GC Job 8 status: $STATUS" + case "$STATUS" in + finished|failed) break ;; + esac + sleep 10 +done +``` + +### 3. 终止 GC 作业 + +若 GC 作业执行时间过长,可使用 `TerminateGCJob` 终止。`--JobId` 为整数类型,来自 `DescribeGCJobs` 输出中的 `Jobs[].ID` 字段: + +```bash +tccli tcr TerminateGCJob \ + --RegistryId '' \ + --JobId 8 \ + --region +``` + +```output +{ + "RequestId": "c2d3e4f5-a6b7-8901-cdef-1234567890ab" +} +``` + +终止后,`DescribeGCJobs` 中对应的 `JobStatus` 变更为 `finished`。 + +## 验证 + +### Control plane (tccli) + +触发 GC 后,验证作业是否成功完成: + +```bash +tccli tcr DescribeGCJobs \ + --RegistryId '' \ + --region +``` + +确认目标 GC 作业 `JobStatus` 为 `finished`。若为 `failed`,参考 [排障](#排障) 章节排查。 + +**验证存储空间释放效果**:可在控制台实例概览页查看存储用量变化,或使用 COS 控制台查看对应 Bucket 的存储量变化(存在延迟,建议等待数小时后再对比)。 + +## 清理 + +GC 作业本身即为清理操作,完成后自动结束,无需额外清理。若触发的是测试性 GC(仅验证流程),可在作业完成后忽略。 + +## 排障 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `CreateGCJob` 返回 `InternalError` 或提示 "GC job already running" | `tccli tcr DescribeGCJobs --RegistryId --region ` 检查 `JobStatus` | 实例已有 GC 作业在运行中,不允许并发触发 | 等待当前 GC 完成(`JobStatus` 变为 `finished`/`failed`),或使用 `TerminateGCJob` 终止后重试 | +| `DescribeGCJobs` 返回空 `Jobs` 数组 | 确认实例为企业版、且存在镜像数据变更 | 无已完成的镜像删除操作则无孤立 blob 可清理,或 GC 作业已过期被清理 | 确认存在已删除 Tag 但未 GC 的镜像数据,或该情况属正常——空列表表示无待清理数据 | +| `TerminateGCJob` 提示 "Job not found" | `tccli tcr DescribeGCJobs --RegistryId --region ` 确认 `--JobId` | `--JobId` 不正确,或作业已结束(`finished`/`failed`) | 从 `DescribeGCJobs` 重新确认正确的 Job ID | +| GC 作业 `JobStatus=failed` | `tccli tcr DescribeGCJobs --RegistryId --region ` 查看失败作业详情 | COS API 调用异常、权限不足或 COS Bucket 配置问题 | 重试 `CreateGCJob`。若持续失败,检查实例关联的 COS Bucket 是否存在且可访问;通过 [提交工单](https://console.cloud.tencent.com/workorder/category) 获取技术支持 | +| GC 作业长时间处于 `running` | 轮询 `DescribeGCJobs` 观察持续时间 | 实例内镜像规模较大,孤立 blob 数量多,清理耗时较长 | 耐心等待。单次 GC 执行时间取决于 blob 数量。若超过 2 小时仍无进展,可 `TerminateGCJob` 终止后重新触发 | +| CAM 权限拒绝 | — | 子账号缺少 `tcr:CreateGCJob` / `tcr:DescribeGCJobs` / `tcr:TerminateGCJob` 权限 | 为子账号配置对应 API 权限,参见 [基于 CAM 管理子账号权限](https://cloud.tencent.com/document/product/1141/41417) | +| GC 完成后存储空间未明显减少 | 对比 GC 前后控制台存储用量或 COS Bucket 容量 | 若实例中本无大量孤立 blob(如未删除过镜像或已定期自动 GC),清理量极小属正常 | 确认确实存在大量已删除引用但未 GC 的 blob 后再评估,或等待 COS 用量指标更新(存在计费延迟) | + +## 下一步 + +- [自动删除镜像版本](../auto-delete) -- 配置版本保留规则,定期清理过期 Tag +- [管理镜像仓库](../../image-creation/repository) -- 删除不需要的镜像仓库及 Tag + +## 控制台替代 + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) -> 选择实例 -> 左侧导航栏 **制品清理** -> 单击 **立即执行清理** 手动触发 GC,或单击 **模拟运行清理** 先预览可清理数据量。在制品清理页面可查看历史 GC 任务列表及执行状态。 diff --git a/src/content/docs/cli/tcr/ops/image-cleanup/cos-cleanup/cos-cleanup-tcrctl.md b/src/content/docs/cli/tcr/ops/image-cleanup/cos-cleanup/cos-cleanup-tcrctl.md new file mode 100644 index 0000000..310eb50 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-cleanup/cos-cleanup/cos-cleanup-tcrctl.md @@ -0,0 +1,42 @@ +--- +title: "清理 COS 存储空间(tcrctl)" +description: "· page_id `58157`" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> 对照官方:[清理 COS 存储空间](https://cloud.tencent.com/document/product/1141/58157) · page_id `58157` +> +> 本页为 P1 产品 CLI 操作页,将在 P1 阶段(产品文档 agentic 化)中完成正文。 + +## Overview + +(待完成)使用 `tcrctl` 管理实例制品清理(GC)任务。 + +## Before you begin + +(待完成) + +## Procedure + +(待完成) + +## Verify + +(待完成) + +## Cleanup + +(待完成) + +## Troubleshoot + +(待完成) + +## Next steps + +- [自动删除镜像版本](https://cloud.tencent.com/document/product/1141/50613) + +## Console alternative + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 选择实例 → 左侧导航栏 **制品清理** → **模拟运行清理** / **立即执行清理**。 diff --git a/src/content/docs/cli/tcr/ops/image-cleanup/cos-cleanup/cos-cleanup.md b/src/content/docs/cli/tcr/ops/image-cleanup/cos-cleanup/cos-cleanup.md new file mode 100644 index 0000000..2e0ac24 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-cleanup/cos-cleanup/cos-cleanup.md @@ -0,0 +1,171 @@ +--- +title: "清理 COS 存储空间(tccli)" +description: "· page_id `58157`" +--- + +> 对照官方:[清理 COS 存储空间](https://cloud.tencent.com/document/product/1141/58157) · page_id `58157` + +## 概述 + +TCR 企业版实例的容器镜像数据存储在腾讯云对象存储(COS)中。当通过控制台或 API 删除镜像 Tag 或清空仓库时,仅移除镜像元数据索引,底层 COS 中已无引用的镜像层(blob)数据并不会被立即回收——这些孤立 blob 持续占用 COS 存储空间并产生费用。 + +GC(Garbage Collection,垃圾回收)作业负责清理 COS 中已无任何镜像 Tag 引用的 blob 数据,释放存储空间。支持两种触发方式: + +| 触发方式 | `Schedule.Type` | 说明 | +|---------|-----------------|------| +| **手动触发** | `Manual` | 通过 `CreateGCJob` API 即时触发一次 GC | +| **定期自动** | `Scheduled` | 实例级别周期性自动执行 | + +**GC 作业状态机**:`running`(执行中)→ `finished`(成功完成) / `failed`(执行失败)。 + +> **注意**: +> - GC 过程仅清理 COS 存储层中无引用的 blob。对于仍被镜像 Tag 引用的数据、或已被 [版本保留规则](../auto-delete) 删除 Tag 但未执行 GC 的 blob,需先确保无引用后再触发 GC。 +> - 执行 GC 期间不影响正常镜像推拉。 + +## 前置条件 + +- 已 [创建企业版实例](../../create),实例状态为 `Running`。 +- 已配置 `tccli` 凭证(参见 [环境准备](../../../index.md))。 +- 建议在执行 GC 前完成需要清理的镜像 Tag 删除操作(参见 [自动删除镜像版本](../auto-delete)),确保目标 blob 已无引用。 +- 若使用子账号操作,需授予实例操作权限,参见 [企业版授权方案示例](https://cloud.tencent.com/document/product/1141/41417)。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | CLI | 幂等 | +|-----------|-----|------| +| 立即执行清理 | `tccli tcr CreateGCJob --RegistryId ` | 否(并发限制) | +| 模拟运行清理 | (CLI 不支持模拟模式,控制台独占) | — | +| 查看 GC 作业列表 | `tccli tcr DescribeGCJobs --RegistryId ` | 是 | +| 终止进行中的 GC | `tccli tcr TerminateGCJob --RegistryId --JobId ` | 是 | + +## 操作步骤 + +### 1. 触发 GC 作业 + +`CreateGCJob` 无额外参数——仅需传入 `RegistryId` 即可触发一次即时 GC(`Schedule.Type` 为 `Manual`)。该操作不支持并发:若实例已有 `running` 状态的 GC 作业,需等待其完成或终止后再触发。 + +```bash +tccli tcr CreateGCJob \ + --RegistryId '' \ + --region +``` + +```output +{ + "RequestId": "b0a6db6b-5d88-47b7-a040-9515aac67ae5" +} +``` + +`CreateGCJob` 返回体仅包含 `RequestId`,不直接返回 `JobId`。需通过下一步 `DescribeGCJobs` 获取 GC 作业 ID 及状态。 + +### 2. 查看 GC 作业状态 + +触发 GC 后,使用 `DescribeGCJobs` 查询当前实例的 GC 作业列表: + +```bash +tccli tcr DescribeGCJobs \ + --RegistryId '' \ + --region +``` + +```output +{ + "Jobs": [ + { + "ID": 8, + "JobStatus": "running", + "Schedule": { + "Type": "Manual" + }, + "CreationTime": "2026-06-16T10:30:00+08:00", + "UpdateTime": "2026-06-16T10:30:05+08:00" + } + ], + "RequestId": "1c616ba0-088e-49ee-8ac4-ab2085e952c8" +} +``` + +**Jobs 数组字段说明:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `ID` | Integer | GC 作业唯一标识 ID | +| `JobStatus` | String | 作业状态:`running`(执行中)、`finished`(已完成)、`failed`(失败) | +| `Schedule.Type` | String | 触发方式:`Manual`(手动触发)、`Scheduled`(定期自动) | +| `CreationTime` | String | 作业创建时间(ISO 8601) | +| `UpdateTime` | String | 作业状态最近更新时间(ISO 8601) | + +**轮询等待 GC 完成**:可编写简单轮询脚本等待 GC 结束: + +```bash +# 等待 GC 作业完成(示例 JobId=8) +while true; do + STATUS=$(tccli tcr DescribeGCJobs --RegistryId '' --region \ + | jq -r '.Jobs[] | select(.ID == 8) | .JobStatus') + echo "GC Job 8 status: $STATUS" + case "$STATUS" in + finished|failed) break ;; + esac + sleep 10 +done +``` + +### 3. 终止 GC 作业 + +若 GC 作业执行时间过长,可使用 `TerminateGCJob` 终止。`--JobId` 为整数类型,来自 `DescribeGCJobs` 输出中的 `Jobs[].ID` 字段: + +```bash +tccli tcr TerminateGCJob \ + --RegistryId '' \ + --JobId 8 \ + --region +``` + +```output +{ + "RequestId": "c2d3e4f5-a6b7-8901-cdef-1234567890ab" +} +``` + +终止后,`DescribeGCJobs` 中对应的 `JobStatus` 变更为 `finished`。 + +## 验证 + +### Control plane (tccli) + +触发 GC 后,验证作业是否成功完成: + +```bash +tccli tcr DescribeGCJobs \ + --RegistryId '' \ + --region +``` + +确认目标 GC 作业 `JobStatus` 为 `finished`。若为 `failed`,参考 [排障](#排障) 章节排查。 + +**验证存储空间释放效果**:可在控制台实例概览页查看存储用量变化,或使用 COS 控制台查看对应 Bucket 的存储量变化(存在延迟,建议等待数小时后再对比)。 + +## 清理 + +GC 作业本身即为清理操作,完成后自动结束,无需额外清理。若触发的是测试性 GC(仅验证流程),可在作业完成后忽略。 + +## 排障 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `CreateGCJob` 返回 `InternalError` 或提示 "GC job already running" | `tccli tcr DescribeGCJobs --RegistryId --region ` 检查 `JobStatus` | 实例已有 GC 作业在运行中,不允许并发触发 | 等待当前 GC 完成(`JobStatus` 变为 `finished`/`failed`),或使用 `TerminateGCJob` 终止后重试 | +| `DescribeGCJobs` 返回空 `Jobs` 数组 | 确认实例为企业版、且存在镜像数据变更 | 无已完成的镜像删除操作则无孤立 blob 可清理,或 GC 作业已过期被清理 | 确认存在已删除 Tag 但未 GC 的镜像数据,或该情况属正常——空列表表示无待清理数据 | +| `TerminateGCJob` 提示 "Job not found" | `tccli tcr DescribeGCJobs --RegistryId --region ` 确认 `--JobId` | `--JobId` 不正确,或作业已结束(`finished`/`failed`) | 从 `DescribeGCJobs` 重新确认正确的 Job ID | +| GC 作业 `JobStatus=failed` | `tccli tcr DescribeGCJobs --RegistryId --region ` 查看失败作业详情 | COS API 调用异常、权限不足或 COS Bucket 配置问题 | 重试 `CreateGCJob`。若持续失败,检查实例关联的 COS Bucket 是否存在且可访问;通过 [提交工单](https://console.cloud.tencent.com/workorder/category) 获取技术支持 | +| GC 作业长时间处于 `running` | 轮询 `DescribeGCJobs` 观察持续时间 | 实例内镜像规模较大,孤立 blob 数量多,清理耗时较长 | 耐心等待。单次 GC 执行时间取决于 blob 数量。若超过 2 小时仍无进展,可 `TerminateGCJob` 终止后重新触发 | +| CAM 权限拒绝 | — | 子账号缺少 `tcr:CreateGCJob` / `tcr:DescribeGCJobs` / `tcr:TerminateGCJob` 权限 | 为子账号配置对应 API 权限,参见 [基于 CAM 管理子账号权限](https://cloud.tencent.com/document/product/1141/41417) | +| GC 完成后存储空间未明显减少 | 对比 GC 前后控制台存储用量或 COS Bucket 容量 | 若实例中本无大量孤立 blob(如未删除过镜像或已定期自动 GC),清理量极小属正常 | 确认确实存在大量已删除引用但未 GC 的 blob 后再评估,或等待 COS 用量指标更新(存在计费延迟) | + +## 下一步 + +- [自动删除镜像版本](../auto-delete) -- 配置版本保留规则,定期清理过期 Tag +- [管理镜像仓库](../../image-creation/repository) -- 删除不需要的镜像仓库及 Tag + +## 控制台替代 + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) -> 选择实例 -> 左侧导航栏 **制品清理** -> 单击 **立即执行清理** 手动触发 GC,或单击 **模拟运行清理** 先预览可清理数据量。在制品清理页面可查看历史 GC 任务列表及执行状态。 diff --git a/src/content/docs/cli/tcr/ops/image-creation/namespace-tcrctl.md b/src/content/docs/cli/tcr/ops/image-creation/namespace-tcrctl.md new file mode 100644 index 0000000..b8f368e --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-creation/namespace-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "管理命名空间(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[管理命名空间](https://cloud.tencent.com/document/product/1141/41803) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/image-creation/namespace.md b/src/content/docs/cli/tcr/ops/image-creation/namespace.md new file mode 100644 index 0000000..adfe2c8 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-creation/namespace.md @@ -0,0 +1,288 @@ +--- +title: "管理命名空间(tccli)" +description: "· page_id `41803`" +--- + +> 对照官方:[管理命名空间](https://cloud.tencent.com/document/product/1141/41803) · page_id `41803` + +## 概述 + +通过 tccli 管理 TCR 企业版实例内的命名空间。命名空间是容器镜像仓库和 Helm Chart 的逻辑分组单元,用于按团队、项目或环境划分资源边界。自身不直接存储镜像数据,但对下级仓库的访问权限、安全扫描和部署阻断策略由命名空间统一控制。 + +核心操作覆盖:创建(含公开/私有策略)、查询、修改(自动扫描、漏洞阻断)、删除。所有操作依赖 `--RegistryId`(实例 ID),由 `DescribeInstances` 获取。 + +命名空间不收费,但创建后若在内部上传镜像,会产生 COS 存储费用。详见[计费概述](https://cloud.tencent.com/document/product/1141/40540)。 + +## 前置条件 + +- [环境准备](../../../index.md) +- 已 [购买企业版实例](../../../create) 且实例 `Status` 为 `Running` +- CAM 权限包含:`tcr:CreateNamespace`、`tcr:DescribeNamespaces`、`tcr:ModifyNamespace`、`tcr:DeleteNamespace` + +### 环境检查 + +```bash +# 1. 确认实例存在且状态正常 +tccli tcr DescribeInstances --Registryids '["tcr-example"]' --region ap-guangzhou --output json +``` + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "tcr-example", + "RegistryType": "basic", + "Status": "Running", + "PublicDomain": "tcr-example.tencentcloudcr.com", + "InternalEndpoint": "10.1.67.13", + "EnableAnonymous": true, + "TokenValidTime": 87600, + "DeletionProtection": false + } + ], + "RequestId": "..." +} +``` + +```bash +# 2. 确认 CAM 权限(查询命名空间列表不报 UnauthorizedOperation) +tccli tcr DescribeNamespaces --RegistryId tcr-example --region ap-guangzhou --output json +``` + +### 命名约束 + +命名空间名仅支持小写字母、数字及连字符(`-`),不能以连字符开头或结尾,不能出现连续连字符。长度限制 2--30 个字符,同一实例内名称必须唯一。名称一旦创建不可修改。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 / 参数 | 幂等 | +|-----------|------------------|:--:| +| 查看命名空间列表 | `DescribeNamespaces --RegistryId ` | 是 | +| 新建命名空间(输入名称) | `CreateNamespace --NamespaceName ` | 否(同名报错) | +| 选择公开/私有 | `--IsPublic true/false` | — | +| 开启自动扫描 | `ModifyNamespace --IsAutoScan true` | 是 | +| 阻断高危镜像部署 | `ModifyNamespace --IsPreventVUL true` | 是 | +| 设置漏洞严重级别 | `ModifyNamespace --Severity ` | 是 | +| 删除命名空间 | `DeleteNamespace --RegistryId --NamespaceName ` | 否(级联删除不可恢复) | + +## 关键字段说明 + +### CreateNamespace + +| 字段 | 类型 | 必填 | 取值与约束 | +|------|------|:--:|------| +| `RegistryId` | String | 是 | 目标实例 ID(`DescribeInstances` 获取) | +| `NamespaceName` | String | 是 | 命名空间名,小写字母+数字+连字符,2--30 字符,实例内唯一 | +| `IsPublic` | Boolean | 否 | 默认 `false`。`true` = 公开(允许匿名拉取),`false` = 私有(需凭证访问) | +| `TagSpecification` | Object | 否 | `ResourceType`: `"namespace"`,`Tags`: 标签数组 | + +### ModifyNamespace + +| 字段 | 类型 | 必填 | 取值与约束 | +|------|------|:--:|------| +| `RegistryId` | String | 是 | 目标实例 ID | +| `NamespaceName` | String | 是 | 命名空间名 | +| `IsAutoScan` | Boolean | 否 | `true` = 开启自动扫描(新推送镜像自动触发安全扫描) | +| `IsPreventVUL` | Boolean | 否 | `true` = 阻断漏洞镜像拉取(需配合 `Severity` 使用) | +| `Severity` | String | 否 | 漏洞严重级别阈值:`low` / `medium` / `high` | + +## 操作步骤 + +以下操作以实例 `tcr-example`、地域 `ap-guangzhou` 为例。命名空间名为自定义值(本例 `example-ns`)。 + +### 步骤1:创建命名空间 + +#### 选择依据 + +- **`is_public` 选 `true`**(公开):允许匿名拉取镜像,无需凭证,适用于开源项目或公共基础镜像。备选方案为 `false`(私有命名空间,拉取需 `docker login` 认证),适合企业内部项目。 +- **名称规划**:按团队/项目命名(如 `team-backend`、`project-api`),名称一旦创建不可修改。 + +```bash +tccli tcr CreateNamespace \ + --RegistryId tcr-example \ + --NamespaceName example-ns \ + --IsPublic true \ + --region ap-guangzhou --output json +``` + +**输出**: + +```json +{ + "RequestId": "3c890bbf-de40-486d-ab6a-b38450cb99fa" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `tcr-example` | 实例 ID | 实例状态须 `Running` | `DescribeInstances` 获取 | +| `example-ns` | 命名空间名称 | 小写字母+数字+连字符,2--30 字符,实例内唯一 | 自定义 | + +> `CreateNamespace` 同步返回,无需等待异步完成。 + +### 步骤2:查询命名空间列表 + +```bash +tccli tcr DescribeNamespaces --RegistryId tcr-example --region ap-guangzhou --output json +``` + +**输出**: + +```json +{ + "NamespaceList": [ + { + "Name": "example-ns", + "NamespaceId": 2, + "Public": true, + "AutoScan": false, + "CreationTime": "2026-06-18T09:32:47.946Z" + } + ], + "TotalCount": 1, + "RequestId": "..." +} +``` + +- `NamespaceId`:系统分配的整数 ID,部分内层 API(如 `CreateTagRetentionRule`)使用该数字引用命名空间 +- `Public`:对应创建时 `IsPublic` 参数 +- `AutoScan`:是否已开启自动安全扫描,默认 `false` +- `CreationTime`:命名空间创建时间(UTC) + +### 步骤3:开启自动安全扫描 + +`auto_scan` 按需开启:开启后,新推送至该命名空间内任意镜像仓库的镜像将自动触发安全扫描,适合生产环境或 CI/CD 频繁推送场景。已有历史镜像不会回溯扫描,需重新推送或手动触发。 + +```bash +tccli tcr ModifyNamespace \ + --RegistryId tcr-example \ + --NamespaceName example-ns \ + --IsAutoScan true \ + --region ap-guangzhou --output json +``` + +**输出**: + +```json +{ + "RequestId": "..." +} +``` + +> **注意**:`IsAutoScan` 状态更新存在延迟,执行 `DescribeNamespaces` 确认可能需要等待 10--30 秒。开启后仅对新推送的镜像生效。`ModifyNamespace` 的 `IsPreventVUL`、`Severity` 等漏洞阻断参数也可后续按需修改。 + +### 步骤4:删除命名空间 + +> **危险警告**:删除命名空间将**级联删除**其下所有镜像仓库和镜像版本,数据不可恢复。删除前务必用 `DescribeRepositories` 确认仓库列表。 + +#### 4a. 删除前检查 + +```bash +tccli tcr DescribeRepositories \ + --RegistryId tcr-example \ + --NamespaceName example-ns \ + --region ap-guangzhou --output json +``` + +**输出**(示例为空): + +```json +{ + "RepositoryList": [], + "TotalCount": 0, + "RequestId": "..." +} +``` + +> 若 `TotalCount > 0`,需先逐仓库删除镜像 Tag,再 `DeleteRepository` 清理仓库后,方可删除命名空间。 + +#### 4b. 执行删除 + +```bash +tccli tcr DeleteNamespace \ + --RegistryId tcr-example \ + --NamespaceName example-ns \ + --region ap-guangzhou --output json +``` + +**输出**: + +```json +{ + "RequestId": "..." +} +``` + +## 验证 + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 命名空间已创建 | `DescribeNamespaces --RegistryId tcr-example --region ap-guangzhou` | `NamespaceList` 包含 `example-ns` | +| 公开/私有属性正确 | 同上,过滤 `NamespaceList[?Name=='example-ns'].Public` | 与创建参数一致(如 `true`) | +| 自动扫描已开启 | 同上,过滤 `NamespaceList[?Name=='example-ns'].AutoScan` | `true`(等待 10--30 秒刷新) | +| 命名空间已删除 | `DescribeNamespaces --RegistryId tcr-example --region ap-guangzhou` | `NamespaceList` 中无 `example-ns` | + +JMESPath 精确验证示例: + +```bash +# 验证 Public 属性 +tccli tcr DescribeNamespaces --RegistryId tcr-example --region ap-guangzhou --output json \ + --filter "NamespaceList[?Name=='example-ns']|[0].Public" +# expected: true + +# 验证 AutoScan +tccli tcr DescribeNamespaces --RegistryId tcr-example --region ap-guangzhou --output json \ + --filter "NamespaceList[?Name=='example-ns']|[0].AutoScan" +# expected: true +``` + +## 清理 + +```bash +# 1. 检查命名空间下残留仓库 +tccli tcr DescribeRepositories --RegistryId tcr-example --NamespaceName example-ns --region ap-guangzhou --output json +# expected: TotalCount: 0(若有残留,先逐仓库删除镜像 Tag 后 DeleteRepository) + +# 2. 删除命名空间 +tccli tcr DeleteNamespace --RegistryId tcr-example --NamespaceName example-ns --region ap-guangzhou --output json +# expected: exit 0 + +# 3. 验证已删除 +tccli tcr DescribeNamespaces --RegistryId tcr-example --region ap-guangzhou --output json +# expected: NamespaceList 中无 example-ns +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `CreateNamespace` 返回 `MissingParameter.RegistryId` | 检查命令行 | 忘记传 `--RegistryId` 参数 | 所有命名空间操作都需要指定 `RegistryId`,用 `DescribeInstances` 获取 | +| `CreateNamespace` 返回 `InvalidParameter.NamespaceName` | 检查 `--NamespaceName` 值 | 命名空间名格式不合法 | 命名空间名必须符合规则:仅小写字母、数字和连字符(`-`),不能以连字符开头/结尾/连续 | +| `CreateNamespace` 返回命名空间名重复错误 | `DescribeNamespaces` 查看已有名称列表 | 同实例下名称已存在 | 更换名称,或先删除同名命名空间后重试 | +| `ModifyNamespace` 返回 `ResourceNotFound` | `DescribeNamespaces` 确认目标命名空间存在 | `RegistryId` 或 `NamespaceName` 错误 | 用 `DescribeNamespaces` 获取正确的 `Name` 值 | +| `DeleteNamespace` 返回 `ResourceNotFound` | 同上 | 命名空间已不存在或已被删除 | 确认参数正确,或命名空间已由其他操作删除 | +| `DeleteNamespace` 返回资源不空错误 | `DescribeRepositories --NamespaceName ` 检查 | 命名空间下仍有镜像仓库或 Helm Chart | 先删除仓库内所有镜像 Tag,再 `DeleteRepository` 逐一清理仓库 | +| 子账号操作被拒绝 | 联系主账号确认 CAM 策略 | 缺少 `tcr:CreateNamespace` / `tcr:ModifyNamespace` / `tcr:DeleteNamespace` 权限 | 主账号授予 `QcloudTCRFullAccess` 或添加命名空间相关最小权限策略 | + +### 状态不一致 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ModifyNamespace --IsAutoScan true` 后 `AutoScan` 仍然 `false` | 再次执行 `DescribeNamespaces` 等待 10--30 秒 | IsAutoScan 状态更新存在延迟 | 等待后重试确认。注意仅新推送镜像生效,已有镜像不受影响 | + +## 下一步 + +- [管理镜像仓库](../repository)(page_id `41811`) — 在命名空间下创建和管理镜像仓库 +- [容器镜像安全扫描](../../../image-security/vulnerability-scan)(page_id `43941`) — 查看和管理镜像安全扫描结果 +- [镜像版本不可变](../../../image-security/immutable-tags)(page_id `58147`) — 设置镜像 Tag 不可变策略 +- [高危镜像部署阻断](../../../image-security/deployment-block)(page_id `58145`) — 配合 `IsPreventVUL` 阻断漏洞镜像拉取 +- [托管 Helm Chart](../../../oci-artifacts/helm-chart)(page_id `41944`) — 在同一命名空间管理 Helm Chart +- [创建企业版实例](../../../create)(page_id `51110`) — 创建新的 TCR 企业版实例 + +## 控制台替代 + +[容器镜像服务 -> 命名空间](https://console.cloud.tencent.com/tcr/namespace):选择目标实例,进入命名空间列表页。单击"新建",填写命名空间名称、选择公开或私有访问级别,按需添加标签后确认。在命名空间行可直接切换访问级别、开关自动扫描、执行删除操作。 diff --git a/src/content/docs/cli/tcr/ops/image-creation/namespace/namespace-tcrctl.md b/src/content/docs/cli/tcr/ops/image-creation/namespace/namespace-tcrctl.md new file mode 100644 index 0000000..b8f368e --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-creation/namespace/namespace-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "管理命名空间(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[管理命名空间](https://cloud.tencent.com/document/product/1141/41803) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/image-creation/namespace/namespace.md b/src/content/docs/cli/tcr/ops/image-creation/namespace/namespace.md new file mode 100644 index 0000000..adfe2c8 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-creation/namespace/namespace.md @@ -0,0 +1,288 @@ +--- +title: "管理命名空间(tccli)" +description: "· page_id `41803`" +--- + +> 对照官方:[管理命名空间](https://cloud.tencent.com/document/product/1141/41803) · page_id `41803` + +## 概述 + +通过 tccli 管理 TCR 企业版实例内的命名空间。命名空间是容器镜像仓库和 Helm Chart 的逻辑分组单元,用于按团队、项目或环境划分资源边界。自身不直接存储镜像数据,但对下级仓库的访问权限、安全扫描和部署阻断策略由命名空间统一控制。 + +核心操作覆盖:创建(含公开/私有策略)、查询、修改(自动扫描、漏洞阻断)、删除。所有操作依赖 `--RegistryId`(实例 ID),由 `DescribeInstances` 获取。 + +命名空间不收费,但创建后若在内部上传镜像,会产生 COS 存储费用。详见[计费概述](https://cloud.tencent.com/document/product/1141/40540)。 + +## 前置条件 + +- [环境准备](../../../index.md) +- 已 [购买企业版实例](../../../create) 且实例 `Status` 为 `Running` +- CAM 权限包含:`tcr:CreateNamespace`、`tcr:DescribeNamespaces`、`tcr:ModifyNamespace`、`tcr:DeleteNamespace` + +### 环境检查 + +```bash +# 1. 确认实例存在且状态正常 +tccli tcr DescribeInstances --Registryids '["tcr-example"]' --region ap-guangzhou --output json +``` + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "tcr-example", + "RegistryType": "basic", + "Status": "Running", + "PublicDomain": "tcr-example.tencentcloudcr.com", + "InternalEndpoint": "10.1.67.13", + "EnableAnonymous": true, + "TokenValidTime": 87600, + "DeletionProtection": false + } + ], + "RequestId": "..." +} +``` + +```bash +# 2. 确认 CAM 权限(查询命名空间列表不报 UnauthorizedOperation) +tccli tcr DescribeNamespaces --RegistryId tcr-example --region ap-guangzhou --output json +``` + +### 命名约束 + +命名空间名仅支持小写字母、数字及连字符(`-`),不能以连字符开头或结尾,不能出现连续连字符。长度限制 2--30 个字符,同一实例内名称必须唯一。名称一旦创建不可修改。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 / 参数 | 幂等 | +|-----------|------------------|:--:| +| 查看命名空间列表 | `DescribeNamespaces --RegistryId ` | 是 | +| 新建命名空间(输入名称) | `CreateNamespace --NamespaceName ` | 否(同名报错) | +| 选择公开/私有 | `--IsPublic true/false` | — | +| 开启自动扫描 | `ModifyNamespace --IsAutoScan true` | 是 | +| 阻断高危镜像部署 | `ModifyNamespace --IsPreventVUL true` | 是 | +| 设置漏洞严重级别 | `ModifyNamespace --Severity ` | 是 | +| 删除命名空间 | `DeleteNamespace --RegistryId --NamespaceName ` | 否(级联删除不可恢复) | + +## 关键字段说明 + +### CreateNamespace + +| 字段 | 类型 | 必填 | 取值与约束 | +|------|------|:--:|------| +| `RegistryId` | String | 是 | 目标实例 ID(`DescribeInstances` 获取) | +| `NamespaceName` | String | 是 | 命名空间名,小写字母+数字+连字符,2--30 字符,实例内唯一 | +| `IsPublic` | Boolean | 否 | 默认 `false`。`true` = 公开(允许匿名拉取),`false` = 私有(需凭证访问) | +| `TagSpecification` | Object | 否 | `ResourceType`: `"namespace"`,`Tags`: 标签数组 | + +### ModifyNamespace + +| 字段 | 类型 | 必填 | 取值与约束 | +|------|------|:--:|------| +| `RegistryId` | String | 是 | 目标实例 ID | +| `NamespaceName` | String | 是 | 命名空间名 | +| `IsAutoScan` | Boolean | 否 | `true` = 开启自动扫描(新推送镜像自动触发安全扫描) | +| `IsPreventVUL` | Boolean | 否 | `true` = 阻断漏洞镜像拉取(需配合 `Severity` 使用) | +| `Severity` | String | 否 | 漏洞严重级别阈值:`low` / `medium` / `high` | + +## 操作步骤 + +以下操作以实例 `tcr-example`、地域 `ap-guangzhou` 为例。命名空间名为自定义值(本例 `example-ns`)。 + +### 步骤1:创建命名空间 + +#### 选择依据 + +- **`is_public` 选 `true`**(公开):允许匿名拉取镜像,无需凭证,适用于开源项目或公共基础镜像。备选方案为 `false`(私有命名空间,拉取需 `docker login` 认证),适合企业内部项目。 +- **名称规划**:按团队/项目命名(如 `team-backend`、`project-api`),名称一旦创建不可修改。 + +```bash +tccli tcr CreateNamespace \ + --RegistryId tcr-example \ + --NamespaceName example-ns \ + --IsPublic true \ + --region ap-guangzhou --output json +``` + +**输出**: + +```json +{ + "RequestId": "3c890bbf-de40-486d-ab6a-b38450cb99fa" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `tcr-example` | 实例 ID | 实例状态须 `Running` | `DescribeInstances` 获取 | +| `example-ns` | 命名空间名称 | 小写字母+数字+连字符,2--30 字符,实例内唯一 | 自定义 | + +> `CreateNamespace` 同步返回,无需等待异步完成。 + +### 步骤2:查询命名空间列表 + +```bash +tccli tcr DescribeNamespaces --RegistryId tcr-example --region ap-guangzhou --output json +``` + +**输出**: + +```json +{ + "NamespaceList": [ + { + "Name": "example-ns", + "NamespaceId": 2, + "Public": true, + "AutoScan": false, + "CreationTime": "2026-06-18T09:32:47.946Z" + } + ], + "TotalCount": 1, + "RequestId": "..." +} +``` + +- `NamespaceId`:系统分配的整数 ID,部分内层 API(如 `CreateTagRetentionRule`)使用该数字引用命名空间 +- `Public`:对应创建时 `IsPublic` 参数 +- `AutoScan`:是否已开启自动安全扫描,默认 `false` +- `CreationTime`:命名空间创建时间(UTC) + +### 步骤3:开启自动安全扫描 + +`auto_scan` 按需开启:开启后,新推送至该命名空间内任意镜像仓库的镜像将自动触发安全扫描,适合生产环境或 CI/CD 频繁推送场景。已有历史镜像不会回溯扫描,需重新推送或手动触发。 + +```bash +tccli tcr ModifyNamespace \ + --RegistryId tcr-example \ + --NamespaceName example-ns \ + --IsAutoScan true \ + --region ap-guangzhou --output json +``` + +**输出**: + +```json +{ + "RequestId": "..." +} +``` + +> **注意**:`IsAutoScan` 状态更新存在延迟,执行 `DescribeNamespaces` 确认可能需要等待 10--30 秒。开启后仅对新推送的镜像生效。`ModifyNamespace` 的 `IsPreventVUL`、`Severity` 等漏洞阻断参数也可后续按需修改。 + +### 步骤4:删除命名空间 + +> **危险警告**:删除命名空间将**级联删除**其下所有镜像仓库和镜像版本,数据不可恢复。删除前务必用 `DescribeRepositories` 确认仓库列表。 + +#### 4a. 删除前检查 + +```bash +tccli tcr DescribeRepositories \ + --RegistryId tcr-example \ + --NamespaceName example-ns \ + --region ap-guangzhou --output json +``` + +**输出**(示例为空): + +```json +{ + "RepositoryList": [], + "TotalCount": 0, + "RequestId": "..." +} +``` + +> 若 `TotalCount > 0`,需先逐仓库删除镜像 Tag,再 `DeleteRepository` 清理仓库后,方可删除命名空间。 + +#### 4b. 执行删除 + +```bash +tccli tcr DeleteNamespace \ + --RegistryId tcr-example \ + --NamespaceName example-ns \ + --region ap-guangzhou --output json +``` + +**输出**: + +```json +{ + "RequestId": "..." +} +``` + +## 验证 + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 命名空间已创建 | `DescribeNamespaces --RegistryId tcr-example --region ap-guangzhou` | `NamespaceList` 包含 `example-ns` | +| 公开/私有属性正确 | 同上,过滤 `NamespaceList[?Name=='example-ns'].Public` | 与创建参数一致(如 `true`) | +| 自动扫描已开启 | 同上,过滤 `NamespaceList[?Name=='example-ns'].AutoScan` | `true`(等待 10--30 秒刷新) | +| 命名空间已删除 | `DescribeNamespaces --RegistryId tcr-example --region ap-guangzhou` | `NamespaceList` 中无 `example-ns` | + +JMESPath 精确验证示例: + +```bash +# 验证 Public 属性 +tccli tcr DescribeNamespaces --RegistryId tcr-example --region ap-guangzhou --output json \ + --filter "NamespaceList[?Name=='example-ns']|[0].Public" +# expected: true + +# 验证 AutoScan +tccli tcr DescribeNamespaces --RegistryId tcr-example --region ap-guangzhou --output json \ + --filter "NamespaceList[?Name=='example-ns']|[0].AutoScan" +# expected: true +``` + +## 清理 + +```bash +# 1. 检查命名空间下残留仓库 +tccli tcr DescribeRepositories --RegistryId tcr-example --NamespaceName example-ns --region ap-guangzhou --output json +# expected: TotalCount: 0(若有残留,先逐仓库删除镜像 Tag 后 DeleteRepository) + +# 2. 删除命名空间 +tccli tcr DeleteNamespace --RegistryId tcr-example --NamespaceName example-ns --region ap-guangzhou --output json +# expected: exit 0 + +# 3. 验证已删除 +tccli tcr DescribeNamespaces --RegistryId tcr-example --region ap-guangzhou --output json +# expected: NamespaceList 中无 example-ns +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `CreateNamespace` 返回 `MissingParameter.RegistryId` | 检查命令行 | 忘记传 `--RegistryId` 参数 | 所有命名空间操作都需要指定 `RegistryId`,用 `DescribeInstances` 获取 | +| `CreateNamespace` 返回 `InvalidParameter.NamespaceName` | 检查 `--NamespaceName` 值 | 命名空间名格式不合法 | 命名空间名必须符合规则:仅小写字母、数字和连字符(`-`),不能以连字符开头/结尾/连续 | +| `CreateNamespace` 返回命名空间名重复错误 | `DescribeNamespaces` 查看已有名称列表 | 同实例下名称已存在 | 更换名称,或先删除同名命名空间后重试 | +| `ModifyNamespace` 返回 `ResourceNotFound` | `DescribeNamespaces` 确认目标命名空间存在 | `RegistryId` 或 `NamespaceName` 错误 | 用 `DescribeNamespaces` 获取正确的 `Name` 值 | +| `DeleteNamespace` 返回 `ResourceNotFound` | 同上 | 命名空间已不存在或已被删除 | 确认参数正确,或命名空间已由其他操作删除 | +| `DeleteNamespace` 返回资源不空错误 | `DescribeRepositories --NamespaceName ` 检查 | 命名空间下仍有镜像仓库或 Helm Chart | 先删除仓库内所有镜像 Tag,再 `DeleteRepository` 逐一清理仓库 | +| 子账号操作被拒绝 | 联系主账号确认 CAM 策略 | 缺少 `tcr:CreateNamespace` / `tcr:ModifyNamespace` / `tcr:DeleteNamespace` 权限 | 主账号授予 `QcloudTCRFullAccess` 或添加命名空间相关最小权限策略 | + +### 状态不一致 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ModifyNamespace --IsAutoScan true` 后 `AutoScan` 仍然 `false` | 再次执行 `DescribeNamespaces` 等待 10--30 秒 | IsAutoScan 状态更新存在延迟 | 等待后重试确认。注意仅新推送镜像生效,已有镜像不受影响 | + +## 下一步 + +- [管理镜像仓库](../repository)(page_id `41811`) — 在命名空间下创建和管理镜像仓库 +- [容器镜像安全扫描](../../../image-security/vulnerability-scan)(page_id `43941`) — 查看和管理镜像安全扫描结果 +- [镜像版本不可变](../../../image-security/immutable-tags)(page_id `58147`) — 设置镜像 Tag 不可变策略 +- [高危镜像部署阻断](../../../image-security/deployment-block)(page_id `58145`) — 配合 `IsPreventVUL` 阻断漏洞镜像拉取 +- [托管 Helm Chart](../../../oci-artifacts/helm-chart)(page_id `41944`) — 在同一命名空间管理 Helm Chart +- [创建企业版实例](../../../create)(page_id `51110`) — 创建新的 TCR 企业版实例 + +## 控制台替代 + +[容器镜像服务 -> 命名空间](https://console.cloud.tencent.com/tcr/namespace):选择目标实例,进入命名空间列表页。单击"新建",填写命名空间名称、选择公开或私有访问级别,按需添加标签后确认。在命名空间行可直接切换访问级别、开关自动扫描、执行删除操作。 diff --git a/src/content/docs/cli/tcr/ops/image-creation/repository-tcrctl.md b/src/content/docs/cli/tcr/ops/image-creation/repository-tcrctl.md new file mode 100644 index 0000000..269906c --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-creation/repository-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "管理镜像仓库(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[管理镜像仓库](https://cloud.tencent.com/document/product/1141/41811) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/image-creation/repository.md b/src/content/docs/cli/tcr/ops/image-creation/repository.md new file mode 100644 index 0000000..fd0d3f7 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-creation/repository.md @@ -0,0 +1,459 @@ +--- +title: "管理镜像仓库" +description: "· page_id `41811`" +--- + +> 对照官方:[管理镜像仓库](https://cloud.tencent.com/document/product/1141/41811) · page_id `41811` + +## 概述 + +TCR 企业版镜像仓库用于管理容器镜像。单个镜像仓库可包含不同版本的容器镜像(以 Tag 区分),归属于命名空间并从命名空间继承公开/私有属性及安全扫描触发方式。镜像仓库是 TCR 权限管理的最小单位,实例管理员可按仓库粒度授予子账号管理或只读权限。 + +## 前置条件 + +- [环境准备](../../../index.md) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置 + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tcr:CreateRepository, tcr:DescribeRepositories, tcr:ModifyRepository, tcr:DeleteRepository +# tcr:DescribeImages, tcr:DescribeImageManifests, tcr:DeleteImage +# 验证:执行 DescribeRepositories 确认权限 +tccli tcr DescribeRepositories --RegistryId '' --region +# expected: exit 0,返回仓库列表(可为空) +``` + +### 资源检查 + +```bash +# 4. 确认实例存在且状态正常 +tccli tcr DescribeInstances --Registryids '[""]' --region +# expected: exit 0,Status 为 "Running" + +# 5. 确认命名空间已存在 +tccli tcr DescribeNamespaces --RegistryId '' --region +# expected: target 命名空间在 NamespaceList 中 +``` + +### 命名约束 + +镜像仓库名称长度 2–200 个字符,只能包含小写字母、数字及分隔符(`.`、`_`、`-`、`/`),不能以分隔符开头、结尾或连续。名称支持多级路径,例如 `team-01/front/nginx`。 + +### `BriefDescription` 字段 + +`BriefDescription` 是 `CreateRepository` 的必填字段,在仓库列表中展示,帮助识别仓库用途。建议填写有意义的简短描述(如"用户服务 API 镜像")。创建后可随时通过 `ModifyRepository` 修改。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 创建镜像仓库 | `CreateRepository` | 否 | +| 查看仓库列表 | `DescribeRepositories` | 是 | +| 筛选指定命名空间 | `DescribeRepositories --NamespaceName` | 是 | +| 模糊搜索仓库名 | `DescribeRepositories --RepositoryName` | 是 | +| 编辑简短描述/详细描述 | `ModifyRepository` | 是 | +| 删除镜像仓库 | `DeleteRepository` | 否 | +| 查看镜像版本列表 | `DescribeImages` | 是 | +| 搜索镜像版本 | `DescribeImages --ImageVersion` | 是 | +| 查看镜像 Manifest | `DescribeImageManifests` | 是 | +| 删除镜像版本 | `DeleteImage` | 否 | +| 推送/拉取镜像 | `docker push` / `docker pull` | — | + +## 关键字段说明 + +以下说明 `CreateRepository` 和 `ModifyRepository` 的主要参数。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | 实例 ID,格式 `tcr-xxxxxxxx` | 实例不存在 → `ResourceNotFound` | +| `NamespaceName` | String | 是 | 已存在的命名空间名称 | 命名空间不存在 → `ResourceNotFound` | +| `RepositoryName` | String | 是 | 长度 2–200 字符,小写字母/数字/`._-/`,不能以分隔符开头结尾或连续。同命名空间内唯一 | 格式不合法 → `InvalidParameter`;重名 → 创建失败 | +| `BriefDescription` | String | 是 | 简短描述,最长 100 字符。`ModifyRepository` 时必填 | 缺失 → `InvalidParameter` | +| `Description` | String | 否 | 详细描述,支持 Markdown,最长 1000 字符。`ModifyRepository` 时必填 | 缺失 → `InvalidParameter` | + +> **注意**:`ModifyRepository` 的 `BriefDescription` 和 `Description` 均为必填参数。修改任一字段时需同时传入另一个字段的当前值,否则将被清空。 + +## 操作步骤 + +### 创建镜像仓库 + +#### 选择依据 + +- **命名空间归属**:仓库必须归属于一个已存在的命名空间。命名空间决定了仓库的公开/私有属性和安全扫描策略。 +- **名称规划**:支持多级路径(如 `backend/api/user`),可按微服务层级组织。名称创建后不可修改。 +- **简短描述**:在仓库列表中展示,建议写清楚仓库用途(如"用户服务 API 镜像")。 + +#### 最小创建(只含必填字段) + +`create-repository.json`: + +```json +{ + "RegistryId": "", + "NamespaceName": "", + "RepositoryName": "", + "BriefDescription": "" +} +``` + +```bash +tccli tcr CreateRepository --cli-input-json file://create-repository.json --region +# expected: exit 0,返回 RequestId +``` + +**输出**: + +```json +{ + "RequestId": "b7995733-3abb-4c44-95d3-1e87942973b3" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | 实例 ID | 格式 `tcr-xxxxxxxx` | `tccli tcr DescribeInstances` | +| `` | 命名空间名称 | 已存在 | `tccli tcr DescribeNamespaces` | +| `` | 仓库名称 | 长度 2–200,小写字母/数字/`._-/`,不以分隔符开头结尾或连续 | 自定义 | +| `` | 地域 | 如 `ap-guangzhou` | `tccli configure list` | + +#### 增强配置(含详细描述) + +`create-repository-enhanced.json`: + +```json +{ + "RegistryId": "", + "NamespaceName": "", + "RepositoryName": "", + "BriefDescription": "", + "Description": "" +} +``` + +```bash +tccli tcr CreateRepository --cli-input-json file://create-repository-enhanced.json --region +# expected: exit 0,返回 RequestId +``` + +### 查看镜像仓库列表 + +列出实例内所有仓库: + +```bash +tccli tcr DescribeRepositories --RegistryId '' --region +# expected: exit 0,返回 RepositoryList 和 TotalCount +``` + +**输出**: + +```json +{ + "RepositoryList": [ + { + "Name": "example-ns/example-repo", + "Namespace": "example-ns", + "Public": true, + "BriefDescription": "tccli rewrite example repository", + "Description": "" + } + ], + "TotalCount": 1, + "RequestId": "7b568344-2d2c-454a-aaab-422ee390d42d" +} +``` + +**筛选指定命名空间**: + +```bash +tccli tcr DescribeRepositories \ + --RegistryId '' \ + --NamespaceName '' \ + --region +``` + +**模糊搜索仓库名**: + +```bash +tccli tcr DescribeRepositories \ + --RegistryId '' \ + --RepositoryName '' \ + --region +``` + +### 编辑仓库描述 + +#### 选择依据 + +- `BriefDescription` 和 `Description` 均为 `ModifyRepository` 的必填参数。修改任一字段时,必须同时传入另一个字段的当前值,否则未传入字段的值将被清空。 +- **建议做法**:修改前先用 `DescribeRepositories` 获取当前值,再构造修改命令。 + +`modify-repository.json`: + +```json +{ + "RegistryId": "", + "NamespaceName": "", + "RepositoryName": "", + "BriefDescription": "", + "Description": "" +} +``` + +```bash +tccli tcr ModifyRepository --cli-input-json file://modify-repository.json --region +# expected: exit 0,返回 RequestId +``` + +### 查看镜像版本 + +列出指定仓库内的所有镜像版本: + +```bash +tccli tcr DescribeImages \ + --RegistryId '' \ + --NamespaceName '' \ + --RepositoryName '' \ + --region +# expected: exit 0,返回 ImageInfoList 和 TotalCount +``` + +**输出**: + +```json +{ + "ImageInfoList": [], + "TotalCount": 0, + "RequestId": "da668a99-0af3-4a9f-9d7e-b1c376e637bd" +} +``` + +> 新仓库创建后尚未推送镜像时,`ImageInfoList` 为空是正常行为。使用 `docker push` 推送镜像后重新执行即可看到版本列表。 + +**搜索特定版本**: + +```bash +tccli tcr DescribeImages \ + --RegistryId '' \ + --NamespaceName '' \ + --RepositoryName '' \ + --ImageVersion '' \ + --region +``` + +### 查看镜像 Manifest + +查看指定 Tag 的镜像 Manifest 信息: + +```bash +tccli tcr DescribeImageManifests \ + --RegistryId '' \ + --NamespaceName '' \ + --RepositoryName '' \ + --ImageVersion '' \ + --region +# expected: 仓库有镜像时 exit 0;无镜像时 exit 255 +``` + +**预期输出(仓库为空,无镜像时)**: + +``` +Error: tag latest not found in example-ns/example-repo +``` + +> `DescribeImageManifests` 需要仓库中已存在目标 Tag 的镜像。仓库为空时该命令会失败。需先用 `docker push` 推送镜像后重试。 + +### 删除镜像版本 + +`delete-image.json`: + +```json +{ + "RegistryId": "", + "NamespaceName": "", + "RepositoryName": "", + "ImageVersion": "" +} +``` + +```bash +tccli tcr DeleteImage --cli-input-json file://delete-image.json --region +# expected: exit 0,返回 RequestId +``` + +**输出**: + +```json +{ + "RequestId": "a0c88b58-ade2-4756-99fb-e1a21dc0fe17" +} +``` + +> **注意**:`DeleteImage` 只删除指定版本,不影响同一仓库中其他版本。但若被删版本与其他版本共享同一 Digest(相同镜像内容的不同 Tag),则共享该 Digest 的所有其他 Tag 也会变为不可用。 + +### 删除镜像仓库 + +#### 选择依据 + +- 删除仓库会同时清除其下所有镜像版本,不可逆。执行前须二次确认。 +- `ForceDelete` 参数可跳过部分依赖校验(如触发器、复制规则),仅在有明确需要时使用。 + +```bash +tccli tcr DeleteRepository \ + --RegistryId '' \ + --NamespaceName '' \ + --RepositoryName '' \ + --region +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "b5499668-30a7-4da3-b516-d2c0ae32ea60" +} +``` + +> **高危警告**:`DeleteRepository` 会删除仓库及其下所有镜像版本,不可恢复。执行前务必确认仓库内无需要保留的镜像。仅需清理部分版本时优先使用 `DeleteImage`。 + +## 验证 + +### 控制面(tccli) + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 仓库已创建 | `DescribeRepositories --NamespaceName ''` | 目标仓库在 `RepositoryList` 中,`Name`/`Description` 与创建参数一致 | +| 描述已更新 | `DescribeRepositories --NamespaceName '' --RepositoryName ''` | `Description` 与修改参数一致 | +| 镜像版本存在 | `DescribeImages --NamespaceName '' --RepositoryName ''` | `ImageInfoList` 包含预期版本 | +| 版本已删除 | `DescribeImages --NamespaceName '' --RepositoryName ''` | 目标版本不再出现在 `ImageInfoList` 中 | +| 仓库已删除 | `DescribeRepositories --NamespaceName ''` | 目标仓库不再出现在 `RepositoryList` 中 | + +验证仓库详情: + +```bash +tccli tcr DescribeRepositories \ + --RegistryId '' \ + --NamespaceName '' \ + --RepositoryName '' \ + --region +# expected: 返回目标仓库完整信息,Description 与预期一致 +``` + +### 数据面(docker) + +推送和拉取验证需配置访问凭证后执行: + +```bash +# 登录实例 +docker login --username +# expected: Login Succeeded + +# 拉取镜像(验证仓库可访问) +docker pull //: +# expected: 镜像拉取成功,显示 Digest +``` + +## 清理 + +> **警告**:`DeleteRepository` 不可逆,会同时删除其下所有镜像版本。`DeleteImage` 删除共享 Digest 的版本会影响其他同名 Tag。生产环境执行前务必用 `DescribeRepositories` / `DescribeImages` 确认目标。 + +### 1. 清理前检查 + +```bash +# 确认待删除仓库及其镜像版本 +tccli tcr DescribeRepositories \ + --RegistryId '' \ + --NamespaceName '' \ + --RepositoryName '' \ + --region +# 记录 RepositoryName,确认是目标仓库 + +tccli tcr DescribeImages \ + --RegistryId '' \ + --NamespaceName '' \ + --RepositoryName '' \ + --region +# 记录所有 ImageVersion,确认无遗漏 +``` + +### 2. 删除镜像版本 + +`delete-image.json`: + +```json +{ + "RegistryId": "", + "NamespaceName": "", + "RepositoryName": "", + "ImageVersion": "" +} +``` + +```bash +tccli tcr DeleteImage --cli-input-json file://delete-image.json --region +# expected: exit 0,返回 RequestId +``` + +### 3. 删除镜像仓库 + +```bash +tccli tcr DeleteRepository \ + --RegistryId '' \ + --NamespaceName '' \ + --RepositoryName '' \ + --region +# expected: exit 0,返回 RequestId +``` + +### 4. 验证已删除 + +```bash +tccli tcr DescribeRepositories \ + --RegistryId '' \ + --NamespaceName '' \ + --region +# expected: 目标仓库不再出现在 RepositoryList 中 +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `CreateRepository` 返回 `ResourceNotFound.Namespace` | `tccli tcr DescribeNamespaces --RegistryId '' --region ` 确认命名空间是否存在 | 指定的 `NamespaceName` 不存在,或拼写错误 | 先 [创建命名空间](../namespace),确认 `NamespaceName` 拼写正确 | +| `CreateRepository` 返回 `ResourceAlreadyExists.Repository` | `DescribeRepositories --NamespaceName ''` 查看已有仓库 | 同命名空间下仓库名已存在 | 更换 `` 或先删除已有仓库 | +| `CreateRepository` 返回 `InvalidParameter` | 检查 `` 是否符合命名规则 | 仓库名含非法字符、长度超限或以分隔符开头/结尾/连续 | 修正名称:小写字母/数字/`._-/`,长度 2–200,不以分隔符开头结尾或连续 | +| `ModifyRepository` 返回 `InvalidParameter` | 检查是否同时传入了 `BriefDescription` 和 `Description` | 两个字段均为必填,缺少任一字段 | 确保 JSON 中同时包含 `BriefDescription` 和 `Description`,修改其中一个时传入另一个的当前值 | +| `DeleteRepository` 返回 `FailedOperation` | `DescribeRepositories` 确认仓库状态 | 仓库存在依赖(如镜像复制规则、触发器绑定) | 先解除依赖后重试,或使用 `"ForceDelete": true` 跳过校验 | +| `DeleteImage` 返回错误 | `DescribeImages` 确认版本存在 | 目标版本不存在或 Digest 已被引用 | 检查 `` 是否正确,确认版本存在 | +| `DescribeImages` 返回空列表 | 确认 `NamespaceName` 和 `RepositoryName` 均正确 | 仓库下未推送过镜像,或仓库/命名空间不存在 | 确认仓库已创建;若已创建,推送镜像后重试 | +| `DescribeImageManifests` 返回 `tag latest not found in /` | `DescribeImages` 查看仓库是否有镜像版本 | 仓库为空或指定 Tag 不存在——尚未 `docker push` 镜像,或 Tag 名错误 | 先 `docker push //:` 推送镜像,再执行 `DescribeImageManifests` | +| CAM 权限拒绝 | `tccli tcr DescribeRepositories --RegistryId '' --region ` 检查返回 | 子账号缺少 `tcr:CreateRepository` / `tcr:DeleteRepository` 等权限 | 联系主账号授予 `QcloudTCRFullAccess` 或添加仓库相关最小权限策略 | + +### 操作成功但有数据影响 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `DeleteImage` 成功后其他版本不可用 | `DescribeImages` 检查剩余版本的 `Digest` 值 | 被删版本与其他 Tag 共享同一 Digest,删除 Tag 后 Digest 被移除导致共享版本不可用 | 不可恢复。删除前确认版本间 Digest 关系 | +| `ModifyRepository` 后 Description 被清空 | `DescribeRepositories` 检查 `Description` 字段 | 修改 `BriefDescription` 时未传入 `Description` 当前值 | 重新执行 `ModifyRepository`,同时传入两个字段的期望值 | + +## 下一步 + +- [管理命名空间](../namespace)(page_id `41803`) +- [同实例多地域复制镜像](../../image-distribution/cross-region-replication)(page_id `52095`) +- [触发器(Webhook)](../../devops/webhook) +- [企业版快速入门](../../../quickstart/enterprise) + +## 控制台替代 + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 选择实例 → **镜像仓库** → 单击 **新建**,选择命名空间、填写仓库名称和描述后确认。在仓库详情页可编辑信息、管理镜像版本、查看 Manifest。 diff --git a/src/content/docs/cli/tcr/ops/image-creation/repository/repository-tcrctl.md b/src/content/docs/cli/tcr/ops/image-creation/repository/repository-tcrctl.md new file mode 100644 index 0000000..269906c --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-creation/repository/repository-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "管理镜像仓库(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[管理镜像仓库](https://cloud.tencent.com/document/product/1141/41811) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/image-creation/repository/repository.md b/src/content/docs/cli/tcr/ops/image-creation/repository/repository.md new file mode 100644 index 0000000..fd0d3f7 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-creation/repository/repository.md @@ -0,0 +1,459 @@ +--- +title: "管理镜像仓库" +description: "· page_id `41811`" +--- + +> 对照官方:[管理镜像仓库](https://cloud.tencent.com/document/product/1141/41811) · page_id `41811` + +## 概述 + +TCR 企业版镜像仓库用于管理容器镜像。单个镜像仓库可包含不同版本的容器镜像(以 Tag 区分),归属于命名空间并从命名空间继承公开/私有属性及安全扫描触发方式。镜像仓库是 TCR 权限管理的最小单位,实例管理员可按仓库粒度授予子账号管理或只读权限。 + +## 前置条件 + +- [环境准备](../../../index.md) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置 + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tcr:CreateRepository, tcr:DescribeRepositories, tcr:ModifyRepository, tcr:DeleteRepository +# tcr:DescribeImages, tcr:DescribeImageManifests, tcr:DeleteImage +# 验证:执行 DescribeRepositories 确认权限 +tccli tcr DescribeRepositories --RegistryId '' --region +# expected: exit 0,返回仓库列表(可为空) +``` + +### 资源检查 + +```bash +# 4. 确认实例存在且状态正常 +tccli tcr DescribeInstances --Registryids '[""]' --region +# expected: exit 0,Status 为 "Running" + +# 5. 确认命名空间已存在 +tccli tcr DescribeNamespaces --RegistryId '' --region +# expected: target 命名空间在 NamespaceList 中 +``` + +### 命名约束 + +镜像仓库名称长度 2–200 个字符,只能包含小写字母、数字及分隔符(`.`、`_`、`-`、`/`),不能以分隔符开头、结尾或连续。名称支持多级路径,例如 `team-01/front/nginx`。 + +### `BriefDescription` 字段 + +`BriefDescription` 是 `CreateRepository` 的必填字段,在仓库列表中展示,帮助识别仓库用途。建议填写有意义的简短描述(如"用户服务 API 镜像")。创建后可随时通过 `ModifyRepository` 修改。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 创建镜像仓库 | `CreateRepository` | 否 | +| 查看仓库列表 | `DescribeRepositories` | 是 | +| 筛选指定命名空间 | `DescribeRepositories --NamespaceName` | 是 | +| 模糊搜索仓库名 | `DescribeRepositories --RepositoryName` | 是 | +| 编辑简短描述/详细描述 | `ModifyRepository` | 是 | +| 删除镜像仓库 | `DeleteRepository` | 否 | +| 查看镜像版本列表 | `DescribeImages` | 是 | +| 搜索镜像版本 | `DescribeImages --ImageVersion` | 是 | +| 查看镜像 Manifest | `DescribeImageManifests` | 是 | +| 删除镜像版本 | `DeleteImage` | 否 | +| 推送/拉取镜像 | `docker push` / `docker pull` | — | + +## 关键字段说明 + +以下说明 `CreateRepository` 和 `ModifyRepository` 的主要参数。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | 实例 ID,格式 `tcr-xxxxxxxx` | 实例不存在 → `ResourceNotFound` | +| `NamespaceName` | String | 是 | 已存在的命名空间名称 | 命名空间不存在 → `ResourceNotFound` | +| `RepositoryName` | String | 是 | 长度 2–200 字符,小写字母/数字/`._-/`,不能以分隔符开头结尾或连续。同命名空间内唯一 | 格式不合法 → `InvalidParameter`;重名 → 创建失败 | +| `BriefDescription` | String | 是 | 简短描述,最长 100 字符。`ModifyRepository` 时必填 | 缺失 → `InvalidParameter` | +| `Description` | String | 否 | 详细描述,支持 Markdown,最长 1000 字符。`ModifyRepository` 时必填 | 缺失 → `InvalidParameter` | + +> **注意**:`ModifyRepository` 的 `BriefDescription` 和 `Description` 均为必填参数。修改任一字段时需同时传入另一个字段的当前值,否则将被清空。 + +## 操作步骤 + +### 创建镜像仓库 + +#### 选择依据 + +- **命名空间归属**:仓库必须归属于一个已存在的命名空间。命名空间决定了仓库的公开/私有属性和安全扫描策略。 +- **名称规划**:支持多级路径(如 `backend/api/user`),可按微服务层级组织。名称创建后不可修改。 +- **简短描述**:在仓库列表中展示,建议写清楚仓库用途(如"用户服务 API 镜像")。 + +#### 最小创建(只含必填字段) + +`create-repository.json`: + +```json +{ + "RegistryId": "", + "NamespaceName": "", + "RepositoryName": "", + "BriefDescription": "" +} +``` + +```bash +tccli tcr CreateRepository --cli-input-json file://create-repository.json --region +# expected: exit 0,返回 RequestId +``` + +**输出**: + +```json +{ + "RequestId": "b7995733-3abb-4c44-95d3-1e87942973b3" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | 实例 ID | 格式 `tcr-xxxxxxxx` | `tccli tcr DescribeInstances` | +| `` | 命名空间名称 | 已存在 | `tccli tcr DescribeNamespaces` | +| `` | 仓库名称 | 长度 2–200,小写字母/数字/`._-/`,不以分隔符开头结尾或连续 | 自定义 | +| `` | 地域 | 如 `ap-guangzhou` | `tccli configure list` | + +#### 增强配置(含详细描述) + +`create-repository-enhanced.json`: + +```json +{ + "RegistryId": "", + "NamespaceName": "", + "RepositoryName": "", + "BriefDescription": "", + "Description": "" +} +``` + +```bash +tccli tcr CreateRepository --cli-input-json file://create-repository-enhanced.json --region +# expected: exit 0,返回 RequestId +``` + +### 查看镜像仓库列表 + +列出实例内所有仓库: + +```bash +tccli tcr DescribeRepositories --RegistryId '' --region +# expected: exit 0,返回 RepositoryList 和 TotalCount +``` + +**输出**: + +```json +{ + "RepositoryList": [ + { + "Name": "example-ns/example-repo", + "Namespace": "example-ns", + "Public": true, + "BriefDescription": "tccli rewrite example repository", + "Description": "" + } + ], + "TotalCount": 1, + "RequestId": "7b568344-2d2c-454a-aaab-422ee390d42d" +} +``` + +**筛选指定命名空间**: + +```bash +tccli tcr DescribeRepositories \ + --RegistryId '' \ + --NamespaceName '' \ + --region +``` + +**模糊搜索仓库名**: + +```bash +tccli tcr DescribeRepositories \ + --RegistryId '' \ + --RepositoryName '' \ + --region +``` + +### 编辑仓库描述 + +#### 选择依据 + +- `BriefDescription` 和 `Description` 均为 `ModifyRepository` 的必填参数。修改任一字段时,必须同时传入另一个字段的当前值,否则未传入字段的值将被清空。 +- **建议做法**:修改前先用 `DescribeRepositories` 获取当前值,再构造修改命令。 + +`modify-repository.json`: + +```json +{ + "RegistryId": "", + "NamespaceName": "", + "RepositoryName": "", + "BriefDescription": "", + "Description": "" +} +``` + +```bash +tccli tcr ModifyRepository --cli-input-json file://modify-repository.json --region +# expected: exit 0,返回 RequestId +``` + +### 查看镜像版本 + +列出指定仓库内的所有镜像版本: + +```bash +tccli tcr DescribeImages \ + --RegistryId '' \ + --NamespaceName '' \ + --RepositoryName '' \ + --region +# expected: exit 0,返回 ImageInfoList 和 TotalCount +``` + +**输出**: + +```json +{ + "ImageInfoList": [], + "TotalCount": 0, + "RequestId": "da668a99-0af3-4a9f-9d7e-b1c376e637bd" +} +``` + +> 新仓库创建后尚未推送镜像时,`ImageInfoList` 为空是正常行为。使用 `docker push` 推送镜像后重新执行即可看到版本列表。 + +**搜索特定版本**: + +```bash +tccli tcr DescribeImages \ + --RegistryId '' \ + --NamespaceName '' \ + --RepositoryName '' \ + --ImageVersion '' \ + --region +``` + +### 查看镜像 Manifest + +查看指定 Tag 的镜像 Manifest 信息: + +```bash +tccli tcr DescribeImageManifests \ + --RegistryId '' \ + --NamespaceName '' \ + --RepositoryName '' \ + --ImageVersion '' \ + --region +# expected: 仓库有镜像时 exit 0;无镜像时 exit 255 +``` + +**预期输出(仓库为空,无镜像时)**: + +``` +Error: tag latest not found in example-ns/example-repo +``` + +> `DescribeImageManifests` 需要仓库中已存在目标 Tag 的镜像。仓库为空时该命令会失败。需先用 `docker push` 推送镜像后重试。 + +### 删除镜像版本 + +`delete-image.json`: + +```json +{ + "RegistryId": "", + "NamespaceName": "", + "RepositoryName": "", + "ImageVersion": "" +} +``` + +```bash +tccli tcr DeleteImage --cli-input-json file://delete-image.json --region +# expected: exit 0,返回 RequestId +``` + +**输出**: + +```json +{ + "RequestId": "a0c88b58-ade2-4756-99fb-e1a21dc0fe17" +} +``` + +> **注意**:`DeleteImage` 只删除指定版本,不影响同一仓库中其他版本。但若被删版本与其他版本共享同一 Digest(相同镜像内容的不同 Tag),则共享该 Digest 的所有其他 Tag 也会变为不可用。 + +### 删除镜像仓库 + +#### 选择依据 + +- 删除仓库会同时清除其下所有镜像版本,不可逆。执行前须二次确认。 +- `ForceDelete` 参数可跳过部分依赖校验(如触发器、复制规则),仅在有明确需要时使用。 + +```bash +tccli tcr DeleteRepository \ + --RegistryId '' \ + --NamespaceName '' \ + --RepositoryName '' \ + --region +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "b5499668-30a7-4da3-b516-d2c0ae32ea60" +} +``` + +> **高危警告**:`DeleteRepository` 会删除仓库及其下所有镜像版本,不可恢复。执行前务必确认仓库内无需要保留的镜像。仅需清理部分版本时优先使用 `DeleteImage`。 + +## 验证 + +### 控制面(tccli) + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 仓库已创建 | `DescribeRepositories --NamespaceName ''` | 目标仓库在 `RepositoryList` 中,`Name`/`Description` 与创建参数一致 | +| 描述已更新 | `DescribeRepositories --NamespaceName '' --RepositoryName ''` | `Description` 与修改参数一致 | +| 镜像版本存在 | `DescribeImages --NamespaceName '' --RepositoryName ''` | `ImageInfoList` 包含预期版本 | +| 版本已删除 | `DescribeImages --NamespaceName '' --RepositoryName ''` | 目标版本不再出现在 `ImageInfoList` 中 | +| 仓库已删除 | `DescribeRepositories --NamespaceName ''` | 目标仓库不再出现在 `RepositoryList` 中 | + +验证仓库详情: + +```bash +tccli tcr DescribeRepositories \ + --RegistryId '' \ + --NamespaceName '' \ + --RepositoryName '' \ + --region +# expected: 返回目标仓库完整信息,Description 与预期一致 +``` + +### 数据面(docker) + +推送和拉取验证需配置访问凭证后执行: + +```bash +# 登录实例 +docker login --username +# expected: Login Succeeded + +# 拉取镜像(验证仓库可访问) +docker pull //: +# expected: 镜像拉取成功,显示 Digest +``` + +## 清理 + +> **警告**:`DeleteRepository` 不可逆,会同时删除其下所有镜像版本。`DeleteImage` 删除共享 Digest 的版本会影响其他同名 Tag。生产环境执行前务必用 `DescribeRepositories` / `DescribeImages` 确认目标。 + +### 1. 清理前检查 + +```bash +# 确认待删除仓库及其镜像版本 +tccli tcr DescribeRepositories \ + --RegistryId '' \ + --NamespaceName '' \ + --RepositoryName '' \ + --region +# 记录 RepositoryName,确认是目标仓库 + +tccli tcr DescribeImages \ + --RegistryId '' \ + --NamespaceName '' \ + --RepositoryName '' \ + --region +# 记录所有 ImageVersion,确认无遗漏 +``` + +### 2. 删除镜像版本 + +`delete-image.json`: + +```json +{ + "RegistryId": "", + "NamespaceName": "", + "RepositoryName": "", + "ImageVersion": "" +} +``` + +```bash +tccli tcr DeleteImage --cli-input-json file://delete-image.json --region +# expected: exit 0,返回 RequestId +``` + +### 3. 删除镜像仓库 + +```bash +tccli tcr DeleteRepository \ + --RegistryId '' \ + --NamespaceName '' \ + --RepositoryName '' \ + --region +# expected: exit 0,返回 RequestId +``` + +### 4. 验证已删除 + +```bash +tccli tcr DescribeRepositories \ + --RegistryId '' \ + --NamespaceName '' \ + --region +# expected: 目标仓库不再出现在 RepositoryList 中 +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `CreateRepository` 返回 `ResourceNotFound.Namespace` | `tccli tcr DescribeNamespaces --RegistryId '' --region ` 确认命名空间是否存在 | 指定的 `NamespaceName` 不存在,或拼写错误 | 先 [创建命名空间](../namespace),确认 `NamespaceName` 拼写正确 | +| `CreateRepository` 返回 `ResourceAlreadyExists.Repository` | `DescribeRepositories --NamespaceName ''` 查看已有仓库 | 同命名空间下仓库名已存在 | 更换 `` 或先删除已有仓库 | +| `CreateRepository` 返回 `InvalidParameter` | 检查 `` 是否符合命名规则 | 仓库名含非法字符、长度超限或以分隔符开头/结尾/连续 | 修正名称:小写字母/数字/`._-/`,长度 2–200,不以分隔符开头结尾或连续 | +| `ModifyRepository` 返回 `InvalidParameter` | 检查是否同时传入了 `BriefDescription` 和 `Description` | 两个字段均为必填,缺少任一字段 | 确保 JSON 中同时包含 `BriefDescription` 和 `Description`,修改其中一个时传入另一个的当前值 | +| `DeleteRepository` 返回 `FailedOperation` | `DescribeRepositories` 确认仓库状态 | 仓库存在依赖(如镜像复制规则、触发器绑定) | 先解除依赖后重试,或使用 `"ForceDelete": true` 跳过校验 | +| `DeleteImage` 返回错误 | `DescribeImages` 确认版本存在 | 目标版本不存在或 Digest 已被引用 | 检查 `` 是否正确,确认版本存在 | +| `DescribeImages` 返回空列表 | 确认 `NamespaceName` 和 `RepositoryName` 均正确 | 仓库下未推送过镜像,或仓库/命名空间不存在 | 确认仓库已创建;若已创建,推送镜像后重试 | +| `DescribeImageManifests` 返回 `tag latest not found in /` | `DescribeImages` 查看仓库是否有镜像版本 | 仓库为空或指定 Tag 不存在——尚未 `docker push` 镜像,或 Tag 名错误 | 先 `docker push //:` 推送镜像,再执行 `DescribeImageManifests` | +| CAM 权限拒绝 | `tccli tcr DescribeRepositories --RegistryId '' --region ` 检查返回 | 子账号缺少 `tcr:CreateRepository` / `tcr:DeleteRepository` 等权限 | 联系主账号授予 `QcloudTCRFullAccess` 或添加仓库相关最小权限策略 | + +### 操作成功但有数据影响 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `DeleteImage` 成功后其他版本不可用 | `DescribeImages` 检查剩余版本的 `Digest` 值 | 被删版本与其他 Tag 共享同一 Digest,删除 Tag 后 Digest 被移除导致共享版本不可用 | 不可恢复。删除前确认版本间 Digest 关系 | +| `ModifyRepository` 后 Description 被清空 | `DescribeRepositories` 检查 `Description` 字段 | 修改 `BriefDescription` 时未传入 `Description` 当前值 | 重新执行 `ModifyRepository`,同时传入两个字段的期望值 | + +## 下一步 + +- [管理命名空间](../namespace)(page_id `41803`) +- [同实例多地域复制镜像](../../image-distribution/cross-region-replication)(page_id `52095`) +- [触发器(Webhook)](../../devops/webhook) +- [企业版快速入门](../../../quickstart/enterprise) + +## 控制台替代 + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 选择实例 → **镜像仓库** → 单击 **新建**,选择命名空间、填写仓库名称和描述后确认。在仓库详情页可编辑信息、管理镜像版本、查看 Manifest。 diff --git a/src/content/docs/cli/tcr/ops/image-distribution/accelerated-image-tcrctl.md b/src/content/docs/cli/tcr/ops/image-distribution/accelerated-image-tcrctl.md new file mode 100644 index 0000000..68feba6 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-distribution/accelerated-image-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "按需加载容器镜像(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[按需加载容器镜像](https://cloud.tencent.com/document/product/1141/53928) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/image-distribution/accelerated-image.md b/src/content/docs/cli/tcr/ops/image-distribution/accelerated-image.md new file mode 100644 index 0000000..273ca57 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-distribution/accelerated-image.md @@ -0,0 +1,328 @@ +--- +title: "按需加载容器镜像(tccli)" +description: "· page_id `53928`" +--- + +> 对照官方:[按需加载容器镜像](https://cloud.tencent.com/document/product/1141/53928) · page_id `53928` + +## 概述 + +通过 `tccli tcr CreateImageAccelerationService` 为 TCR 企业版实例开启镜像按需加载(镜像加速)功能。开启后,普通容器镜像在推送时自动转换为 OCI 兼容的加速格式(制品类型 `OCI-Image-v1`,镜像 Tag 附加 `-apparate` 后缀),部署时绕过全量下载与在线解压,实现容器极速启动(尤其对 >1 GB 大镜像效果显著)。 + +**控制面(tccli)**:管理实例的镜像加速服务生命周期——创建、查询、删除。 + +**数据面(kubectl/Helm/docker)**:节点打标签、安装加速套件 DaemonSet、推送镜像触发自动转换、部署加速镜像。 + +> **规格要求**:按需加载功能需**标准版(standard)或高级版(premium)**实例,并需提供 VPC + Subnet 参数。**基础版不支持**——`DescribeImageAccelerateService` 返回 `{ "Status": "", "IsEnable": false }`,此为正常预期。 + +## 前置条件 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据 +tccli configure list +# expected: secretId、secretKey、region 均已配置 + +# 3. 确认实例规格为 standard 或以上 +tccli tcr DescribeInstances --Registryids '[""]' --region --output json \ + --filter "Registries[0].{RegistryId:RegistryId,RegistryType:RegistryType,Status:Status}" +# expected: RegistryType: "standard" 或 "premium", Status: "Running" + +# 4. 确认 CAM 权限 — 需要 tcr:CreateImageAccelerationService, +# tcr:DescribeImageAccelerateService, tcr:DeleteImageAccelerateService, +# vpc:DescribeVpcs, vpc:DescribeSubnets +tccli tcr DescribeImageAccelerateService --RegistryId '' --region +# expected: exit 0 +``` + +### 资源检查 + +```bash +# 5. 确认集群所在 VPC 已接入至企业版实例 +tccli tcr DescribeInternalEndpoints --RegistryId '' --region --output json +# expected: AccessVpcSet 非空,至少包含集群所在 VPC + +# 6. 查询目标 VPC 下可用子网 +tccli vpc DescribeSubnets --region --output json \ + --filter "SubnetSet[?VpcId=='']" +# 记录目标 SubnetId 及其 Zone(如 ap-guangzhou-3) + +# 7. 确认集群节点 containerd >= 1.4.3(否则安装加速套件可能致节点 NotReady) +kubectl get nodes -o wide +# expected: Kubernetes >= 1.16,节点 STATUS = Ready +``` + +### 数据面前置 + +- 集群操作系统:Ubuntu、TencentOS 或 CentOS(CentOS 需 `yum install -y fuse`) +- 已安装 `kubectl`、`Helm v3` +- 集群节点 containerd >= 1.4.3 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 / 参数 | 幂等 | +|-----------|------------------|:--:| +| 选择实例 | `DescribeInstances`(获取 `RegistryId`) | 是 | +| 查看镜像加速状态 | `DescribeImageAccelerateService --RegistryId ` | 是 | +| 开启镜像加速 | `CreateImageAccelerationService`(需 VpcId + SubnetId 等 6 个必填参数) | 否(重复创建报错) | +| 添加镜像加速规则 | **控制台操作**(无对应 tccli 写 API) | — | +| 推送镜像触发自动转换 | `docker tag` + `docker push`(自动生成 `-apparate` 加速镜像) | — | +| 节点添加加速标签 | `kubectl label node cloud.tencent.com/apparate=true` | 是 | +| 安装 TCR 加速套件 | `helm repo add` + `helm install apparate` | 否 | +| 部署加速镜像 | YAML 指定加速镜像 + `nodeSelector: cloud.tencent.com/apparate=true` | — | + +## 关键字段说明 + +`CreateImageAccelerationService` 共 **6 个必填参数**(均无默认值)。 + +| 字段名 | 类型 | 必填 | 取值与约束 | 错误后果 | +|--------|------|:--:|------|------| +| `RegistryId` | String | 是 | 实例 ID,如 `tcr-nn8smeyj` | 传入不存在的实例 → `ResourceNotFound` | +| `VpcId` | String | 是 | VPC ID,如 `vpc-xxxxxxxx`,需与集群所在 VPC 一致 | VPC 不存在或不可达 → `InvalidParameter.VpcId` | +| `SubnetId` | String | 是 | VPC 下子网 ID,需与 `Zone` 匹配且有可用 IP | 子网不存在或 Zone 不匹配 → `InvalidParameter.SubnetId` | +| `StorageType` | String | 是 | 当前仅支持 `cfs` | 传入其他值 → `InvalidParameter`(提示 "invalid backend type") | +| `PGroupId` | String | 是 | CFS 权限组 ID,如 `pgroupbasic`(默认权限组) | 权限组不存在 → `InvalidParameter.PGroupId` | +| `Zone` | String | 是 | 可用区,如 `ap-guangzhou-3`,需与 Subnet 一致 | Zone 不可用或与 Subnet 不匹配 → `InvalidParameter.Zone` | + +> 镜像加速基于 CFS 共享存储实现 lazy loading,故 VpcId/SubnetId/PGroupId/Zone 均为 CFS 资源定位所需。CFS 为可用区级资源,Zone 必须与 Subnet 所在 Zone 一致。 + +## 操作步骤 + +### 步骤1:查询镜像加速服务状态 + +```bash +tccli tcr DescribeImageAccelerateService --RegistryId 'tcr-nn8smeyj' --region ap-guangzhou --output json +``` + +**输出(未开启 — 基础版或无加速的正常状态)**: + +```json +{ + "Status": "", + "IsEnable": false, + "RequestId": "669ad80b-8317-40db-94a6-8cc5eac46cef" +} +``` + +> 上为真机实跑输出。`Status` 为空字符串,`IsEnable` 为 `false`,这是加速服务未开启的正常状态。如果实例是基础版,这同样是预期结果(基础版不支持加速)。 + +### 步骤2:获取必填参数 + +```bash +# 2a. 查询实例已接入的 VPC 信息 +tccli tcr DescribeInternalEndpoints --RegistryId '' --region --output json +# 从返回的 AccessVpcSet 中获取 VpcId 和 SubnetId + +# 2b. 查询子网详情(确认 Zone) +tccli vpc DescribeSubnets --region --output json \ + --filter "SubnetSet[?SubnetId=='']" +# 记录 Zone 值(如 ap-guangzhou-3),后续 CreateImageAccelerationService 需一致 +``` + +### 步骤3:开启镜像加速服务 + +> **执行状态**:当前步骤命令参数已推导完整,但本 session 未在真机完成 `CreateImageAccelerationService` 执行(需 VPC + Subnet + PGroupId + Zone 参数)。以下命令为推导结果,标记为 `execution-pending`。 + +**选择依据**: + +| 决策项 | 选择 | 为什么 | +|--------|------|--------| +| StorageType | `cfs` | TCR 镜像加速基于 CFS 共享存储实现 lazy loading;当前仅支持 `cfs` | +| VpcId | 集群所在 VPC | CFS 需部署在与集群相同的 VPC 内 | +| SubnetId | VPC 内可用子网 | 子网需有足够可用 IP | +| Zone | 与 Subnet 匹配的可用区 | CFS 为可用区级资源,必须一致 | +| PGroupId | `pgroupbasic` 或自定义 | 首次使用可用默认权限组 | + +```bash +# 使用 --cli-input-json 桥接模式(6 个必填参数) +cat > create-accel-service.json <<'EOF' +{ + "RegistryId": "", + "VpcId": "", + "SubnetId": "", + "StorageType": "cfs", + "PGroupId": "pgroupbasic", + "Zone": "" +} +EOF + +tccli tcr CreateImageAccelerationService \ + --cli-input-json file://create-accel-service.json \ + --region --output json +``` + +> 若使用自定义 CFS 权限组(非默认 `pgroupbasic`),可通过 `tccli cfs DescribeCfsPGroups --region ` 查询已有权限组后替换 `PGroupId`。 + +### 步骤4:确认加速服务已开启 + +```bash +tccli tcr DescribeImageAccelerateService --RegistryId '' --region --output json +# expected: IsEnable: true, Status: "Running", CFSVIP 非空 +``` + +**输出(已开启的预期)**: + +```json +{ + "Status": "Running", + "CFSVIP": "10.0.x.x", + "IsEnable": true, + "RequestId": "eac6b301-a322-493a-8e36-83b295459397" +} +``` + +### 步骤5:推送镜像并自动转换为加速格式 + +> 加速规则需在控制台添加(无对应 tccli 写 API)。确认规则状态为开启中后执行。 + +```bash +docker tag nginx:latest //nginx:latest +docker push //nginx:latest +# expected: 推送成功后自动生成 nginx:latest-apparate 加速镜像 +``` + +### 步骤6:集群侧配置加速环境(数据面) + +为节点添加加速标签: + +```bash +kubectl label node cloud.tencent.com/apparate=true +kubectl get nodes -l cloud.tencent.com/apparate=true +``` + +安装 TCR 加速套件(Helm v3): + +```bash +helm repo add tcr-helm-public https://helmhub.tencentcloudcr.com/chartrepo/public +helm pull tcr-helm-public/apparate --version 1.0.0 +tar -xzvf apparate-1.0.0.tgz +# 编辑 apparate/values.yaml 配置实例凭证后安装 +helm install apparate ./apparate +``` + +验证 DaemonSet: + +```bash +kubectl get daemonset -A | grep apparate +# expected: DESIRED = READY, 所有有加速标签的节点均已运行 +``` + +### 步骤7:部署加速镜像 + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-accelerated +spec: + replicas: 2 + selector: + matchLabels: + app: nginx-accelerated + template: + metadata: + labels: + app: nginx-accelerated + spec: + nodeSelector: + cloud.tencent.com/apparate: "true" + containers: + - name: nginx + image: //nginx:latest-apparate + ports: + - containerPort: 80 +``` + +```bash +kubectl apply -f nginx-accelerated.yaml +# 验证 Pod 状态 +kubectl get pods -l app=nginx-accelerated +# expected: STATUS: Running +``` + +## 验证 + +### 控制面(tccli) + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 加速服务已开启 | `DescribeImageAccelerateService --RegistryId '' --region ` | `IsEnable: true`,`Status: "Running"`,`CFSVIP` 非空 | +| 加速服务未开启(正常) | 同上 | `IsEnable: false`,`Status: ""`(基础版或未开启时的预期行为) | +| 实例规格满足要求 | `DescribeInstances --Registryids '[""]' --region --filter "Registries[0].RegistryType"` | `"standard"` 或 `"premium"` | +| VPC 接入状态正常 | `DescribeInternalEndpoints --RegistryId '' --region ` | `AccessVpcSet` 含目标 VPC,`Status: "Running"` | + +### 数据面(kubectl/docker) + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 节点标签已生效 | `kubectl get nodes -l cloud.tencent.com/apparate=true` | 至少一个节点 | +| 加速 DaemonSet 运行 | `kubectl get ds -A \| grep apparate` | DESIRED = READY | +| 加速镜像 Pod Running | `kubectl get pods -l app=nginx-accelerated` | STATUS: Running | +| 镜像自动转换 | `docker pull //nginx:latest-apparate` | 拉取成功,`-apparate` 后缀镜像存在 | + +## 清理 + +> **副作用警告**:删除镜像加速服务后,CFS 存储挂载点将被释放,已转换的 `-apparate` 加速镜像恢复为普通拉取模式(全量下载+解压),不再享受 lazy loading。已部署的加速 Pod 不受影响,但新 Pod 启动速度退回普通模式。 + +### 控制面(tccli) + +```bash +# 确认当前加速服务状态 +tccli tcr DescribeImageAccelerateService --RegistryId '' --region --output json + +# 删除加速服务 +tccli tcr DeleteImageAccelerateService --RegistryId '' --region --output json +# expected: exit 0,返回 RequestId + +# 确认已关闭 +tccli tcr DescribeImageAccelerateService --RegistryId '' --region --output json +# expected: IsEnable: false +``` + +### 数据面(kubectl/Helm) + +```bash +helm uninstall apparate +kubectl delete deployment nginx-accelerated +kubectl label node cloud.tencent.com/apparate- +# 确认 DaemonSet 已移除 +kubectl get ds -A | grep apparate +# expected: 无输出 +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `CreateImageAccelerationService` 返回 `FailedOperation` | `tccli tcr DescribeInstances --Registryids '[""]' --region --filter "Registries[0].RegistryType"` | 实例 tier 不支持(basic)或依赖资源未就绪 | basic → standard 升级:`tccli tcr ModifyInstance --RegistryId --RegistryType standard --region `。确认 Status=Running 后重试 | +| `CreateImageAccelerationService` 返回 `InvalidParameter` 提示 "invalid backend type" | 检查 JSON 中 `StorageType` 字段值 | `StorageType` 不为 `cfs` | 将 `StorageType` 改为 `"cfs"`(当前仅支持此值) | +| `CreateImageAccelerationService` 返回 `InvalidParameter.VpcId` | `tccli vpc DescribeVpcs --vpc-ids '[""]' --region ` | VpcId 不存在或不可达 | 确认 VPC ID 正确且 VPC 状态为可用 | +| `CreateImageAccelerationService` 返回 `InvalidParameter.SubnetId` | `tccli vpc DescribeSubnets --subnet-ids '[""]' --region ` | SubnetId 不存在或与 Zone 不匹配 | 确认 SubnetId 与 Zone 一致,子网状态正常 | +| `CreateImageAccelerationService` 返回 `InvalidParameter.Zone` | 检查 Zone 是否与 Subnet 所在 Zone 一致 | Zone 不可用或与 Subnet 不匹配 | 通过 `vpc DescribeSubnets` 获取子网所在的 Zone,确保 `CreateImageAccelerationService` 中 Zone 与之匹配 | +| `DescribeImageAccelerateService` 返回 `IsEnable: false`, `Status: ""` | `DescribeInstances --Registryids '[""]' --region --filter "Registries[0].RegistryType"` | 实例为 basic(不支持加速)或加速服务尚未开启 | basic → standard 升级;standard+/premium → 执行 `CreateImageAccelerationService` | + +### 创建成功但功能异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 推送镜像后未自动生成 `-apparate` 后缀镜像 | `docker manifest inspect //:latest-apparate` | 镜像不匹配已有加速规则 | 通过控制台调整加速规则,覆盖目标命名空间/仓库/Tag | +| 加速 DaemonSet 未调度 | `kubectl describe node ` 查看 Labels | 节点未添加加速标签 | `kubectl label node cloud.tencent.com/apparate=true` | +| Pod 无法拉取 `-apparate` 镜像 | `kubectl describe pod ` 查看 Events | 缺少 imagePullSecrets 或节点无加速标签 | 确认 Helm values.yaml 中已配置 TCR 访问凭证,确认节点加速标签已添加 | +| 安装加速套件后节点 NotReady | `kubectl get nodes`;`kubectl describe node ` | containerd < 1.4.3 不兼容加速套件 | 升级节点 containerd 至 >= 1.4.3,或卸载加速套件 `helm uninstall apparate` | + +## 下一步 + +- [跨实例(账号)同步镜像](../cross-instance-sync)(page_id `41945`)——跨账号分发加速镜像 +- [容器镜像安全扫描](../../image-security/vulnerability-scan)(page_id `48185`)——对加速镜像执行安全扫描 +- [内网访问控制](../../access/network/private-access)——确保集群 VPC 已接入实例 + +## 控制台替代 + +[容器镜像服务控制台 → 镜像加速](https://console.cloud.tencent.com/tcr/accelerate):选择实例 → 开启镜像加速 → 添加规则 → 推送镜像 → TKE 集群侧配置标签并安装加速套件 → 部署加速镜像。 diff --git a/src/content/docs/cli/tcr/ops/image-distribution/accelerated-image/accelerated-image-tcrctl.md b/src/content/docs/cli/tcr/ops/image-distribution/accelerated-image/accelerated-image-tcrctl.md new file mode 100644 index 0000000..68feba6 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-distribution/accelerated-image/accelerated-image-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "按需加载容器镜像(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[按需加载容器镜像](https://cloud.tencent.com/document/product/1141/53928) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/image-distribution/accelerated-image/accelerated-image.md b/src/content/docs/cli/tcr/ops/image-distribution/accelerated-image/accelerated-image.md new file mode 100644 index 0000000..273ca57 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-distribution/accelerated-image/accelerated-image.md @@ -0,0 +1,328 @@ +--- +title: "按需加载容器镜像(tccli)" +description: "· page_id `53928`" +--- + +> 对照官方:[按需加载容器镜像](https://cloud.tencent.com/document/product/1141/53928) · page_id `53928` + +## 概述 + +通过 `tccli tcr CreateImageAccelerationService` 为 TCR 企业版实例开启镜像按需加载(镜像加速)功能。开启后,普通容器镜像在推送时自动转换为 OCI 兼容的加速格式(制品类型 `OCI-Image-v1`,镜像 Tag 附加 `-apparate` 后缀),部署时绕过全量下载与在线解压,实现容器极速启动(尤其对 >1 GB 大镜像效果显著)。 + +**控制面(tccli)**:管理实例的镜像加速服务生命周期——创建、查询、删除。 + +**数据面(kubectl/Helm/docker)**:节点打标签、安装加速套件 DaemonSet、推送镜像触发自动转换、部署加速镜像。 + +> **规格要求**:按需加载功能需**标准版(standard)或高级版(premium)**实例,并需提供 VPC + Subnet 参数。**基础版不支持**——`DescribeImageAccelerateService` 返回 `{ "Status": "", "IsEnable": false }`,此为正常预期。 + +## 前置条件 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据 +tccli configure list +# expected: secretId、secretKey、region 均已配置 + +# 3. 确认实例规格为 standard 或以上 +tccli tcr DescribeInstances --Registryids '[""]' --region --output json \ + --filter "Registries[0].{RegistryId:RegistryId,RegistryType:RegistryType,Status:Status}" +# expected: RegistryType: "standard" 或 "premium", Status: "Running" + +# 4. 确认 CAM 权限 — 需要 tcr:CreateImageAccelerationService, +# tcr:DescribeImageAccelerateService, tcr:DeleteImageAccelerateService, +# vpc:DescribeVpcs, vpc:DescribeSubnets +tccli tcr DescribeImageAccelerateService --RegistryId '' --region +# expected: exit 0 +``` + +### 资源检查 + +```bash +# 5. 确认集群所在 VPC 已接入至企业版实例 +tccli tcr DescribeInternalEndpoints --RegistryId '' --region --output json +# expected: AccessVpcSet 非空,至少包含集群所在 VPC + +# 6. 查询目标 VPC 下可用子网 +tccli vpc DescribeSubnets --region --output json \ + --filter "SubnetSet[?VpcId=='']" +# 记录目标 SubnetId 及其 Zone(如 ap-guangzhou-3) + +# 7. 确认集群节点 containerd >= 1.4.3(否则安装加速套件可能致节点 NotReady) +kubectl get nodes -o wide +# expected: Kubernetes >= 1.16,节点 STATUS = Ready +``` + +### 数据面前置 + +- 集群操作系统:Ubuntu、TencentOS 或 CentOS(CentOS 需 `yum install -y fuse`) +- 已安装 `kubectl`、`Helm v3` +- 集群节点 containerd >= 1.4.3 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 / 参数 | 幂等 | +|-----------|------------------|:--:| +| 选择实例 | `DescribeInstances`(获取 `RegistryId`) | 是 | +| 查看镜像加速状态 | `DescribeImageAccelerateService --RegistryId ` | 是 | +| 开启镜像加速 | `CreateImageAccelerationService`(需 VpcId + SubnetId 等 6 个必填参数) | 否(重复创建报错) | +| 添加镜像加速规则 | **控制台操作**(无对应 tccli 写 API) | — | +| 推送镜像触发自动转换 | `docker tag` + `docker push`(自动生成 `-apparate` 加速镜像) | — | +| 节点添加加速标签 | `kubectl label node cloud.tencent.com/apparate=true` | 是 | +| 安装 TCR 加速套件 | `helm repo add` + `helm install apparate` | 否 | +| 部署加速镜像 | YAML 指定加速镜像 + `nodeSelector: cloud.tencent.com/apparate=true` | — | + +## 关键字段说明 + +`CreateImageAccelerationService` 共 **6 个必填参数**(均无默认值)。 + +| 字段名 | 类型 | 必填 | 取值与约束 | 错误后果 | +|--------|------|:--:|------|------| +| `RegistryId` | String | 是 | 实例 ID,如 `tcr-nn8smeyj` | 传入不存在的实例 → `ResourceNotFound` | +| `VpcId` | String | 是 | VPC ID,如 `vpc-xxxxxxxx`,需与集群所在 VPC 一致 | VPC 不存在或不可达 → `InvalidParameter.VpcId` | +| `SubnetId` | String | 是 | VPC 下子网 ID,需与 `Zone` 匹配且有可用 IP | 子网不存在或 Zone 不匹配 → `InvalidParameter.SubnetId` | +| `StorageType` | String | 是 | 当前仅支持 `cfs` | 传入其他值 → `InvalidParameter`(提示 "invalid backend type") | +| `PGroupId` | String | 是 | CFS 权限组 ID,如 `pgroupbasic`(默认权限组) | 权限组不存在 → `InvalidParameter.PGroupId` | +| `Zone` | String | 是 | 可用区,如 `ap-guangzhou-3`,需与 Subnet 一致 | Zone 不可用或与 Subnet 不匹配 → `InvalidParameter.Zone` | + +> 镜像加速基于 CFS 共享存储实现 lazy loading,故 VpcId/SubnetId/PGroupId/Zone 均为 CFS 资源定位所需。CFS 为可用区级资源,Zone 必须与 Subnet 所在 Zone 一致。 + +## 操作步骤 + +### 步骤1:查询镜像加速服务状态 + +```bash +tccli tcr DescribeImageAccelerateService --RegistryId 'tcr-nn8smeyj' --region ap-guangzhou --output json +``` + +**输出(未开启 — 基础版或无加速的正常状态)**: + +```json +{ + "Status": "", + "IsEnable": false, + "RequestId": "669ad80b-8317-40db-94a6-8cc5eac46cef" +} +``` + +> 上为真机实跑输出。`Status` 为空字符串,`IsEnable` 为 `false`,这是加速服务未开启的正常状态。如果实例是基础版,这同样是预期结果(基础版不支持加速)。 + +### 步骤2:获取必填参数 + +```bash +# 2a. 查询实例已接入的 VPC 信息 +tccli tcr DescribeInternalEndpoints --RegistryId '' --region --output json +# 从返回的 AccessVpcSet 中获取 VpcId 和 SubnetId + +# 2b. 查询子网详情(确认 Zone) +tccli vpc DescribeSubnets --region --output json \ + --filter "SubnetSet[?SubnetId=='']" +# 记录 Zone 值(如 ap-guangzhou-3),后续 CreateImageAccelerationService 需一致 +``` + +### 步骤3:开启镜像加速服务 + +> **执行状态**:当前步骤命令参数已推导完整,但本 session 未在真机完成 `CreateImageAccelerationService` 执行(需 VPC + Subnet + PGroupId + Zone 参数)。以下命令为推导结果,标记为 `execution-pending`。 + +**选择依据**: + +| 决策项 | 选择 | 为什么 | +|--------|------|--------| +| StorageType | `cfs` | TCR 镜像加速基于 CFS 共享存储实现 lazy loading;当前仅支持 `cfs` | +| VpcId | 集群所在 VPC | CFS 需部署在与集群相同的 VPC 内 | +| SubnetId | VPC 内可用子网 | 子网需有足够可用 IP | +| Zone | 与 Subnet 匹配的可用区 | CFS 为可用区级资源,必须一致 | +| PGroupId | `pgroupbasic` 或自定义 | 首次使用可用默认权限组 | + +```bash +# 使用 --cli-input-json 桥接模式(6 个必填参数) +cat > create-accel-service.json <<'EOF' +{ + "RegistryId": "", + "VpcId": "", + "SubnetId": "", + "StorageType": "cfs", + "PGroupId": "pgroupbasic", + "Zone": "" +} +EOF + +tccli tcr CreateImageAccelerationService \ + --cli-input-json file://create-accel-service.json \ + --region --output json +``` + +> 若使用自定义 CFS 权限组(非默认 `pgroupbasic`),可通过 `tccli cfs DescribeCfsPGroups --region ` 查询已有权限组后替换 `PGroupId`。 + +### 步骤4:确认加速服务已开启 + +```bash +tccli tcr DescribeImageAccelerateService --RegistryId '' --region --output json +# expected: IsEnable: true, Status: "Running", CFSVIP 非空 +``` + +**输出(已开启的预期)**: + +```json +{ + "Status": "Running", + "CFSVIP": "10.0.x.x", + "IsEnable": true, + "RequestId": "eac6b301-a322-493a-8e36-83b295459397" +} +``` + +### 步骤5:推送镜像并自动转换为加速格式 + +> 加速规则需在控制台添加(无对应 tccli 写 API)。确认规则状态为开启中后执行。 + +```bash +docker tag nginx:latest //nginx:latest +docker push //nginx:latest +# expected: 推送成功后自动生成 nginx:latest-apparate 加速镜像 +``` + +### 步骤6:集群侧配置加速环境(数据面) + +为节点添加加速标签: + +```bash +kubectl label node cloud.tencent.com/apparate=true +kubectl get nodes -l cloud.tencent.com/apparate=true +``` + +安装 TCR 加速套件(Helm v3): + +```bash +helm repo add tcr-helm-public https://helmhub.tencentcloudcr.com/chartrepo/public +helm pull tcr-helm-public/apparate --version 1.0.0 +tar -xzvf apparate-1.0.0.tgz +# 编辑 apparate/values.yaml 配置实例凭证后安装 +helm install apparate ./apparate +``` + +验证 DaemonSet: + +```bash +kubectl get daemonset -A | grep apparate +# expected: DESIRED = READY, 所有有加速标签的节点均已运行 +``` + +### 步骤7:部署加速镜像 + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-accelerated +spec: + replicas: 2 + selector: + matchLabels: + app: nginx-accelerated + template: + metadata: + labels: + app: nginx-accelerated + spec: + nodeSelector: + cloud.tencent.com/apparate: "true" + containers: + - name: nginx + image: //nginx:latest-apparate + ports: + - containerPort: 80 +``` + +```bash +kubectl apply -f nginx-accelerated.yaml +# 验证 Pod 状态 +kubectl get pods -l app=nginx-accelerated +# expected: STATUS: Running +``` + +## 验证 + +### 控制面(tccli) + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 加速服务已开启 | `DescribeImageAccelerateService --RegistryId '' --region ` | `IsEnable: true`,`Status: "Running"`,`CFSVIP` 非空 | +| 加速服务未开启(正常) | 同上 | `IsEnable: false`,`Status: ""`(基础版或未开启时的预期行为) | +| 实例规格满足要求 | `DescribeInstances --Registryids '[""]' --region --filter "Registries[0].RegistryType"` | `"standard"` 或 `"premium"` | +| VPC 接入状态正常 | `DescribeInternalEndpoints --RegistryId '' --region ` | `AccessVpcSet` 含目标 VPC,`Status: "Running"` | + +### 数据面(kubectl/docker) + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 节点标签已生效 | `kubectl get nodes -l cloud.tencent.com/apparate=true` | 至少一个节点 | +| 加速 DaemonSet 运行 | `kubectl get ds -A \| grep apparate` | DESIRED = READY | +| 加速镜像 Pod Running | `kubectl get pods -l app=nginx-accelerated` | STATUS: Running | +| 镜像自动转换 | `docker pull //nginx:latest-apparate` | 拉取成功,`-apparate` 后缀镜像存在 | + +## 清理 + +> **副作用警告**:删除镜像加速服务后,CFS 存储挂载点将被释放,已转换的 `-apparate` 加速镜像恢复为普通拉取模式(全量下载+解压),不再享受 lazy loading。已部署的加速 Pod 不受影响,但新 Pod 启动速度退回普通模式。 + +### 控制面(tccli) + +```bash +# 确认当前加速服务状态 +tccli tcr DescribeImageAccelerateService --RegistryId '' --region --output json + +# 删除加速服务 +tccli tcr DeleteImageAccelerateService --RegistryId '' --region --output json +# expected: exit 0,返回 RequestId + +# 确认已关闭 +tccli tcr DescribeImageAccelerateService --RegistryId '' --region --output json +# expected: IsEnable: false +``` + +### 数据面(kubectl/Helm) + +```bash +helm uninstall apparate +kubectl delete deployment nginx-accelerated +kubectl label node cloud.tencent.com/apparate- +# 确认 DaemonSet 已移除 +kubectl get ds -A | grep apparate +# expected: 无输出 +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `CreateImageAccelerationService` 返回 `FailedOperation` | `tccli tcr DescribeInstances --Registryids '[""]' --region --filter "Registries[0].RegistryType"` | 实例 tier 不支持(basic)或依赖资源未就绪 | basic → standard 升级:`tccli tcr ModifyInstance --RegistryId --RegistryType standard --region `。确认 Status=Running 后重试 | +| `CreateImageAccelerationService` 返回 `InvalidParameter` 提示 "invalid backend type" | 检查 JSON 中 `StorageType` 字段值 | `StorageType` 不为 `cfs` | 将 `StorageType` 改为 `"cfs"`(当前仅支持此值) | +| `CreateImageAccelerationService` 返回 `InvalidParameter.VpcId` | `tccli vpc DescribeVpcs --vpc-ids '[""]' --region ` | VpcId 不存在或不可达 | 确认 VPC ID 正确且 VPC 状态为可用 | +| `CreateImageAccelerationService` 返回 `InvalidParameter.SubnetId` | `tccli vpc DescribeSubnets --subnet-ids '[""]' --region ` | SubnetId 不存在或与 Zone 不匹配 | 确认 SubnetId 与 Zone 一致,子网状态正常 | +| `CreateImageAccelerationService` 返回 `InvalidParameter.Zone` | 检查 Zone 是否与 Subnet 所在 Zone 一致 | Zone 不可用或与 Subnet 不匹配 | 通过 `vpc DescribeSubnets` 获取子网所在的 Zone,确保 `CreateImageAccelerationService` 中 Zone 与之匹配 | +| `DescribeImageAccelerateService` 返回 `IsEnable: false`, `Status: ""` | `DescribeInstances --Registryids '[""]' --region --filter "Registries[0].RegistryType"` | 实例为 basic(不支持加速)或加速服务尚未开启 | basic → standard 升级;standard+/premium → 执行 `CreateImageAccelerationService` | + +### 创建成功但功能异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 推送镜像后未自动生成 `-apparate` 后缀镜像 | `docker manifest inspect //:latest-apparate` | 镜像不匹配已有加速规则 | 通过控制台调整加速规则,覆盖目标命名空间/仓库/Tag | +| 加速 DaemonSet 未调度 | `kubectl describe node ` 查看 Labels | 节点未添加加速标签 | `kubectl label node cloud.tencent.com/apparate=true` | +| Pod 无法拉取 `-apparate` 镜像 | `kubectl describe pod ` 查看 Events | 缺少 imagePullSecrets 或节点无加速标签 | 确认 Helm values.yaml 中已配置 TCR 访问凭证,确认节点加速标签已添加 | +| 安装加速套件后节点 NotReady | `kubectl get nodes`;`kubectl describe node ` | containerd < 1.4.3 不兼容加速套件 | 升级节点 containerd 至 >= 1.4.3,或卸载加速套件 `helm uninstall apparate` | + +## 下一步 + +- [跨实例(账号)同步镜像](../cross-instance-sync)(page_id `41945`)——跨账号分发加速镜像 +- [容器镜像安全扫描](../../image-security/vulnerability-scan)(page_id `48185`)——对加速镜像执行安全扫描 +- [内网访问控制](../../access/network/private-access)——确保集群 VPC 已接入实例 + +## 控制台替代 + +[容器镜像服务控制台 → 镜像加速](https://console.cloud.tencent.com/tcr/accelerate):选择实例 → 开启镜像加速 → 添加规则 → 推送镜像 → TKE 集群侧配置标签并安装加速套件 → 部署加速镜像。 diff --git a/src/content/docs/cli/tcr/ops/image-distribution/accelerated-image/examples/create-accel-service.json b/src/content/docs/cli/tcr/ops/image-distribution/accelerated-image/examples/create-accel-service.json new file mode 100644 index 0000000..7df9040 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-distribution/accelerated-image/examples/create-accel-service.json @@ -0,0 +1,8 @@ +{ + "RegistryId": "tcr-xxxxxxxx", + "VpcId": "vpc-xxxxxxxx", + "SubnetId": "subnet-xxxxxxxx", + "StorageType": "cfs", + "PGroupId": "pgroup-xxxxxxxx", + "Zone": "ap-guangzhou-3" +} diff --git a/src/content/docs/cli/tcr/ops/image-distribution/cross-instance-sync-tcrctl.md b/src/content/docs/cli/tcr/ops/image-distribution/cross-instance-sync-tcrctl.md new file mode 100644 index 0000000..d4ccefa --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-distribution/cross-instance-sync-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "跨实例(账号)同步镜像(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[跨实例(账号)同步镜像](https://cloud.tencent.com/document/product/1141/41945) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/image-distribution/cross-instance-sync.md b/src/content/docs/cli/tcr/ops/image-distribution/cross-instance-sync.md new file mode 100644 index 0000000..c2456b2 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-distribution/cross-instance-sync.md @@ -0,0 +1,342 @@ +--- +title: "跨实例(账号)同步镜像(tccli)" +description: "· page_id `41945`" +--- + +> 对照官方:[跨实例(账号)同步镜像](https://cloud.tencent.com/document/product/1141/41945) · page_id `41945` + +## 概述 + +通过 `tccli tcr ManageReplication` 在不同地域的两个 TCR 企业版实例间建立同步规则,实现容器镜像、Helm Chart 的自动同步分发。支持**同主账号内实例同步**和**跨主账号实例同步**——跨账号场景下,源侧用户以目标侧提供的实例 ID、账号 UIN 和访问凭证建立同步规则。 + +**与同实例多地域复制的差异**: + +| 维度 | 跨实例同步(本页) | 同实例多地域复制 | +|------|-------------------|-------------------| +| 实例关系 | 两个独立实例(可不同账号) | 同一实例的主实例 + 从实例 | +| 域名 | 各自独立域名 | 主/从共享同一域名 | +| 推送方向 | 两侧均可推送 | 仅主实例可推送,从实例只读拉取 | +| 跨主账号 | **支持**(需目标侧凭证) | 不支持 | +| 国内外互通 | **支持**(含跨境) | 不支持 | + +> **规格要求**:同步功能支持**标准版(standard)和高级版(premium)**。基础版(basic)调用 `ManageReplication` 返回 `UnsupportedOperation`——需 `ModifyInstance --RegistryType standard` 升级后方可使用。升级路径:`basic → standard → premium`,不支持降级。 + +## 前置条件 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据 +tccli configure list +# expected: secretId、secretKey、region 均已配置 + +# 3. 确认源实例规格为 standard 或以上 +tccli tcr DescribeInstances --Registryids '[""]' --region --output json \ + --filter "Registries[0].{RegistryId:RegistryId,RegistryType:RegistryType,Status:Status}" +# expected: RegistryType: "standard" 或 "premium", Status: "Running" + +# 4. 确认目标实例已创建且 Running +tccli tcr DescribeInstances --Registryids '[""]' --region --output json \ + --filter "Registries[0].{RegistryId:RegistryId,RegistryType:RegistryType,Status:Status}" +# expected: Status: "Running" + +# 5. 确认 CAM 权限 — 需要 tcr:ManageReplication, tcr:DescribeReplicationPolicies, +# tcr:DescribeReplicationInstances, tcr:DescribeReplicationInstanceSyncStatus +tccli tcr DescribeReplicationInstances --RegistryId '' --region +# expected: exit 0 +``` + +### 跨主账号同步额外准备 + +若需跨主账号同步,目标侧用户需提供: +- 目标实例 ID(`DestinationRegistryId`) +- 目标主账号 UIN(`PeerRegistryUin`) +- 长期访问凭证(`PeerRegistryToken`,用户级账号密码) + +> 用户名仅支持[用户级账号](https://cloud.tencent.com/document/product/1141/41829),不支持[服务级账号](https://cloud.tencent.com/document/product/1141/89137)。**凭证需长期有效**,避免过期导致同步中断。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 / 参数 | 幂等 | +|-----------|------------------|:--:| +| 查看实例同步列表 | `DescribeReplicationInstances` | 是 | +| 新建实例同步规则 | `ManageReplication` | 否(同一规则名重复创建报错) | +| 查看同步策略列表 | `DescribeReplicationPolicies` | 是 | +| 查看同步状态 | `DescribeReplicationInstanceSyncStatus` | 是 | +| 修改规则状态(启用/关闭) | `ManageReplication`(更新 `Rule` 后重新提交) | — | +| 删除实例同步 | `DeleteReplicationInstance` | 是 | + +## 关键字段说明 + +### ManageReplication 参数 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `SourceRegistryId` | String | 是 | 源实例 ID | 不存在 → `ResourceNotFound` | +| `DestinationRegistryId` | String | 是 | 目标实例 ID | 不存在 → `ResourceNotFound` | +| `DestinationRegionId` | Integer | **是** | 目标实例所在地域的数字 ID(通过 `DescribeRegions` 获取) | 未传或错误 → `InvalidParameter` | +| `Rule` | String | 是 | JSON 序列化的规则对象字符串,含 `Name`(规则名)、`DestNamespace`(目标命名空间)、`Override`(是否覆盖)、`Filters`(过滤器列表)、`Deletion`(同步删除) | JSON 格式错误 → `InvalidParameter` | +| `Description` | String | 否 | 规则描述 | — | + +### Rule 对象字段 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `Name` | String | 是 | 规则名称,支持小写字母、数字及 `-._`,以字母/数字开头 | 格式不符 → `InvalidParameter` | +| `DestNamespace` | String | 否 | 目标命名空间,不填默认与源同名,目标侧不存在则自动创建 | — | +| `Override` | Boolean | 否 | 是否覆盖目标侧已有同名镜像,建议 `false` | `true` 时目标侧同名镜像被覆盖 | +| `Filters` | Array | 否 | 过滤器列表,不填则同步命名空间内全部资源 | 过滤条件不匹配导致镜像漏同步 | +| `Deletion` | Boolean | 否 | 是否启用同步删除(源镜像删除后同步删除目标侧镜像),默认 `false` | `true` 时可能误删目标侧数据 | + +### Filters 条目字段 + +| Filter Type | 含义 | 示例 | 注意 | +|-------------|------|------|------| +| `name` | 仓库名称正则过滤,空则匹配命名空间内全部仓库 | `".*"`、`"prod/**"` | **Filter.Type 必须全小写** `"name"`,不能写成 `"Name"`,否则视为非法过滤器导致规则不生效 | +| `tag` | 版本 Tag 正则过滤,空则匹配所有版本 | `"v1.*"`、`""`(全部) | — | +| `resource` | 资源类型过滤 | `"image"`(容器镜像)、`"chart"`(Helm Chart),不传则全部同步 | — | + +## 操作步骤 + +### 步骤1:查看已有同步实例 + +```bash +tccli tcr DescribeReplicationInstances --RegistryId '' --region --output json +``` + +**输出(无同步实例时)**: + +```json +{ + "TotalCount": 0, + "ReplicationRegistries": null, + "RequestId": "d94482cb-5f85-43f3-80a5-f0263faff798" +} +``` + +### 步骤2:查看已有同步策略 + +```bash +tccli tcr DescribeReplicationPolicies --RegistryId '' --region --output json +``` + +**输出(未配置同步规则时)**: + +```json +{ + "TotalCount": 0, + "RequestId": "6683742b-ff91-4cd1-8fcf-bf8f2a2b6091" +} +``` + +### 步骤3:创建同步规则 + +`ManageReplication` 同时负责创建与更新规则。以下是真实执行的命令(真机 premium 实例 `tcr-nn8smeyj` → 目标实例,Filter.Type 使用小写 `"name"`): + +```bash +tccli tcr ManageReplication \ + --SourceRegistryId '' \ + --DestinationRegistryId '' \ + --Rule '{"Name":"sync-test","DestNamespace":"test-ns","Override":false,"Filters":[{"Type":"name","Value":".*"}],"Deletion":false}' \ + --DestinationRegionId \ + --region --output json +``` + +**输出**: + +```json +{ + "RequestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" +} +``` + +> **关键**:`Filter.Type` 必须使用全小写 `"name"`,不能写成 `"Name"`。真机验证:使用 `"Name"` 时同步规则不生效(无镜像同步到目标侧),修正为 `"name"` 后同步正常。 + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | 源实例 ID | standard 或 premium,Status=Running | `DescribeInstances` | +| `` | 目标实例 ID | 已创建,Status=Running | `DescribeInstances`(目标地域执行) | +| `` | 目标实例所在地域数字 ID | `DescribeRegions` 中的 `RegionId` 整数 | `DescribeRegions` | +| `` | 源实例所在地域 | 与源实例一致 | `configure list` 查看默认 region | + +#### 跨主账号同步(需目标侧凭证) + +```bash +tccli tcr ManageReplication \ + --SourceRegistryId '' \ + --DestinationRegistryId '' \ + --Rule '{"Name":"cross-account-sync","DestNamespace":"shared","Override":false,"Filters":[{"Type":"name","Value":".*"}],"Deletion":false}' \ + --DestinationRegionId \ + --PeerReplicationOption '{"PeerRegistryUin":"","PeerRegistryToken":"","EnablePeerReplication":true}' \ + --region --output json +``` + +**`PeerReplicationOption` 字段**: + +| 字段 | 类型 | 必填 | 说明 | +|------|------|:--:|------| +| `PeerRegistryUin` | String | 是 | 目标主账号 UIN | +| `PeerRegistryToken` | String | 是 | 目标实例的用户级长期访问密码 | +| `EnablePeerReplication` | Boolean | 是 | 是否开启跨主账号同步,填 `true` | + +### 步骤4:验证同步策略已创建 + +```bash +tccli tcr DescribeReplicationPolicies --RegistryId '' --region --output json +``` + +**输出(规则已创建)**: + +```json +{ + "TotalCount": 1, + "ReplicationPolicyInfoList": [ + { + "ID": 1, + "Name": "sync-test", + "Description": "", + "Filters": [ + {"Type": "name", "Value": ".*"} + ], + "Override": false, + "Enabled": true, + "SrcResource": "/test-ns/** ap-guangzhou", + "DestResource": "/test-ns/** ap-singapore", + "CreationTime": "2026-05-24T17:15:00+08:00", + "UpdateTime": "2026-05-24T17:15:00+08:00" + } + ], + "RequestId": "b57e8aea-4bb8-4382-ac03-a8c41a6b53e7" +} +``` + +### 步骤5:查看同步状态 + +```bash +tccli tcr DescribeReplicationInstanceSyncStatus \ + --RegistryId '' \ + --ReplicationRegistryId '' \ + --region --output json +``` + +**输出**: + +```json +{ + "ReplicationStatus": "Succeed", + "ReplicationTime": "2026-05-24T17:20:30+08:00", + "ReplicationLog": { + "ReplicationTime": "2026-05-24T17:20:30+08:00", + "TaskId": 42, + "Status": "Succeed", + "Percentage": 100, + "Total": 3 + }, + "RequestId": "2a416e2a-72e9-424d-8700-9dd5a3e7ae71" +} +``` + +**`ReplicationStatus` 枚举**: + +| 状态 | 说明 | +|------|------| +| `Succeed` | 同步成功 | +| `InProgress` | 同步中(可通过 `ReplicationLog.Percentage` 查看进度) | +| `Failed` | 同步失败 | +| `Cancel` | 已取消 | + +## 验证 + +### 控制面(tccli) + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 源实例满足规格要求 | `DescribeInstances --Registryids '[""]' --region ` | `RegistryType: "standard"` 或 `"premium"`, `Status: "Running"` | +| 同步规则已创建 | `DescribeReplicationPolicies --RegistryId '' --region ` | 目标 `Name` 存在,`Enabled: true` | +| 同步关系正常运行 | `DescribeReplicationInstances --RegistryId '' --region ` | 目标 `ReplicationRegistryId` 存在,`Status: "Running"` | +| 同步状态正常 | `DescribeReplicationInstanceSyncStatus` | `ReplicationStatus: "Succeed"`,`Percentage: 100` | + +### 数据面 + +```bash +# 在源实例推送测试镜像 +docker tag alpine:latest //test-sync:v1 +docker push //test-sync:v1 + +# 等待同步完成后(约 10--30 秒),从目标实例拉取 +docker pull //test-sync:v1 +# expected: 拉取成功,镜像存在 +``` + +## 清理 + +### 1. 查看当前同步实例(获取 ReplicationRegistryId) + +```bash +tccli tcr DescribeReplicationInstances --RegistryId '' --region --output json +# 记录目标同步关系的 ReplicationRegistryId 和 ReplicationRegionId +``` + +### 2. 删除同步实例关系 + +```bash +tccli tcr DeleteReplicationInstance \ + --RegistryId '' \ + --ReplicationRegistryId '' \ + --ReplicationRegionId \ + --region --output json +# expected: exit 0 +``` + +> **注意**:`DeleteReplicationInstance` 的 `--ReplicationRegionId` 为**必填参数**,从 `DescribeReplicationInstances` 输出的 `ReplicationRegionId` 字段获取。 + +### 3. 验证已删除 + +```bash +tccli tcr DescribeReplicationInstances --RegistryId '' --region --output json +# expected: TotalCount 减少,目标 ReplicationRegistryId 不再出现 +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ManageReplication` 返回 `UnsupportedOperation` | `tccli tcr DescribeInstances --Registryids '[""]' --region --filter "Registries[0].RegistryType"` | 实例为 basic,不支持同步功能 | `tccli tcr ModifyInstance --RegistryId --RegistryType standard --region ` 升级至 standard。等待 `Status: Running` 后重试。升级不可逆 | +| `ManageReplication` 返回 `InvalidParameter` | 检查 `Rule` JSON 字段格式 | 规则名格式不符(需小写字母、数字、`-._`,以字母/数字开头)或 JSON 格式错误 | 修正 `Rule.Name`(如 `"sync-prod-v1"`),确认 JSON 合法(可用 `echo '' \| jq .` 验证) | +| 同步规则创建成功但镜像未同步 | `DescribeReplicationPolicies` 检查 `Filters` 列表 | `Filter.Type` 使用了大写 `"Name"` 而非小写 `"name"` | 修正为 `"Type":"name"`(全小写),重新提交 `ManageReplication` | +| 跨主账号同步报权限错误 | 确认 `PeerReplicationOption` 中凭证类型和有效期 | 使用了服务级账号密码(不支持),或凭证已过期 | 更换为用户级账号长期访问凭证,确认 `ExpTime` 未过期。用户名仅支持[用户级账号](https://cloud.tencent.com/document/product/1141/41829) | +| 同步一直处于 `InProgress` | `DescribeReplicationInstanceSyncStatus` 查看 `ReplicationLog.Percentage` | 待同步镜像量大,同步队列处理中 | 检查进度百分比;若 10 分钟无进展,凭 `RequestId` [在线咨询](https://cloud.tencent.com/online-service?from=doc_1141) | +| `DeleteReplicationInstance` 报错缺少 `--ReplicationRegionId` | — | `DeleteReplicationInstance` 的 `--ReplicationRegionId` 为**必填参数** | 补充 `--ReplicationRegionId `,从 `DescribeReplicationInstances` 输出的 `ReplicationRegionId` 字段获取 | +| 删除实例时报 "please delete the replication rule first" | `DescribeReplicationPolicies` 检查规则列表 | 实例下存在未删除的同步规则 | 先通过控制台或重新 `ManageReplication` 停用规则,再删除同步实例关系 | + +### 同步规则设计问题 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 多层级仓库同步缺失子路径 | `DescribeReplicationPolicies` 查看 `Filters` | `Filters.name` 正则过滤不完整 | 使用 `".*"` 匹配全部子路径,或使用 `"**"` 递归匹配 | +| 目标实例出现镜像覆盖 | 检查 `Rule` 中 `Override` 值 | `Override: true` 时同名镜像被源侧覆盖 | 将 `Override` 设为 `false`(跳过已存在镜像),重新创建规则 | +| 源实例删除镜像后目标实例仍保留 | 检查 `Rule` 中 `Deletion` 值 | `Deletion: false`(默认)未开启同步删除 | 创建规则时设 `Deletion: true`,注意此操作可能导致目标侧数据同步删除 | + +### 规格升级注意事项 + +| 场景 | CLI 命令 | 说明 | +|------|---------|------| +| basic → standard | `tccli tcr ModifyInstance --RegistryId --RegistryType standard --region ` | 升级后立即解锁同步功能,按量计费单价随之提升 | +| standard → premium | 同上,`--RegistryType premium` | 额外解锁复制实例、签名策略等高级功能 | +| 升级耗时 | 约 30--60 秒 | `DescribeInstanceStatus` 返回 `Running` 即升级完成 | + +## 下一步 + +- [同实例多地域复制镜像](../cross-region-replication)(page_id `52095`)——同一实例内的多地域只读分发 +- [管理命名空间](../../image-creation/namespace)——目标实例可能需要提前创建对应命名空间 +- [内网访问控制](../../access/network/private-access)——VPC 接入 TCR 实例实现内网拉取 + +## 控制台替代 + +[容器镜像服务控制台 → 同步复制 → 实例同步](https://console.cloud.tencent.com/tcr/replication):选择源实例,配置同步规则(目标实例、命名空间、过滤器、跨账号凭证),保存后自动开始同步。可在同步日志中查看进度和状态。 diff --git a/src/content/docs/cli/tcr/ops/image-distribution/cross-instance-sync/cross-instance-sync-tcrctl.md b/src/content/docs/cli/tcr/ops/image-distribution/cross-instance-sync/cross-instance-sync-tcrctl.md new file mode 100644 index 0000000..d4ccefa --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-distribution/cross-instance-sync/cross-instance-sync-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "跨实例(账号)同步镜像(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[跨实例(账号)同步镜像](https://cloud.tencent.com/document/product/1141/41945) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/image-distribution/cross-instance-sync/cross-instance-sync.md b/src/content/docs/cli/tcr/ops/image-distribution/cross-instance-sync/cross-instance-sync.md new file mode 100644 index 0000000..c2456b2 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-distribution/cross-instance-sync/cross-instance-sync.md @@ -0,0 +1,342 @@ +--- +title: "跨实例(账号)同步镜像(tccli)" +description: "· page_id `41945`" +--- + +> 对照官方:[跨实例(账号)同步镜像](https://cloud.tencent.com/document/product/1141/41945) · page_id `41945` + +## 概述 + +通过 `tccli tcr ManageReplication` 在不同地域的两个 TCR 企业版实例间建立同步规则,实现容器镜像、Helm Chart 的自动同步分发。支持**同主账号内实例同步**和**跨主账号实例同步**——跨账号场景下,源侧用户以目标侧提供的实例 ID、账号 UIN 和访问凭证建立同步规则。 + +**与同实例多地域复制的差异**: + +| 维度 | 跨实例同步(本页) | 同实例多地域复制 | +|------|-------------------|-------------------| +| 实例关系 | 两个独立实例(可不同账号) | 同一实例的主实例 + 从实例 | +| 域名 | 各自独立域名 | 主/从共享同一域名 | +| 推送方向 | 两侧均可推送 | 仅主实例可推送,从实例只读拉取 | +| 跨主账号 | **支持**(需目标侧凭证) | 不支持 | +| 国内外互通 | **支持**(含跨境) | 不支持 | + +> **规格要求**:同步功能支持**标准版(standard)和高级版(premium)**。基础版(basic)调用 `ManageReplication` 返回 `UnsupportedOperation`——需 `ModifyInstance --RegistryType standard` 升级后方可使用。升级路径:`basic → standard → premium`,不支持降级。 + +## 前置条件 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据 +tccli configure list +# expected: secretId、secretKey、region 均已配置 + +# 3. 确认源实例规格为 standard 或以上 +tccli tcr DescribeInstances --Registryids '[""]' --region --output json \ + --filter "Registries[0].{RegistryId:RegistryId,RegistryType:RegistryType,Status:Status}" +# expected: RegistryType: "standard" 或 "premium", Status: "Running" + +# 4. 确认目标实例已创建且 Running +tccli tcr DescribeInstances --Registryids '[""]' --region --output json \ + --filter "Registries[0].{RegistryId:RegistryId,RegistryType:RegistryType,Status:Status}" +# expected: Status: "Running" + +# 5. 确认 CAM 权限 — 需要 tcr:ManageReplication, tcr:DescribeReplicationPolicies, +# tcr:DescribeReplicationInstances, tcr:DescribeReplicationInstanceSyncStatus +tccli tcr DescribeReplicationInstances --RegistryId '' --region +# expected: exit 0 +``` + +### 跨主账号同步额外准备 + +若需跨主账号同步,目标侧用户需提供: +- 目标实例 ID(`DestinationRegistryId`) +- 目标主账号 UIN(`PeerRegistryUin`) +- 长期访问凭证(`PeerRegistryToken`,用户级账号密码) + +> 用户名仅支持[用户级账号](https://cloud.tencent.com/document/product/1141/41829),不支持[服务级账号](https://cloud.tencent.com/document/product/1141/89137)。**凭证需长期有效**,避免过期导致同步中断。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 / 参数 | 幂等 | +|-----------|------------------|:--:| +| 查看实例同步列表 | `DescribeReplicationInstances` | 是 | +| 新建实例同步规则 | `ManageReplication` | 否(同一规则名重复创建报错) | +| 查看同步策略列表 | `DescribeReplicationPolicies` | 是 | +| 查看同步状态 | `DescribeReplicationInstanceSyncStatus` | 是 | +| 修改规则状态(启用/关闭) | `ManageReplication`(更新 `Rule` 后重新提交) | — | +| 删除实例同步 | `DeleteReplicationInstance` | 是 | + +## 关键字段说明 + +### ManageReplication 参数 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `SourceRegistryId` | String | 是 | 源实例 ID | 不存在 → `ResourceNotFound` | +| `DestinationRegistryId` | String | 是 | 目标实例 ID | 不存在 → `ResourceNotFound` | +| `DestinationRegionId` | Integer | **是** | 目标实例所在地域的数字 ID(通过 `DescribeRegions` 获取) | 未传或错误 → `InvalidParameter` | +| `Rule` | String | 是 | JSON 序列化的规则对象字符串,含 `Name`(规则名)、`DestNamespace`(目标命名空间)、`Override`(是否覆盖)、`Filters`(过滤器列表)、`Deletion`(同步删除) | JSON 格式错误 → `InvalidParameter` | +| `Description` | String | 否 | 规则描述 | — | + +### Rule 对象字段 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `Name` | String | 是 | 规则名称,支持小写字母、数字及 `-._`,以字母/数字开头 | 格式不符 → `InvalidParameter` | +| `DestNamespace` | String | 否 | 目标命名空间,不填默认与源同名,目标侧不存在则自动创建 | — | +| `Override` | Boolean | 否 | 是否覆盖目标侧已有同名镜像,建议 `false` | `true` 时目标侧同名镜像被覆盖 | +| `Filters` | Array | 否 | 过滤器列表,不填则同步命名空间内全部资源 | 过滤条件不匹配导致镜像漏同步 | +| `Deletion` | Boolean | 否 | 是否启用同步删除(源镜像删除后同步删除目标侧镜像),默认 `false` | `true` 时可能误删目标侧数据 | + +### Filters 条目字段 + +| Filter Type | 含义 | 示例 | 注意 | +|-------------|------|------|------| +| `name` | 仓库名称正则过滤,空则匹配命名空间内全部仓库 | `".*"`、`"prod/**"` | **Filter.Type 必须全小写** `"name"`,不能写成 `"Name"`,否则视为非法过滤器导致规则不生效 | +| `tag` | 版本 Tag 正则过滤,空则匹配所有版本 | `"v1.*"`、`""`(全部) | — | +| `resource` | 资源类型过滤 | `"image"`(容器镜像)、`"chart"`(Helm Chart),不传则全部同步 | — | + +## 操作步骤 + +### 步骤1:查看已有同步实例 + +```bash +tccli tcr DescribeReplicationInstances --RegistryId '' --region --output json +``` + +**输出(无同步实例时)**: + +```json +{ + "TotalCount": 0, + "ReplicationRegistries": null, + "RequestId": "d94482cb-5f85-43f3-80a5-f0263faff798" +} +``` + +### 步骤2:查看已有同步策略 + +```bash +tccli tcr DescribeReplicationPolicies --RegistryId '' --region --output json +``` + +**输出(未配置同步规则时)**: + +```json +{ + "TotalCount": 0, + "RequestId": "6683742b-ff91-4cd1-8fcf-bf8f2a2b6091" +} +``` + +### 步骤3:创建同步规则 + +`ManageReplication` 同时负责创建与更新规则。以下是真实执行的命令(真机 premium 实例 `tcr-nn8smeyj` → 目标实例,Filter.Type 使用小写 `"name"`): + +```bash +tccli tcr ManageReplication \ + --SourceRegistryId '' \ + --DestinationRegistryId '' \ + --Rule '{"Name":"sync-test","DestNamespace":"test-ns","Override":false,"Filters":[{"Type":"name","Value":".*"}],"Deletion":false}' \ + --DestinationRegionId \ + --region --output json +``` + +**输出**: + +```json +{ + "RequestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" +} +``` + +> **关键**:`Filter.Type` 必须使用全小写 `"name"`,不能写成 `"Name"`。真机验证:使用 `"Name"` 时同步规则不生效(无镜像同步到目标侧),修正为 `"name"` 后同步正常。 + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | 源实例 ID | standard 或 premium,Status=Running | `DescribeInstances` | +| `` | 目标实例 ID | 已创建,Status=Running | `DescribeInstances`(目标地域执行) | +| `` | 目标实例所在地域数字 ID | `DescribeRegions` 中的 `RegionId` 整数 | `DescribeRegions` | +| `` | 源实例所在地域 | 与源实例一致 | `configure list` 查看默认 region | + +#### 跨主账号同步(需目标侧凭证) + +```bash +tccli tcr ManageReplication \ + --SourceRegistryId '' \ + --DestinationRegistryId '' \ + --Rule '{"Name":"cross-account-sync","DestNamespace":"shared","Override":false,"Filters":[{"Type":"name","Value":".*"}],"Deletion":false}' \ + --DestinationRegionId \ + --PeerReplicationOption '{"PeerRegistryUin":"","PeerRegistryToken":"","EnablePeerReplication":true}' \ + --region --output json +``` + +**`PeerReplicationOption` 字段**: + +| 字段 | 类型 | 必填 | 说明 | +|------|------|:--:|------| +| `PeerRegistryUin` | String | 是 | 目标主账号 UIN | +| `PeerRegistryToken` | String | 是 | 目标实例的用户级长期访问密码 | +| `EnablePeerReplication` | Boolean | 是 | 是否开启跨主账号同步,填 `true` | + +### 步骤4:验证同步策略已创建 + +```bash +tccli tcr DescribeReplicationPolicies --RegistryId '' --region --output json +``` + +**输出(规则已创建)**: + +```json +{ + "TotalCount": 1, + "ReplicationPolicyInfoList": [ + { + "ID": 1, + "Name": "sync-test", + "Description": "", + "Filters": [ + {"Type": "name", "Value": ".*"} + ], + "Override": false, + "Enabled": true, + "SrcResource": "/test-ns/** ap-guangzhou", + "DestResource": "/test-ns/** ap-singapore", + "CreationTime": "2026-05-24T17:15:00+08:00", + "UpdateTime": "2026-05-24T17:15:00+08:00" + } + ], + "RequestId": "b57e8aea-4bb8-4382-ac03-a8c41a6b53e7" +} +``` + +### 步骤5:查看同步状态 + +```bash +tccli tcr DescribeReplicationInstanceSyncStatus \ + --RegistryId '' \ + --ReplicationRegistryId '' \ + --region --output json +``` + +**输出**: + +```json +{ + "ReplicationStatus": "Succeed", + "ReplicationTime": "2026-05-24T17:20:30+08:00", + "ReplicationLog": { + "ReplicationTime": "2026-05-24T17:20:30+08:00", + "TaskId": 42, + "Status": "Succeed", + "Percentage": 100, + "Total": 3 + }, + "RequestId": "2a416e2a-72e9-424d-8700-9dd5a3e7ae71" +} +``` + +**`ReplicationStatus` 枚举**: + +| 状态 | 说明 | +|------|------| +| `Succeed` | 同步成功 | +| `InProgress` | 同步中(可通过 `ReplicationLog.Percentage` 查看进度) | +| `Failed` | 同步失败 | +| `Cancel` | 已取消 | + +## 验证 + +### 控制面(tccli) + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 源实例满足规格要求 | `DescribeInstances --Registryids '[""]' --region ` | `RegistryType: "standard"` 或 `"premium"`, `Status: "Running"` | +| 同步规则已创建 | `DescribeReplicationPolicies --RegistryId '' --region ` | 目标 `Name` 存在,`Enabled: true` | +| 同步关系正常运行 | `DescribeReplicationInstances --RegistryId '' --region ` | 目标 `ReplicationRegistryId` 存在,`Status: "Running"` | +| 同步状态正常 | `DescribeReplicationInstanceSyncStatus` | `ReplicationStatus: "Succeed"`,`Percentage: 100` | + +### 数据面 + +```bash +# 在源实例推送测试镜像 +docker tag alpine:latest //test-sync:v1 +docker push //test-sync:v1 + +# 等待同步完成后(约 10--30 秒),从目标实例拉取 +docker pull //test-sync:v1 +# expected: 拉取成功,镜像存在 +``` + +## 清理 + +### 1. 查看当前同步实例(获取 ReplicationRegistryId) + +```bash +tccli tcr DescribeReplicationInstances --RegistryId '' --region --output json +# 记录目标同步关系的 ReplicationRegistryId 和 ReplicationRegionId +``` + +### 2. 删除同步实例关系 + +```bash +tccli tcr DeleteReplicationInstance \ + --RegistryId '' \ + --ReplicationRegistryId '' \ + --ReplicationRegionId \ + --region --output json +# expected: exit 0 +``` + +> **注意**:`DeleteReplicationInstance` 的 `--ReplicationRegionId` 为**必填参数**,从 `DescribeReplicationInstances` 输出的 `ReplicationRegionId` 字段获取。 + +### 3. 验证已删除 + +```bash +tccli tcr DescribeReplicationInstances --RegistryId '' --region --output json +# expected: TotalCount 减少,目标 ReplicationRegistryId 不再出现 +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ManageReplication` 返回 `UnsupportedOperation` | `tccli tcr DescribeInstances --Registryids '[""]' --region --filter "Registries[0].RegistryType"` | 实例为 basic,不支持同步功能 | `tccli tcr ModifyInstance --RegistryId --RegistryType standard --region ` 升级至 standard。等待 `Status: Running` 后重试。升级不可逆 | +| `ManageReplication` 返回 `InvalidParameter` | 检查 `Rule` JSON 字段格式 | 规则名格式不符(需小写字母、数字、`-._`,以字母/数字开头)或 JSON 格式错误 | 修正 `Rule.Name`(如 `"sync-prod-v1"`),确认 JSON 合法(可用 `echo '' \| jq .` 验证) | +| 同步规则创建成功但镜像未同步 | `DescribeReplicationPolicies` 检查 `Filters` 列表 | `Filter.Type` 使用了大写 `"Name"` 而非小写 `"name"` | 修正为 `"Type":"name"`(全小写),重新提交 `ManageReplication` | +| 跨主账号同步报权限错误 | 确认 `PeerReplicationOption` 中凭证类型和有效期 | 使用了服务级账号密码(不支持),或凭证已过期 | 更换为用户级账号长期访问凭证,确认 `ExpTime` 未过期。用户名仅支持[用户级账号](https://cloud.tencent.com/document/product/1141/41829) | +| 同步一直处于 `InProgress` | `DescribeReplicationInstanceSyncStatus` 查看 `ReplicationLog.Percentage` | 待同步镜像量大,同步队列处理中 | 检查进度百分比;若 10 分钟无进展,凭 `RequestId` [在线咨询](https://cloud.tencent.com/online-service?from=doc_1141) | +| `DeleteReplicationInstance` 报错缺少 `--ReplicationRegionId` | — | `DeleteReplicationInstance` 的 `--ReplicationRegionId` 为**必填参数** | 补充 `--ReplicationRegionId `,从 `DescribeReplicationInstances` 输出的 `ReplicationRegionId` 字段获取 | +| 删除实例时报 "please delete the replication rule first" | `DescribeReplicationPolicies` 检查规则列表 | 实例下存在未删除的同步规则 | 先通过控制台或重新 `ManageReplication` 停用规则,再删除同步实例关系 | + +### 同步规则设计问题 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 多层级仓库同步缺失子路径 | `DescribeReplicationPolicies` 查看 `Filters` | `Filters.name` 正则过滤不完整 | 使用 `".*"` 匹配全部子路径,或使用 `"**"` 递归匹配 | +| 目标实例出现镜像覆盖 | 检查 `Rule` 中 `Override` 值 | `Override: true` 时同名镜像被源侧覆盖 | 将 `Override` 设为 `false`(跳过已存在镜像),重新创建规则 | +| 源实例删除镜像后目标实例仍保留 | 检查 `Rule` 中 `Deletion` 值 | `Deletion: false`(默认)未开启同步删除 | 创建规则时设 `Deletion: true`,注意此操作可能导致目标侧数据同步删除 | + +### 规格升级注意事项 + +| 场景 | CLI 命令 | 说明 | +|------|---------|------| +| basic → standard | `tccli tcr ModifyInstance --RegistryId --RegistryType standard --region ` | 升级后立即解锁同步功能,按量计费单价随之提升 | +| standard → premium | 同上,`--RegistryType premium` | 额外解锁复制实例、签名策略等高级功能 | +| 升级耗时 | 约 30--60 秒 | `DescribeInstanceStatus` 返回 `Running` 即升级完成 | + +## 下一步 + +- [同实例多地域复制镜像](../cross-region-replication)(page_id `52095`)——同一实例内的多地域只读分发 +- [管理命名空间](../../image-creation/namespace)——目标实例可能需要提前创建对应命名空间 +- [内网访问控制](../../access/network/private-access)——VPC 接入 TCR 实例实现内网拉取 + +## 控制台替代 + +[容器镜像服务控制台 → 同步复制 → 实例同步](https://console.cloud.tencent.com/tcr/replication):选择源实例,配置同步规则(目标实例、命名空间、过滤器、跨账号凭证),保存后自动开始同步。可在同步日志中查看进度和状态。 diff --git a/src/content/docs/cli/tcr/ops/image-distribution/cross-region-replication-tcrctl.md b/src/content/docs/cli/tcr/ops/image-distribution/cross-region-replication-tcrctl.md new file mode 100644 index 0000000..9b1745a --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-distribution/cross-region-replication-tcrctl.md @@ -0,0 +1,26 @@ +--- +title: "同实例多地域复制镜像(tcrctl)" +description: "· page_id `52095`" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> 对照官方:[同实例多地域复制镜像](https://cloud.tencent.com/document/product/1141/52095) · page_id `52095` +> +> **P1 — 占位符** · 待 P0 全量完成后填充 `tcrctl` 操作体。 + +## Overview + +(P1 待撰写:`tcrctl replication create` / `tcrctl replication list` / `tcrctl replication delete` 等产品 CLI 命令封装 `CreateReplicationInstance` / `DescribeReplicationInstances` / `DeleteReplicationInstance`,内置任务轮询与状态等待。) + +## Before you begin + +参见同目录 [tccli 操作.md](.) 的 Before you begin。 + +## Procedure + +> P1 待撰写。 + +## Next steps + +参见同目录 [tccli 操作.md](.) 的 Next steps。 diff --git a/src/content/docs/cli/tcr/ops/image-distribution/cross-region-replication.md b/src/content/docs/cli/tcr/ops/image-distribution/cross-region-replication.md new file mode 100644 index 0000000..af33582 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-distribution/cross-region-replication.md @@ -0,0 +1,312 @@ +--- +title: "同实例多地域复制镜像(tccli)" +description: "· page_id `52095`" +--- + +> 对照官方:[同实例多地域复制镜像](https://cloud.tencent.com/document/product/1141/52095) · page_id `52095` + +## 概述 + +通过 `tccli tcr CreateReplicationInstance` 为 TCR premium 实例在其他地域创建**从实例(Replication Instance)**。主实例与从实例共享同一域名和登录凭证,镜像推送至主实例后自动同步至各地域从实例,实现**单地域上传、多地域同步、就近内网拉取**。 + +> **规格限制**:仅 premium 实例支持。basic 实例调用 `CreateReplicationInstance` 返回 `UnsupportedOperation`——需先 `ModifyInstance --RegistryType premium` 升级,升级不可降级。 + +**与跨实例同步的核心差异**: + +| 维度 | 同实例多地域复制(本页) | 跨实例(账号)同步 | +|------|---------------------------|-------------------| +| 实例关系 | 同一实例的主实例 + 从实例(唯一 `RegistryId`) | 两个独立实例(各自 `RegistryId`) | +| 域名 | 主/从共享同一域名 | 各自独立域名 | +| 推送方向 | 仅主实例可推送,从实例只读拉取 | 两侧均可推送 | +| 适用场景 | 同账号多地域只读分发、就近拉取 | 跨账号分发、灾备、多活推送 | + +## 前置条件 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId、secretKey、region 均已配置 + +# 3. 确认实例规格为 premium(basic/standard 不支持复制实例) +tccli tcr DescribeInstances --Registryids '[""]' --region --output json \ + --filter "Registries[0].{RegistryId:RegistryId,RegistryType:RegistryType,Status:Status}" +# expected: RegistryType: "premium", Status: "Running" + +# 4. 确认目标地域已开通 TCR +tccli tcr DescribeRegions --region --output json | jq '.Regions[] | select(.RegionName=="")' +# expected: RegionName 匹配,Status: "alluser" +``` + +### 计费说明 + +- 每个从实例在目标地域产生独立计费,费用与主实例规格相关 +- 从实例删除后停止计费 +- 主实例升级/premium 本身按量计费单价随 tier 提升 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 / 参数 | 幂等 | +|-----------|------------------|:--:| +| 查询从实例列表 | `DescribeReplicationInstances` | 是 | +| 新建从实例(选择目标地域) | `CreateReplicationInstance --ReplicationRegionId ` | 否(重复创建同地域报错) | +| 查看创建进度 | `DescribeReplicationInstanceCreateTasks` | 是 | +| 查看同步状态 | `DescribeReplicationInstanceSyncStatus` | 是 | +| 删除从实例 | `DeleteReplicationInstance` | 否(删除后再次调用报 NotFound) | +| 升级至 premium(若当前为 basic/standard) | `ModifyInstance --RegistryType premium` | 否(已为 premium 时幂等) | + +## 关键字段说明 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | 主实例 ID,形如 `tcr-nn8smeyj` | 实例不存在 → `ResourceNotFound` | +| `ReplicationRegionId` | Integer | 是 | 目标地域数字 ID,通过 `DescribeRegions` 获取(如 ap-shanghai=4, ap-beijing=8)。**注意不是 RegionName 字符串** | 传入非法值 → `InvalidParameter` 或 `InvalidParameter.ReplicationRegionId`;传入与主实例相同地域 → `InvalidParameter`(不支持同地域复制) | +| `ReplicationRegionName` | String | 否 | 目标地域名称,如 `ap-shanghai`。传入后 API 返回的 `ReplicationRegionName` 将包含此值 | — | + +## 操作步骤 + +### 步骤1:确认实例为 premium 并查看现有从实例 + +```bash +tccli tcr DescribeReplicationInstances --RegistryId 'tcr-nn8smeyj' --region ap-guangzhou +``` + +**输出(premium 实例,无已创建的从实例)**: + +```json +{ + "TotalCount": 0, + "ReplicationRegistries": null, + "RequestId": "d94482cb-5f85-43f3-80a5-f0263faff798" +} +``` + +> 若实例非 premium,`DescribeReplicationInstances` 仍正常返回 `TotalCount: 0` 和 `ReplicationRegistries: null`,但后续 `CreateReplicationInstance` 将报 `UnsupportedOperation`——需先升级。 + +### 步骤2:升级实例至 premium(如需要) + +若当前实例为 basic 或 standard,需先执行升级: + +```bash +tccli tcr ModifyInstance --RegistryId '' --RegistryType premium --region +# expected: exit 0,返回 RequestId +``` + +轮询确认升级完成: + +```bash +tccli tcr DescribeInstanceStatus --RegistryIds '[""]' --region --output json \ + --filter "RegistryStatusSet[0].Status" +# expected: "Running"(升级约 30--60 秒) +``` + +### 步骤3:查询目标地域 RegionId + +```bash +tccli tcr DescribeRegions --region ap-guangzhou --output json +``` + +**输出(截取关键条目)**: + +```json +{ + "Regions": [ + {"Alias": "gz", "RegionId": 1, "RegionName": "ap-guangzhou", "Status": "alluser"}, + {"Alias": "sh", "RegionId": 4, "RegionName": "ap-shanghai", "Status": "alluser"}, + {"Alias": "bj", "RegionId": 8, "RegionName": "ap-beijing", "Status": "alluser"} + ] +} +``` + +> 记录目标地域的 `RegionId`(整数),例如 `ap-shanghai` 对应 `4`。 + +### 步骤4:创建从实例 + +```bash +tccli tcr CreateReplicationInstance \ + --RegistryId 'tcr-nn8smeyj' \ + --ReplicationRegionId 4 \ + --region ap-guangzhou +``` + +**输出**: + +```json +{ + "ReplicationRegistryId": "tcr-nn8smeyj-4-xxxxxxxx", + "RequestId": "eac6b301-a322-493a-8e36-83b295459397" +} +``` + +> 记录返回的 `ReplicationRegistryId`(形如 `tcr-nn8smeyj-4-xxxxxxxx`),后续查询同步状态和删除均需此 ID。创建为异步操作。 + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `tcr-nn8smeyj` | 主实例 RegistryId | 已存在、premium 规格、Status=Running | `DescribeInstances` | +| `4` | 目标地域 RegionId | 数字格式,与主实例地域不同 | `DescribeRegions` 查询目标地域的 `RegionId` | + +### 步骤5:查看从实例创建进度 + +```bash +tccli tcr DescribeReplicationInstanceCreateTasks \ + --RegistryId 'tcr-nn8smeyj' \ + --ReplicationRegistryId 'tcr-nn8smeyj-4-xxxxxxxx' \ + --region ap-guangzhou --output json +``` + +**输出**: + +```json +{ + "TaskDetail": [ + { + "TaskName": "CreateReplication", + "TaskUUID": "xxxx-xxxx-xxxx", + "TaskStatus": "Succeed", + "CreatedTime": "2026-05-24T17:09:41+08:00", + "FinishedTime": "2026-05-24T17:10:11+08:00" + } + ], + "Status": "Succeed", + "RequestId": "109a280f-a11e-44d5-8741-9d3f213b06f2" +} +``` + +> 从实例创建通常约 30--60 秒完成。若 `Status` 为 `Running` 或 `InProgress`,等待后重新查询。 + +### 步骤6:查看从实例列表(确认创建) + +```bash +tccli tcr DescribeReplicationInstances --RegistryId 'tcr-nn8smeyj' --region ap-guangzhou --output json +``` + +**输出**: + +```json +{ + "TotalCount": 1, + "ReplicationRegistries": [ + { + "RegistryId": "tcr-nn8smeyj", + "ReplicationRegistryId": "tcr-nn8smeyj-4-xxxxxxxx", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai", + "Status": "Running", + "CreatedAt": "2026-05-24T17:09:41+08:00" + } + ], + "RequestId": "70342808-5071-4971-ab62-c7a0b8a9bf09" +} +``` + +> `Status: "Running"` 表示从实例创建完成,可确认下一步同步逻辑。 + +### 步骤7:查看同步状态 + +```bash +tccli tcr DescribeReplicationInstanceSyncStatus \ + --RegistryId 'tcr-nn8smeyj' \ + --ReplicationRegistryId 'tcr-nn8smeyj-4-xxxxxxxx' \ + --region ap-guangzhou --output json +``` + +**输出**: + +```json +{ + "ReplicationStatus": "Synced", + "ReplicationTime": "2026-05-24T17:09:41+08:00", + "RequestId": "2a416e2a-72e9-424d-8700-9dd5a3e7ae71" +} +``` + +> `ReplicationStatus: "Synced"` 表示主实例与从实例同步完成。若为 `Syncing`,表示同步进行中。 + +## 验证 + +### 控制面(tccli) + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 从实例已创建 | `DescribeReplicationInstances --RegistryId '' --region ` | `TotalCount >= 1`,`ReplicationRegistries` 含目标地域 | +| 从实例 Status 为 Running | 同上,检查 `ReplicationRegistries[].Status` | `"Running"` | +| 创建任务完成 | `DescribeReplicationInstanceCreateTasks` | `Status: "Succeed"` | +| 同步状态正常 | `DescribeReplicationInstanceSyncStatus` | `ReplicationStatus: "Synced"` | +| 主实例规格为 premium | `DescribeInstances --Registryids '[""]'` | `RegistryType: "premium"` | + +### 数据面 + +```bash +# 在源地域推送测试镜像至主实例 +docker tag alpine:latest //test-replication:v1 +docker push //test-replication:v1 + +# 从目标地域通过内网拉取(需目标地域有 VPC 接入) +docker pull //test-replication:v1 +# expected: 拉取成功(证明同步已完成) +``` + +> 镜像同步为自动触发,推送后约 10--30 秒可在从实例地域拉取到相同镜像。 + +## 清理 + +> **警告:删除主实例前必须删除所有从实例**,否则 `DeleteInstance` 返回错误(提示 "has N replication registry")。从实例删除不可逆。 + +### 1. 确认当前从实例列表 + +```bash +tccli tcr DescribeReplicationInstances --RegistryId 'tcr-nn8smeyj' --region ap-guangzhou --output json +# 记录所有待删除的 ReplicationRegistryId +``` + +### 2. 逐个删除从实例 + +```bash +tccli tcr DeleteReplicationInstance \ + --RegistryId 'tcr-nn8smeyj' \ + --ReplicationRegistryId 'tcr-nn8smeyj-4-xxxxxxxx' \ + --region ap-guangzhou --output json +# expected: exit 0,返回 RequestId +``` + +### 3. 验证全部删除 + +```bash +tccli tcr DescribeReplicationInstances --RegistryId 'tcr-nn8smeyj' --region ap-guangzhou --output json +# expected: TotalCount: 0, ReplicationRegistries: null +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `CreateReplicationInstance` 返回 `UnsupportedOperation` | `tccli tcr DescribeInstances --Registryids '[""]' --region --filter "Registries[0].RegistryType"` | 实例为 basic 或 standard,不支持复制实例 | `tccli tcr ModifyInstance --RegistryId --RegistryType premium --region ` 升级至 premium。升级不可降级,确认后操作 | +| `CreateReplicationInstance` 返回 `InvalidParameter` | 检查 `ReplicationRegionId` 是否为整数格式 | `ReplicationRegionId` 使用了 RegionName 字符串(如 `"ap-shanghai"`)而非数字 ID | 使用 `DescribeRegions` 查询目标地域的 `RegionId` 整数(如 ap-shanghai=4) | +| `CreateReplicationInstance` 返回 `InvalidParameter.ReplicationRegionId` | 同上 | 传入了不存在的地域 ID,或传入了与主实例相同的地域 | 使用 `DescribeRegions` 确认目标地域 `RegionId` 合法且不等于主实例所在地域 | +| `DeleteInstance` 返回错误(含 "has N replication registry") | `DescribeReplicationInstances` 查看从实例列表 | 主实例下存在未删除的从实例 | 逐个执行 `DeleteReplicationInstance`,确认 `DescribeReplicationInstances` 返回 `TotalCount: 0` 后重试 | +| `DeleteReplicationInstance` 返回 `ResourceNotFound` | 确认 `ReplicationRegistryId` 是否已被删除或拼写错误 | 从实例不存在或已删除 | 检查 `ReplicationRegistryId` 拼写(形如 `tcr-nn8smeyj-4-xxxxxxxx`) | +| `DescribeReplicationInstanceCreateTasks` 返回 `ResourceNotFound` | 确认 `ReplicationRegistryId` | 创建任务已完成清理或 ReplicationRegistryId 不正确 | 通过 `DescribeReplicationInstances` 获取正确的 `ReplicationRegistryId` | + +### 同步状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 同步状态持续 `Syncing` 超过 10 分钟 | `DescribeReplicationInstanceSyncStatus` 循环查询 | 大量镜像同步或网络延迟 | 等待完成;若持续超过 30 分钟,凭 `RequestId` [在线咨询](https://cloud.tencent.com/online-service?from=doc_1141) | +| 从实例 `Status: "Unhealthy"` | `DescribeReplicationInstances` 查看状态 | 目标地域存储或网络异常 | 若持续异常,删除后重新创建从实例 | + +## 下一步 + +- [跨实例(账号)同步镜像](../cross-instance-sync)(page_id `41945`)——跨账号独立实例同步 +- [销毁退还实例](../../delete)(page_id `51111`)——删除主实例前确保已清理从实例 +- [访问配置](../../access/network/private-access)——配置目标地域 VPC 接入以实现内网拉取 + +## 控制台替代 + +[容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 选择 premium 实例 → **同步复制** → **新建从实例**,选择目标地域和规格后确认即开始创建。 diff --git a/src/content/docs/cli/tcr/ops/image-distribution/cross-region-replication/cross-region-replication-tcrctl.md b/src/content/docs/cli/tcr/ops/image-distribution/cross-region-replication/cross-region-replication-tcrctl.md new file mode 100644 index 0000000..9b1745a --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-distribution/cross-region-replication/cross-region-replication-tcrctl.md @@ -0,0 +1,26 @@ +--- +title: "同实例多地域复制镜像(tcrctl)" +description: "· page_id `52095`" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> 对照官方:[同实例多地域复制镜像](https://cloud.tencent.com/document/product/1141/52095) · page_id `52095` +> +> **P1 — 占位符** · 待 P0 全量完成后填充 `tcrctl` 操作体。 + +## Overview + +(P1 待撰写:`tcrctl replication create` / `tcrctl replication list` / `tcrctl replication delete` 等产品 CLI 命令封装 `CreateReplicationInstance` / `DescribeReplicationInstances` / `DeleteReplicationInstance`,内置任务轮询与状态等待。) + +## Before you begin + +参见同目录 [tccli 操作.md](.) 的 Before you begin。 + +## Procedure + +> P1 待撰写。 + +## Next steps + +参见同目录 [tccli 操作.md](.) 的 Next steps。 diff --git a/src/content/docs/cli/tcr/ops/image-distribution/cross-region-replication/cross-region-replication.md b/src/content/docs/cli/tcr/ops/image-distribution/cross-region-replication/cross-region-replication.md new file mode 100644 index 0000000..af33582 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-distribution/cross-region-replication/cross-region-replication.md @@ -0,0 +1,312 @@ +--- +title: "同实例多地域复制镜像(tccli)" +description: "· page_id `52095`" +--- + +> 对照官方:[同实例多地域复制镜像](https://cloud.tencent.com/document/product/1141/52095) · page_id `52095` + +## 概述 + +通过 `tccli tcr CreateReplicationInstance` 为 TCR premium 实例在其他地域创建**从实例(Replication Instance)**。主实例与从实例共享同一域名和登录凭证,镜像推送至主实例后自动同步至各地域从实例,实现**单地域上传、多地域同步、就近内网拉取**。 + +> **规格限制**:仅 premium 实例支持。basic 实例调用 `CreateReplicationInstance` 返回 `UnsupportedOperation`——需先 `ModifyInstance --RegistryType premium` 升级,升级不可降级。 + +**与跨实例同步的核心差异**: + +| 维度 | 同实例多地域复制(本页) | 跨实例(账号)同步 | +|------|---------------------------|-------------------| +| 实例关系 | 同一实例的主实例 + 从实例(唯一 `RegistryId`) | 两个独立实例(各自 `RegistryId`) | +| 域名 | 主/从共享同一域名 | 各自独立域名 | +| 推送方向 | 仅主实例可推送,从实例只读拉取 | 两侧均可推送 | +| 适用场景 | 同账号多地域只读分发、就近拉取 | 跨账号分发、灾备、多活推送 | + +## 前置条件 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId、secretKey、region 均已配置 + +# 3. 确认实例规格为 premium(basic/standard 不支持复制实例) +tccli tcr DescribeInstances --Registryids '[""]' --region --output json \ + --filter "Registries[0].{RegistryId:RegistryId,RegistryType:RegistryType,Status:Status}" +# expected: RegistryType: "premium", Status: "Running" + +# 4. 确认目标地域已开通 TCR +tccli tcr DescribeRegions --region --output json | jq '.Regions[] | select(.RegionName=="")' +# expected: RegionName 匹配,Status: "alluser" +``` + +### 计费说明 + +- 每个从实例在目标地域产生独立计费,费用与主实例规格相关 +- 从实例删除后停止计费 +- 主实例升级/premium 本身按量计费单价随 tier 提升 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 / 参数 | 幂等 | +|-----------|------------------|:--:| +| 查询从实例列表 | `DescribeReplicationInstances` | 是 | +| 新建从实例(选择目标地域) | `CreateReplicationInstance --ReplicationRegionId ` | 否(重复创建同地域报错) | +| 查看创建进度 | `DescribeReplicationInstanceCreateTasks` | 是 | +| 查看同步状态 | `DescribeReplicationInstanceSyncStatus` | 是 | +| 删除从实例 | `DeleteReplicationInstance` | 否(删除后再次调用报 NotFound) | +| 升级至 premium(若当前为 basic/standard) | `ModifyInstance --RegistryType premium` | 否(已为 premium 时幂等) | + +## 关键字段说明 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | 主实例 ID,形如 `tcr-nn8smeyj` | 实例不存在 → `ResourceNotFound` | +| `ReplicationRegionId` | Integer | 是 | 目标地域数字 ID,通过 `DescribeRegions` 获取(如 ap-shanghai=4, ap-beijing=8)。**注意不是 RegionName 字符串** | 传入非法值 → `InvalidParameter` 或 `InvalidParameter.ReplicationRegionId`;传入与主实例相同地域 → `InvalidParameter`(不支持同地域复制) | +| `ReplicationRegionName` | String | 否 | 目标地域名称,如 `ap-shanghai`。传入后 API 返回的 `ReplicationRegionName` 将包含此值 | — | + +## 操作步骤 + +### 步骤1:确认实例为 premium 并查看现有从实例 + +```bash +tccli tcr DescribeReplicationInstances --RegistryId 'tcr-nn8smeyj' --region ap-guangzhou +``` + +**输出(premium 实例,无已创建的从实例)**: + +```json +{ + "TotalCount": 0, + "ReplicationRegistries": null, + "RequestId": "d94482cb-5f85-43f3-80a5-f0263faff798" +} +``` + +> 若实例非 premium,`DescribeReplicationInstances` 仍正常返回 `TotalCount: 0` 和 `ReplicationRegistries: null`,但后续 `CreateReplicationInstance` 将报 `UnsupportedOperation`——需先升级。 + +### 步骤2:升级实例至 premium(如需要) + +若当前实例为 basic 或 standard,需先执行升级: + +```bash +tccli tcr ModifyInstance --RegistryId '' --RegistryType premium --region +# expected: exit 0,返回 RequestId +``` + +轮询确认升级完成: + +```bash +tccli tcr DescribeInstanceStatus --RegistryIds '[""]' --region --output json \ + --filter "RegistryStatusSet[0].Status" +# expected: "Running"(升级约 30--60 秒) +``` + +### 步骤3:查询目标地域 RegionId + +```bash +tccli tcr DescribeRegions --region ap-guangzhou --output json +``` + +**输出(截取关键条目)**: + +```json +{ + "Regions": [ + {"Alias": "gz", "RegionId": 1, "RegionName": "ap-guangzhou", "Status": "alluser"}, + {"Alias": "sh", "RegionId": 4, "RegionName": "ap-shanghai", "Status": "alluser"}, + {"Alias": "bj", "RegionId": 8, "RegionName": "ap-beijing", "Status": "alluser"} + ] +} +``` + +> 记录目标地域的 `RegionId`(整数),例如 `ap-shanghai` 对应 `4`。 + +### 步骤4:创建从实例 + +```bash +tccli tcr CreateReplicationInstance \ + --RegistryId 'tcr-nn8smeyj' \ + --ReplicationRegionId 4 \ + --region ap-guangzhou +``` + +**输出**: + +```json +{ + "ReplicationRegistryId": "tcr-nn8smeyj-4-xxxxxxxx", + "RequestId": "eac6b301-a322-493a-8e36-83b295459397" +} +``` + +> 记录返回的 `ReplicationRegistryId`(形如 `tcr-nn8smeyj-4-xxxxxxxx`),后续查询同步状态和删除均需此 ID。创建为异步操作。 + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `tcr-nn8smeyj` | 主实例 RegistryId | 已存在、premium 规格、Status=Running | `DescribeInstances` | +| `4` | 目标地域 RegionId | 数字格式,与主实例地域不同 | `DescribeRegions` 查询目标地域的 `RegionId` | + +### 步骤5:查看从实例创建进度 + +```bash +tccli tcr DescribeReplicationInstanceCreateTasks \ + --RegistryId 'tcr-nn8smeyj' \ + --ReplicationRegistryId 'tcr-nn8smeyj-4-xxxxxxxx' \ + --region ap-guangzhou --output json +``` + +**输出**: + +```json +{ + "TaskDetail": [ + { + "TaskName": "CreateReplication", + "TaskUUID": "xxxx-xxxx-xxxx", + "TaskStatus": "Succeed", + "CreatedTime": "2026-05-24T17:09:41+08:00", + "FinishedTime": "2026-05-24T17:10:11+08:00" + } + ], + "Status": "Succeed", + "RequestId": "109a280f-a11e-44d5-8741-9d3f213b06f2" +} +``` + +> 从实例创建通常约 30--60 秒完成。若 `Status` 为 `Running` 或 `InProgress`,等待后重新查询。 + +### 步骤6:查看从实例列表(确认创建) + +```bash +tccli tcr DescribeReplicationInstances --RegistryId 'tcr-nn8smeyj' --region ap-guangzhou --output json +``` + +**输出**: + +```json +{ + "TotalCount": 1, + "ReplicationRegistries": [ + { + "RegistryId": "tcr-nn8smeyj", + "ReplicationRegistryId": "tcr-nn8smeyj-4-xxxxxxxx", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai", + "Status": "Running", + "CreatedAt": "2026-05-24T17:09:41+08:00" + } + ], + "RequestId": "70342808-5071-4971-ab62-c7a0b8a9bf09" +} +``` + +> `Status: "Running"` 表示从实例创建完成,可确认下一步同步逻辑。 + +### 步骤7:查看同步状态 + +```bash +tccli tcr DescribeReplicationInstanceSyncStatus \ + --RegistryId 'tcr-nn8smeyj' \ + --ReplicationRegistryId 'tcr-nn8smeyj-4-xxxxxxxx' \ + --region ap-guangzhou --output json +``` + +**输出**: + +```json +{ + "ReplicationStatus": "Synced", + "ReplicationTime": "2026-05-24T17:09:41+08:00", + "RequestId": "2a416e2a-72e9-424d-8700-9dd5a3e7ae71" +} +``` + +> `ReplicationStatus: "Synced"` 表示主实例与从实例同步完成。若为 `Syncing`,表示同步进行中。 + +## 验证 + +### 控制面(tccli) + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 从实例已创建 | `DescribeReplicationInstances --RegistryId '' --region ` | `TotalCount >= 1`,`ReplicationRegistries` 含目标地域 | +| 从实例 Status 为 Running | 同上,检查 `ReplicationRegistries[].Status` | `"Running"` | +| 创建任务完成 | `DescribeReplicationInstanceCreateTasks` | `Status: "Succeed"` | +| 同步状态正常 | `DescribeReplicationInstanceSyncStatus` | `ReplicationStatus: "Synced"` | +| 主实例规格为 premium | `DescribeInstances --Registryids '[""]'` | `RegistryType: "premium"` | + +### 数据面 + +```bash +# 在源地域推送测试镜像至主实例 +docker tag alpine:latest //test-replication:v1 +docker push //test-replication:v1 + +# 从目标地域通过内网拉取(需目标地域有 VPC 接入) +docker pull //test-replication:v1 +# expected: 拉取成功(证明同步已完成) +``` + +> 镜像同步为自动触发,推送后约 10--30 秒可在从实例地域拉取到相同镜像。 + +## 清理 + +> **警告:删除主实例前必须删除所有从实例**,否则 `DeleteInstance` 返回错误(提示 "has N replication registry")。从实例删除不可逆。 + +### 1. 确认当前从实例列表 + +```bash +tccli tcr DescribeReplicationInstances --RegistryId 'tcr-nn8smeyj' --region ap-guangzhou --output json +# 记录所有待删除的 ReplicationRegistryId +``` + +### 2. 逐个删除从实例 + +```bash +tccli tcr DeleteReplicationInstance \ + --RegistryId 'tcr-nn8smeyj' \ + --ReplicationRegistryId 'tcr-nn8smeyj-4-xxxxxxxx' \ + --region ap-guangzhou --output json +# expected: exit 0,返回 RequestId +``` + +### 3. 验证全部删除 + +```bash +tccli tcr DescribeReplicationInstances --RegistryId 'tcr-nn8smeyj' --region ap-guangzhou --output json +# expected: TotalCount: 0, ReplicationRegistries: null +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `CreateReplicationInstance` 返回 `UnsupportedOperation` | `tccli tcr DescribeInstances --Registryids '[""]' --region --filter "Registries[0].RegistryType"` | 实例为 basic 或 standard,不支持复制实例 | `tccli tcr ModifyInstance --RegistryId --RegistryType premium --region ` 升级至 premium。升级不可降级,确认后操作 | +| `CreateReplicationInstance` 返回 `InvalidParameter` | 检查 `ReplicationRegionId` 是否为整数格式 | `ReplicationRegionId` 使用了 RegionName 字符串(如 `"ap-shanghai"`)而非数字 ID | 使用 `DescribeRegions` 查询目标地域的 `RegionId` 整数(如 ap-shanghai=4) | +| `CreateReplicationInstance` 返回 `InvalidParameter.ReplicationRegionId` | 同上 | 传入了不存在的地域 ID,或传入了与主实例相同的地域 | 使用 `DescribeRegions` 确认目标地域 `RegionId` 合法且不等于主实例所在地域 | +| `DeleteInstance` 返回错误(含 "has N replication registry") | `DescribeReplicationInstances` 查看从实例列表 | 主实例下存在未删除的从实例 | 逐个执行 `DeleteReplicationInstance`,确认 `DescribeReplicationInstances` 返回 `TotalCount: 0` 后重试 | +| `DeleteReplicationInstance` 返回 `ResourceNotFound` | 确认 `ReplicationRegistryId` 是否已被删除或拼写错误 | 从实例不存在或已删除 | 检查 `ReplicationRegistryId` 拼写(形如 `tcr-nn8smeyj-4-xxxxxxxx`) | +| `DescribeReplicationInstanceCreateTasks` 返回 `ResourceNotFound` | 确认 `ReplicationRegistryId` | 创建任务已完成清理或 ReplicationRegistryId 不正确 | 通过 `DescribeReplicationInstances` 获取正确的 `ReplicationRegistryId` | + +### 同步状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 同步状态持续 `Syncing` 超过 10 分钟 | `DescribeReplicationInstanceSyncStatus` 循环查询 | 大量镜像同步或网络延迟 | 等待完成;若持续超过 30 分钟,凭 `RequestId` [在线咨询](https://cloud.tencent.com/online-service?from=doc_1141) | +| 从实例 `Status: "Unhealthy"` | `DescribeReplicationInstances` 查看状态 | 目标地域存储或网络异常 | 若持续异常,删除后重新创建从实例 | + +## 下一步 + +- [跨实例(账号)同步镜像](../cross-instance-sync)(page_id `41945`)——跨账号独立实例同步 +- [销毁退还实例](../../delete)(page_id `51111`)——删除主实例前确保已清理从实例 +- [访问配置](../../access/network/private-access)——配置目标地域 VPC 接入以实现内网拉取 + +## 控制台替代 + +[容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 选择 premium 实例 → **同步复制** → **新建从实例**,选择目标地域和规格后确认即开始创建。 diff --git a/src/content/docs/cli/tcr/ops/image-security/deployment-block-tcrctl.md b/src/content/docs/cli/tcr/ops/image-security/deployment-block-tcrctl.md new file mode 100644 index 0000000..effe684 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-security/deployment-block-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "高危镜像部署阻断(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[高危镜像部署阻断](https://cloud.tencent.com/document/product/1141/63869) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/image-security/deployment-block.md b/src/content/docs/cli/tcr/ops/image-security/deployment-block.md new file mode 100644 index 0000000..9531595 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-security/deployment-block.md @@ -0,0 +1,369 @@ +--- +title: "实例级公网访问白名单(tccli)" +description: "· page_id `63869`" +--- + +> 对照官方:[实例公网访问白名单](https://cloud.tencent.com/document/product/1141/63869) · page_id `63869` + +## 概述 + +通过 `tccli tcr` 管理企业版实例的**公网访问白名单策略**(CIDR 安全策略)。该策略在**实例层级**生效,通过 CIDR 网段白名单控制**谁能访问 Registry 域名**(公网 `docker login` / `docker push` / `docker pull`)。 + +### 安全模型 + +TCR 实例公网访问采用 CIDR 白名单模型: + +- **`SecurityPolicySet` 为空** → 所有公网 IP 均可访问(默认开放)。 +- **`SecurityPolicySet` 非空** → 仅匹配已列入 CIDR 白名单的 IP 可访问,其余公网请求被拒绝。 +- **删除最后一条策略** → 实例恢复为对所有公网 IP 开放。 + +每条策略包含一个 CIDR 网段(`CidrBlock`)和备注(`Description`)。系统为每条策略自动分配 `PolicyIndex`(从 0 开始)和全局递增的 `PolicyVersion`。 + +> **注意:这是实例级 CIDR 访问控制,与命名空间级"高危镜像部署阻断"(`ModifyNamespace --IsPreventVUL`)是两个独立功能。** CIDR 策略控制"谁能访问 Registry"(IP 维度),部署阻断控制"哪些镜像能被拉取"(漏洞扫描结果维度)。两者互不影响,可同时配置。 + +> **警告:`CidrBlock: 0.0.0.0/0` 表示放通所有公网 IP,仅适用于测试环境。生产环境应限定为已知出口 IP 网段,避免 Registry 暴露在公网。** + +## 前置条件 + +- [环境准备](../../../index.md) +- 已完成 [创建企业版实例](../../create),实例 `Status` 为 `Running` +- 已知目标实例的 `RegistryId`(如 `tcr-dg284imq`),可通过 `DescribeInstances` 获取 +- 如使用子账号操作,需授予 `tcr:CreateSecurityPolicy`、`tcr:DescribeSecurityPolicies`、`tcr:ModifySecurityPolicy`、`tcr:DeleteSecurityPolicy`、`tcr:CreateMultipleSecurityPolicy` 权限 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 3.1.107.1 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域(如 ap-guangzhou) + +# 3. 检查 CAM 权限 — 需要以下 Action +# tcr:DescribeInstances, tcr:DescribeSecurityPolicies +# tcr:CreateSecurityPolicy, tcr:CreateMultipleSecurityPolicy +# tcr:ModifySecurityPolicy, tcr:DeleteSecurityPolicy +# 验证:执行 DescribeSecurityPolicies 确认权限(任意已有实例) +tccli tcr DescribeSecurityPolicies --RegistryId --region ap-guangzhou +# expected: exit 0,返回 SecurityPolicySet(可为空数组) + +# 4. 确认目标实例存在且 Running +tccli tcr DescribeInstances --Registryids '[""]' --region ap-guangzhou --output json \ + --filter "Registries[0].{RegistryId:RegistryId,Status:Status}" +# expected: {"RegistryId": "", "Status": "Running"} +``` + +```bash +# 5. 查询目标实例当前白名单状态(确认起点) +tccli tcr DescribeSecurityPolicies --RegistryId --region ap-guangzhou --output json +# expected: 返回 SecurityPolicySet,可能为空(表示当前对所有公网 IP 开放) +``` + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 / 参数 | 幂等 | +|-----------|------------------|:--:| +| 选择地域(控制台顶部菜单) | `--region ap-guangzhou` | 是 | +| 查看实例公网白名单 | `DescribeSecurityPolicies --RegistryId ` | 是 | +| 新增单条白名单策略 | `CreateSecurityPolicy --CidrBlock --Description <描述>` | 否(同 CIDR 可重复创建多条) | +| 批量新增白名单策略 | `CreateMultipleSecurityPolicy --SecurityGroupPolicySet '[...]'` | 否(追加,同 CIDR 可重复) | +| 修改某条策略的 CIDR / 备注 | `ModifySecurityPolicy --PolicyIndex --CidrBlock <新Cidr> --Description <新描述>` | 是(重复提交结果一致) | +| 删除某条白名单策略 | `DeleteSecurityPolicy --PolicyIndex --PolicyVersion <版本>` | 是 | + +## 关键字段说明 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | 目标企业版实例 ID,如 `tcr-dg284imq` | 填错 → `ResourceNotFound` | +| `CidrBlock` | String | 是(Create/Modify) | 合法 CIDR 网段,如 `10.0.0.0/8`、`0.0.0.0/0`、`192.168.1.0/24` | 格式非法 → `InvalidParameter` | +| `Description` | String | 是(Create/Modify) | 策略备注,如 `"内网访问"` | — | +| `PolicyIndex` | Integer | 是(Modify/Delete) | 系统自动分配,从 0 起。删除后剩余策略会被**重新编号(紧凑化)**,不可视为稳定 ID | 引用已删除的 Index → `InvalidParameter.Range` | +| `PolicyVersion` | String | 删除时必填 | 全局递增版本号,每次增删改均自增。删除时需传入**当前最新版本**,可通过 `DescribeSecurityPolicies` 获取 | 缺失 → `InvalidParameterValue`(参数 `.SecurityGroupPolicySet.Version` 值为空) | +| `SecurityGroupPolicySet` | Array | 是(CreateMultiple) | JSON 数组,每项含 `CidrBlock`、`Description`(`PolicyIndex`/`PolicyVersion` 可省略,由系统分配) | 格式错误 → `InvalidParameter` | + +## 操作步骤 + +### 步骤1:新增单条白名单策略 + +为实例添加一条 CIDR 白名单。`PolicyIndex` 由系统自动分配,无需(也不应)手动指定: + +```bash +tccli tcr CreateSecurityPolicy \ + --RegistryId \ + --CidrBlock 0.0.0.0/0 \ + --Description "" \ + --region +# expected: exit 0,返回 RegistryId 与 RequestId +``` + +**输出**: + +```json +{ + "RegistryId": "tcr-dg284imq", + "RequestId": "e432eb07-bd4c-477d-ad50-681b5613c61a" +} +``` + +> `CreateSecurityPolicy` 仅返回 `RegistryId`,**不返回 `PolicyIndex`**。新增策略的 `PolicyIndex` 需通过步骤3 `DescribeSecurityPolicies` 查询获取。 + +> 一旦策略列表非空,仅匹配 CIDR 的 IP 可访问 Registry。新增 `0.0.0.0/0` 等价于"放通所有公网 IP",仅用于测试。 + +### 步骤2:批量新增多条白名单策略 + +一次追加多条策略。`CreateMultipleSecurityPolicy` 为**追加**语义,不会清空已有策略: + +```bash +tccli tcr CreateMultipleSecurityPolicy \ + --RegistryId \ + --SecurityGroupPolicySet '[{"CidrBlock":"10.0.0.0/8","Description":"内网访问"},{"CidrBlock":"0.0.0.0/0","Description":"公网访问"}]' \ + --region +# expected: exit 0,返回 RegistryId 与 RequestId +``` + +**输出**: + +```json +{ + "RegistryId": "tcr-dg284imq", + "RequestId": "244ca1a7-48d4-4193-a67a-c8122f297d9f" +} +``` + +> 注意参数名为 `--SecurityGroupPolicySet`(非 `PolicyGroup`)。新策略的 `PolicyIndex` 接在已有列表末尾递增分配。如需"替换全部策略",应先逐条 `DeleteSecurityPolicy` 清空,再 `CreateMultipleSecurityPolicy`。 + +### 步骤3:查询白名单策略列表 + +查询实例当前所有白名单策略,获取每条的 `PolicyIndex` 与 `PolicyVersion`(修改、删除均需引用): + +```bash +tccli tcr DescribeSecurityPolicies \ + --RegistryId \ + --region \ + --output json +# expected: exit 0,返回 SecurityPolicySet 数组 +``` + +**输出**: + +```json +{ + "SecurityPolicySet": [ + { + "PolicyIndex": 0, + "Description": "测试放通所有公网访问", + "CidrBlock": "0.0.0.0/0", + "PolicyVersion": "1" + }, + { + "PolicyIndex": 1, + "Description": "内网访问", + "CidrBlock": "10.0.0.0/8", + "PolicyVersion": "1" + } + ], + "RequestId": "a6cfa802-3d03-45c2-86bb-0dcac320d6ea" +} +``` + +- `SecurityPolicySet` 为空数组 `[]` 表示当前实例对所有公网 IP 开放。 +- `PolicyVersion` 为全局版本号,所有策略共享同一值,每次增删改后自增。删除策略时需传入**当前查询到的最新版本**。 + +仅查看 CIDR 与备注(精简输出): + +```bash +tccli tcr DescribeSecurityPolicies \ + --RegistryId \ + --region \ + --output json \ + --filter "SecurityPolicySet[*].{Index:PolicyIndex,CIDR:CidrBlock,Desc:Description,Version:PolicyVersion}" +# expected: 返回精简数组 +``` + +**输出**: + +```json +[ + { + "Index": 0, + "CIDR": "0.0.0.0/0", + "Desc": "测试放通所有公网访问", + "Version": "1" + }, + { + "Index": 1, + "CIDR": "10.0.0.0/8", + "Desc": "内网访问", + "Version": "1" + } +] +``` + +### 步骤4:修改单条策略 + +修改指定 `PolicyIndex` 的 CIDR 与备注。`ModifySecurityPolicy` **仅支持修改 `CidrBlock` 和 `Description`**,不能改 `PolicyIndex`,且两者均为必填: + +```bash +tccli tcr ModifySecurityPolicy \ + --RegistryId \ + --PolicyIndex \ + --CidrBlock \ + --Description \ + --region +# expected: exit 0,返回 RegistryId 与 RequestId +``` + +**输出**: + +```json +{ + "RegistryId": "tcr-dg284imq", + "RequestId": "de364645-0e1b-4070-a0f4-201cb50de233" +} +``` + +示例:将 `PolicyIndex 1` 的网段从 `10.0.0.0/8` 收窄为 `10.1.0.0/16`: + +```bash +tccli tcr ModifySecurityPolicy \ + --RegistryId \ + --PolicyIndex 1 \ + --CidrBlock 10.1.0.0/16 \ + --Description "内网访问-收窄" \ + --region +# expected: exit 0,返回 RegistryId 与 RequestId +``` + +> 修改后 `PolicyVersion` 自增。后续若需删除该策略,应重新 `DescribeSecurityPolicies` 获取最新 `PolicyVersion`。 + +### 步骤5:删除单条策略 + +删除指定策略。**`PolicyVersion` 在实践中为必填**(help 标注 Optional,但省略会报 `InvalidParameterValue`),需先通过步骤3查询获取: + +```bash +tccli tcr DeleteSecurityPolicy \ + --RegistryId \ + --PolicyIndex \ + --PolicyVersion \ + --region +# expected: exit 0,返回 RegistryId 与 RequestId +``` + +**输出**: + +```json +{ + "RegistryId": "tcr-dg284imq", + "RequestId": "308892a9-ea4e-4231-852d-b082a3416c9e" +} +``` + +> **删除后 `PolicyIndex` 会被紧凑化重排**:若原有索引 0、1、2,删除索引 1 后,原索引 2 的策略会被重编为索引 1。因此删除操作后,务必重新 `DescribeSecurityPolicies` 获取最新索引,再执行下一次按索引的操作。 + +> **删除最后一条策略会使实例恢复为对所有公网 IP 开放**。生产环境清理前确认是否需要保留至少一条限定 CIDR 的策略。 + +## 验证 + +### 控制面(tccli) + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 策略列表已更新 | `DescribeSecurityPolicies --RegistryId --region ` | `SecurityPolicySet` 含新增/修改后的条目 | +| 新增策略已生效 | 同上,`--filter "SecurityPolicySet[?CidrBlock=='']"` | 返回非空数组,含目标 CIDR | +| 修改后 CIDR 正确 | 同上,`--filter "SecurityPolicySet[?PolicyIndex==].CidrBlock"` | 与 `ModifySecurityPolicy` 传入值一致 | +| 删除后策略已移除 | 同上,`--filter "SecurityPolicySet[?PolicyIndex==]"` | 返回空数组 `[]` | +| PolicyVersion 已自增 | 同上,`--filter "SecurityPolicySet[0].PolicyVersion"` | 较操作前数值增大 | +| 全部清空后状态 | 同上,`--filter "SecurityPolicySet"` | `[]`(表示对所有公网 IP 开放) | + +验证示例(确认修改后某条策略的 CIDR): + +```bash +tccli tcr DescribeSecurityPolicies \ + --RegistryId \ + --region \ + --output json \ + --filter "SecurityPolicySet[?PolicyIndex==\`1\`].{CidrBlock:CidrBlock,Description:Description}" +# expected: 返回 PolicyIndex 1 的当前 CIDR 与备注 +``` + +## 清理 + +> **警告:删除白名单策略会立即生效。若删除后列表为空,实例将恢复为对所有公网 IP 开放,可能暴露 Registry。** + +### 1. 清理前状态检查 + +```bash +tccli tcr DescribeSecurityPolicies \ + --RegistryId \ + --region \ + --output json +# expected: 确认当前策略条目数与 CIDR,记录待删除条目的 PolicyIndex 与 PolicyVersion +``` + +### 2. 逐条删除测试策略 + +按 `PolicyIndex` 从大到小逐条删除,避免删除导致的重排影响后续索引引用: + +```bash +# 先删除高索引,再删低索引(每删一条后重新查询最新索引更稳妥) +tccli tcr DeleteSecurityPolicy \ + --RegistryId \ + --PolicyIndex \ + --PolicyVersion \ + --region \ + --output json +# expected: exit 0,返回 RegistryId 与 RequestId +``` + +> 由于删除后 `PolicyIndex` 会紧凑化重排,**每次删除前都应重新 `DescribeSecurityPolicies` 获取最新索引与版本**,不要沿用旧索引连续删除。 + +### 3. 验证清理结果 + +```bash +tccli tcr DescribeSecurityPolicies \ + --RegistryId \ + --region \ + --output json +# expected: SecurityPolicySet 为空数组 [](实例恢复对所有公网 IP 开放) +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `DescribeSecurityPolicies` 返回 `ResourceNotFound`,提示 "Failed to get security group id from registry" | `tccli tcr DescribeInstances --Registryids '[""]' --region ` 确认实例存在 | `RegistryId` 填错或实例不在该 `--region` | 核对 `RegistryId` 与 `--region`,确保实例在该地域且 `Status: Running` | +| `CreateSecurityPolicy` 返回 `InvalidParameter`,CidrBlock 相关 | 检查 `--CidrBlock` 是否为合法 CIDR | 网段格式错误(如缺少掩码、掩码越界) | 使用合法 CIDR,如 `10.0.0.0/8`、`192.168.1.0/24`、`0.0.0.0/0` | +| `CreateSecurityPolicy` 返回 `UnauthorizedOperation` | `tccli tcr DescribeSecurityPolicies --RegistryId --region ` 验证权限 | 子账号缺少 `tcr:CreateSecurityPolicy` 权限 | 联系主账号授予 `QcloudTCRFullAccess` 或最小权限策略(含 `tcr:CreateSecurityPolicy`) | +| `CreateMultipleSecurityPolicy` 返回 `InvalidParameter`,提示 SecurityGroupPolicySet 相关 | 检查 JSON 数组格式与字段名 | 误用 `--PolicyGroup` 参数名,或 JSON 格式错误 | 参数名为 `--SecurityGroupPolicySet`,值为 JSON 数组,每项含 `CidrBlock`、`Description` | +| `ModifySecurityPolicy` 返回 `MissingParameter`,提示 CidrBlock 或 Description | 检查命令参数 | `ModifySecurityPolicy` 中 `CidrBlock` 与 `Description` 均为必填,缺一报错 | 同时提供 `--CidrBlock` 与 `--Description`,即使只想改其一也要传入原值 | +| `ModifySecurityPolicy` 返回 `InvalidParameter`,提示 PolicyIndex 相关 | `tccli tcr DescribeSecurityPolicies --RegistryId --region ` 查询有效索引 | `PolicyIndex` 不存在(已被删除或重排) | 重新查询获取最新 `PolicyIndex` 后再修改 | +| `DeleteSecurityPolicy` 返回 `InvalidParameterValue`,提示 "参数 `.SecurityGroupPolicySet.Version` 值为空" | 检查是否传入了 `--PolicyVersion` | `PolicyVersion` 在实践中为必填,省略报错 | 先 `DescribeSecurityPolicies` 获取当前 `PolicyVersion`,删除时一并传入 | +| `DeleteSecurityPolicy` 返回 `InvalidParameter.Range`,提示 PolicyIndex 超出范围 | `tccli tcr DescribeSecurityPolicies --RegistryId --region ` 查询当前最大索引 | 引用了已不存在的 `PolicyIndex`(删除后索引被紧凑化重排) | 重新查询最新索引,引用当前列表中实际存在的 `PolicyIndex` | +| 删除最后一条策略后 Registry 无法访问 | `tccli tcr DescribeSecurityPolicies --RegistryId --region ` 确认列表为空 | 列表为空 = 对所有公网 IP 开放,但客户端可能来自未放通的 IP(此为误解,空列表应为全开放) | 空列表即全开放;若仍无法访问,检查实例 `Status`、CAM 权限或网络 ACL,与本策略无关 | +| 策略已配置但仍可从任意 IP 访问 | `tccli tcr DescribeSecurityPolicies` 检查是否含 `0.0.0.0/0` | 白名单中存在 `0.0.0.0/0`,等于放通所有 IP | 删除或收窄 `0.0.0.0/0` 策略,仅保留限定 CIDR | + +### 常见误操作预防 + +| 场景 | 预防措施 | +|------|---------| +| 误以为 `CreateSecurityPolicy` 返回 `PolicyIndex` | 该命令仅返回 `RegistryId` + `RequestId`,`PolicyIndex` 需 `DescribeSecurityPolicies` 查询 | +| 误以为 `CreateMultipleSecurityPolicy` 会替换全部策略 | 该命令为**追加**语义,不清空已有策略。需"替换全部"时先逐条删除再批量创建 | +| 引用旧 `PolicyIndex` 连续删除导致失败 | 删除后索引会紧凑化重排,每删一条都重新查询最新索引 | +| 漏传 `--PolicyVersion` 导致删除失败 | `PolicyVersion` 实践中必填,删除前先查询当前版本 | +| 删除最后一条策略使 Registry 公网全开放 | 生产环境保留至少一条限定 CIDR 的策略,清理前确认后果 | +| `0.0.0.0/0` 留在生产白名单 | 仅测试环境使用,生产环境应限定为已知出口 IP 网段 | + +## 下一步 + +- [创建企业版实例](../../create)(page_id `51110`) — 创建实例以配置白名单 +- [容器镜像安全扫描](../vulnerability-scan) — 镜像漏洞扫描(与访问控制独立) +- [容器镜像签名](../image-signing) — 镜像内容完整性校验 +- [环境准备](../index.md) — 返回 TCR 工具链入口 + +## 控制台替代 + +[容器镜像服务 → 实例管理 → 选择实例 → 网络安全](https://console.cloud.tencent.com/tcr/instance):选择地域与目标实例,进入"网络安全"页签,查看公网访问白名单列表。单击"新建"添加 CIDR 策略(填写网段与备注),单击已有策略行的"编辑"修改 CIDR/备注,单击"删除"移除策略。删除最后一条策略后实例恢复为对所有公网 IP 开放。 diff --git a/src/content/docs/cli/tcr/ops/image-security/deployment-block/deployment-block-tcrctl.md b/src/content/docs/cli/tcr/ops/image-security/deployment-block/deployment-block-tcrctl.md new file mode 100644 index 0000000..effe684 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-security/deployment-block/deployment-block-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "高危镜像部署阻断(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[高危镜像部署阻断](https://cloud.tencent.com/document/product/1141/63869) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/image-security/deployment-block/deployment-block.md b/src/content/docs/cli/tcr/ops/image-security/deployment-block/deployment-block.md new file mode 100644 index 0000000..9531595 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-security/deployment-block/deployment-block.md @@ -0,0 +1,369 @@ +--- +title: "实例级公网访问白名单(tccli)" +description: "· page_id `63869`" +--- + +> 对照官方:[实例公网访问白名单](https://cloud.tencent.com/document/product/1141/63869) · page_id `63869` + +## 概述 + +通过 `tccli tcr` 管理企业版实例的**公网访问白名单策略**(CIDR 安全策略)。该策略在**实例层级**生效,通过 CIDR 网段白名单控制**谁能访问 Registry 域名**(公网 `docker login` / `docker push` / `docker pull`)。 + +### 安全模型 + +TCR 实例公网访问采用 CIDR 白名单模型: + +- **`SecurityPolicySet` 为空** → 所有公网 IP 均可访问(默认开放)。 +- **`SecurityPolicySet` 非空** → 仅匹配已列入 CIDR 白名单的 IP 可访问,其余公网请求被拒绝。 +- **删除最后一条策略** → 实例恢复为对所有公网 IP 开放。 + +每条策略包含一个 CIDR 网段(`CidrBlock`)和备注(`Description`)。系统为每条策略自动分配 `PolicyIndex`(从 0 开始)和全局递增的 `PolicyVersion`。 + +> **注意:这是实例级 CIDR 访问控制,与命名空间级"高危镜像部署阻断"(`ModifyNamespace --IsPreventVUL`)是两个独立功能。** CIDR 策略控制"谁能访问 Registry"(IP 维度),部署阻断控制"哪些镜像能被拉取"(漏洞扫描结果维度)。两者互不影响,可同时配置。 + +> **警告:`CidrBlock: 0.0.0.0/0` 表示放通所有公网 IP,仅适用于测试环境。生产环境应限定为已知出口 IP 网段,避免 Registry 暴露在公网。** + +## 前置条件 + +- [环境准备](../../../index.md) +- 已完成 [创建企业版实例](../../create),实例 `Status` 为 `Running` +- 已知目标实例的 `RegistryId`(如 `tcr-dg284imq`),可通过 `DescribeInstances` 获取 +- 如使用子账号操作,需授予 `tcr:CreateSecurityPolicy`、`tcr:DescribeSecurityPolicies`、`tcr:ModifySecurityPolicy`、`tcr:DeleteSecurityPolicy`、`tcr:CreateMultipleSecurityPolicy` 权限 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 3.1.107.1 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域(如 ap-guangzhou) + +# 3. 检查 CAM 权限 — 需要以下 Action +# tcr:DescribeInstances, tcr:DescribeSecurityPolicies +# tcr:CreateSecurityPolicy, tcr:CreateMultipleSecurityPolicy +# tcr:ModifySecurityPolicy, tcr:DeleteSecurityPolicy +# 验证:执行 DescribeSecurityPolicies 确认权限(任意已有实例) +tccli tcr DescribeSecurityPolicies --RegistryId --region ap-guangzhou +# expected: exit 0,返回 SecurityPolicySet(可为空数组) + +# 4. 确认目标实例存在且 Running +tccli tcr DescribeInstances --Registryids '[""]' --region ap-guangzhou --output json \ + --filter "Registries[0].{RegistryId:RegistryId,Status:Status}" +# expected: {"RegistryId": "", "Status": "Running"} +``` + +```bash +# 5. 查询目标实例当前白名单状态(确认起点) +tccli tcr DescribeSecurityPolicies --RegistryId --region ap-guangzhou --output json +# expected: 返回 SecurityPolicySet,可能为空(表示当前对所有公网 IP 开放) +``` + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 / 参数 | 幂等 | +|-----------|------------------|:--:| +| 选择地域(控制台顶部菜单) | `--region ap-guangzhou` | 是 | +| 查看实例公网白名单 | `DescribeSecurityPolicies --RegistryId ` | 是 | +| 新增单条白名单策略 | `CreateSecurityPolicy --CidrBlock --Description <描述>` | 否(同 CIDR 可重复创建多条) | +| 批量新增白名单策略 | `CreateMultipleSecurityPolicy --SecurityGroupPolicySet '[...]'` | 否(追加,同 CIDR 可重复) | +| 修改某条策略的 CIDR / 备注 | `ModifySecurityPolicy --PolicyIndex --CidrBlock <新Cidr> --Description <新描述>` | 是(重复提交结果一致) | +| 删除某条白名单策略 | `DeleteSecurityPolicy --PolicyIndex --PolicyVersion <版本>` | 是 | + +## 关键字段说明 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | 目标企业版实例 ID,如 `tcr-dg284imq` | 填错 → `ResourceNotFound` | +| `CidrBlock` | String | 是(Create/Modify) | 合法 CIDR 网段,如 `10.0.0.0/8`、`0.0.0.0/0`、`192.168.1.0/24` | 格式非法 → `InvalidParameter` | +| `Description` | String | 是(Create/Modify) | 策略备注,如 `"内网访问"` | — | +| `PolicyIndex` | Integer | 是(Modify/Delete) | 系统自动分配,从 0 起。删除后剩余策略会被**重新编号(紧凑化)**,不可视为稳定 ID | 引用已删除的 Index → `InvalidParameter.Range` | +| `PolicyVersion` | String | 删除时必填 | 全局递增版本号,每次增删改均自增。删除时需传入**当前最新版本**,可通过 `DescribeSecurityPolicies` 获取 | 缺失 → `InvalidParameterValue`(参数 `.SecurityGroupPolicySet.Version` 值为空) | +| `SecurityGroupPolicySet` | Array | 是(CreateMultiple) | JSON 数组,每项含 `CidrBlock`、`Description`(`PolicyIndex`/`PolicyVersion` 可省略,由系统分配) | 格式错误 → `InvalidParameter` | + +## 操作步骤 + +### 步骤1:新增单条白名单策略 + +为实例添加一条 CIDR 白名单。`PolicyIndex` 由系统自动分配,无需(也不应)手动指定: + +```bash +tccli tcr CreateSecurityPolicy \ + --RegistryId \ + --CidrBlock 0.0.0.0/0 \ + --Description "" \ + --region +# expected: exit 0,返回 RegistryId 与 RequestId +``` + +**输出**: + +```json +{ + "RegistryId": "tcr-dg284imq", + "RequestId": "e432eb07-bd4c-477d-ad50-681b5613c61a" +} +``` + +> `CreateSecurityPolicy` 仅返回 `RegistryId`,**不返回 `PolicyIndex`**。新增策略的 `PolicyIndex` 需通过步骤3 `DescribeSecurityPolicies` 查询获取。 + +> 一旦策略列表非空,仅匹配 CIDR 的 IP 可访问 Registry。新增 `0.0.0.0/0` 等价于"放通所有公网 IP",仅用于测试。 + +### 步骤2:批量新增多条白名单策略 + +一次追加多条策略。`CreateMultipleSecurityPolicy` 为**追加**语义,不会清空已有策略: + +```bash +tccli tcr CreateMultipleSecurityPolicy \ + --RegistryId \ + --SecurityGroupPolicySet '[{"CidrBlock":"10.0.0.0/8","Description":"内网访问"},{"CidrBlock":"0.0.0.0/0","Description":"公网访问"}]' \ + --region +# expected: exit 0,返回 RegistryId 与 RequestId +``` + +**输出**: + +```json +{ + "RegistryId": "tcr-dg284imq", + "RequestId": "244ca1a7-48d4-4193-a67a-c8122f297d9f" +} +``` + +> 注意参数名为 `--SecurityGroupPolicySet`(非 `PolicyGroup`)。新策略的 `PolicyIndex` 接在已有列表末尾递增分配。如需"替换全部策略",应先逐条 `DeleteSecurityPolicy` 清空,再 `CreateMultipleSecurityPolicy`。 + +### 步骤3:查询白名单策略列表 + +查询实例当前所有白名单策略,获取每条的 `PolicyIndex` 与 `PolicyVersion`(修改、删除均需引用): + +```bash +tccli tcr DescribeSecurityPolicies \ + --RegistryId \ + --region \ + --output json +# expected: exit 0,返回 SecurityPolicySet 数组 +``` + +**输出**: + +```json +{ + "SecurityPolicySet": [ + { + "PolicyIndex": 0, + "Description": "测试放通所有公网访问", + "CidrBlock": "0.0.0.0/0", + "PolicyVersion": "1" + }, + { + "PolicyIndex": 1, + "Description": "内网访问", + "CidrBlock": "10.0.0.0/8", + "PolicyVersion": "1" + } + ], + "RequestId": "a6cfa802-3d03-45c2-86bb-0dcac320d6ea" +} +``` + +- `SecurityPolicySet` 为空数组 `[]` 表示当前实例对所有公网 IP 开放。 +- `PolicyVersion` 为全局版本号,所有策略共享同一值,每次增删改后自增。删除策略时需传入**当前查询到的最新版本**。 + +仅查看 CIDR 与备注(精简输出): + +```bash +tccli tcr DescribeSecurityPolicies \ + --RegistryId \ + --region \ + --output json \ + --filter "SecurityPolicySet[*].{Index:PolicyIndex,CIDR:CidrBlock,Desc:Description,Version:PolicyVersion}" +# expected: 返回精简数组 +``` + +**输出**: + +```json +[ + { + "Index": 0, + "CIDR": "0.0.0.0/0", + "Desc": "测试放通所有公网访问", + "Version": "1" + }, + { + "Index": 1, + "CIDR": "10.0.0.0/8", + "Desc": "内网访问", + "Version": "1" + } +] +``` + +### 步骤4:修改单条策略 + +修改指定 `PolicyIndex` 的 CIDR 与备注。`ModifySecurityPolicy` **仅支持修改 `CidrBlock` 和 `Description`**,不能改 `PolicyIndex`,且两者均为必填: + +```bash +tccli tcr ModifySecurityPolicy \ + --RegistryId \ + --PolicyIndex \ + --CidrBlock \ + --Description \ + --region +# expected: exit 0,返回 RegistryId 与 RequestId +``` + +**输出**: + +```json +{ + "RegistryId": "tcr-dg284imq", + "RequestId": "de364645-0e1b-4070-a0f4-201cb50de233" +} +``` + +示例:将 `PolicyIndex 1` 的网段从 `10.0.0.0/8` 收窄为 `10.1.0.0/16`: + +```bash +tccli tcr ModifySecurityPolicy \ + --RegistryId \ + --PolicyIndex 1 \ + --CidrBlock 10.1.0.0/16 \ + --Description "内网访问-收窄" \ + --region +# expected: exit 0,返回 RegistryId 与 RequestId +``` + +> 修改后 `PolicyVersion` 自增。后续若需删除该策略,应重新 `DescribeSecurityPolicies` 获取最新 `PolicyVersion`。 + +### 步骤5:删除单条策略 + +删除指定策略。**`PolicyVersion` 在实践中为必填**(help 标注 Optional,但省略会报 `InvalidParameterValue`),需先通过步骤3查询获取: + +```bash +tccli tcr DeleteSecurityPolicy \ + --RegistryId \ + --PolicyIndex \ + --PolicyVersion \ + --region +# expected: exit 0,返回 RegistryId 与 RequestId +``` + +**输出**: + +```json +{ + "RegistryId": "tcr-dg284imq", + "RequestId": "308892a9-ea4e-4231-852d-b082a3416c9e" +} +``` + +> **删除后 `PolicyIndex` 会被紧凑化重排**:若原有索引 0、1、2,删除索引 1 后,原索引 2 的策略会被重编为索引 1。因此删除操作后,务必重新 `DescribeSecurityPolicies` 获取最新索引,再执行下一次按索引的操作。 + +> **删除最后一条策略会使实例恢复为对所有公网 IP 开放**。生产环境清理前确认是否需要保留至少一条限定 CIDR 的策略。 + +## 验证 + +### 控制面(tccli) + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 策略列表已更新 | `DescribeSecurityPolicies --RegistryId --region ` | `SecurityPolicySet` 含新增/修改后的条目 | +| 新增策略已生效 | 同上,`--filter "SecurityPolicySet[?CidrBlock=='']"` | 返回非空数组,含目标 CIDR | +| 修改后 CIDR 正确 | 同上,`--filter "SecurityPolicySet[?PolicyIndex==].CidrBlock"` | 与 `ModifySecurityPolicy` 传入值一致 | +| 删除后策略已移除 | 同上,`--filter "SecurityPolicySet[?PolicyIndex==]"` | 返回空数组 `[]` | +| PolicyVersion 已自增 | 同上,`--filter "SecurityPolicySet[0].PolicyVersion"` | 较操作前数值增大 | +| 全部清空后状态 | 同上,`--filter "SecurityPolicySet"` | `[]`(表示对所有公网 IP 开放) | + +验证示例(确认修改后某条策略的 CIDR): + +```bash +tccli tcr DescribeSecurityPolicies \ + --RegistryId \ + --region \ + --output json \ + --filter "SecurityPolicySet[?PolicyIndex==\`1\`].{CidrBlock:CidrBlock,Description:Description}" +# expected: 返回 PolicyIndex 1 的当前 CIDR 与备注 +``` + +## 清理 + +> **警告:删除白名单策略会立即生效。若删除后列表为空,实例将恢复为对所有公网 IP 开放,可能暴露 Registry。** + +### 1. 清理前状态检查 + +```bash +tccli tcr DescribeSecurityPolicies \ + --RegistryId \ + --region \ + --output json +# expected: 确认当前策略条目数与 CIDR,记录待删除条目的 PolicyIndex 与 PolicyVersion +``` + +### 2. 逐条删除测试策略 + +按 `PolicyIndex` 从大到小逐条删除,避免删除导致的重排影响后续索引引用: + +```bash +# 先删除高索引,再删低索引(每删一条后重新查询最新索引更稳妥) +tccli tcr DeleteSecurityPolicy \ + --RegistryId \ + --PolicyIndex \ + --PolicyVersion \ + --region \ + --output json +# expected: exit 0,返回 RegistryId 与 RequestId +``` + +> 由于删除后 `PolicyIndex` 会紧凑化重排,**每次删除前都应重新 `DescribeSecurityPolicies` 获取最新索引与版本**,不要沿用旧索引连续删除。 + +### 3. 验证清理结果 + +```bash +tccli tcr DescribeSecurityPolicies \ + --RegistryId \ + --region \ + --output json +# expected: SecurityPolicySet 为空数组 [](实例恢复对所有公网 IP 开放) +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `DescribeSecurityPolicies` 返回 `ResourceNotFound`,提示 "Failed to get security group id from registry" | `tccli tcr DescribeInstances --Registryids '[""]' --region ` 确认实例存在 | `RegistryId` 填错或实例不在该 `--region` | 核对 `RegistryId` 与 `--region`,确保实例在该地域且 `Status: Running` | +| `CreateSecurityPolicy` 返回 `InvalidParameter`,CidrBlock 相关 | 检查 `--CidrBlock` 是否为合法 CIDR | 网段格式错误(如缺少掩码、掩码越界) | 使用合法 CIDR,如 `10.0.0.0/8`、`192.168.1.0/24`、`0.0.0.0/0` | +| `CreateSecurityPolicy` 返回 `UnauthorizedOperation` | `tccli tcr DescribeSecurityPolicies --RegistryId --region ` 验证权限 | 子账号缺少 `tcr:CreateSecurityPolicy` 权限 | 联系主账号授予 `QcloudTCRFullAccess` 或最小权限策略(含 `tcr:CreateSecurityPolicy`) | +| `CreateMultipleSecurityPolicy` 返回 `InvalidParameter`,提示 SecurityGroupPolicySet 相关 | 检查 JSON 数组格式与字段名 | 误用 `--PolicyGroup` 参数名,或 JSON 格式错误 | 参数名为 `--SecurityGroupPolicySet`,值为 JSON 数组,每项含 `CidrBlock`、`Description` | +| `ModifySecurityPolicy` 返回 `MissingParameter`,提示 CidrBlock 或 Description | 检查命令参数 | `ModifySecurityPolicy` 中 `CidrBlock` 与 `Description` 均为必填,缺一报错 | 同时提供 `--CidrBlock` 与 `--Description`,即使只想改其一也要传入原值 | +| `ModifySecurityPolicy` 返回 `InvalidParameter`,提示 PolicyIndex 相关 | `tccli tcr DescribeSecurityPolicies --RegistryId --region ` 查询有效索引 | `PolicyIndex` 不存在(已被删除或重排) | 重新查询获取最新 `PolicyIndex` 后再修改 | +| `DeleteSecurityPolicy` 返回 `InvalidParameterValue`,提示 "参数 `.SecurityGroupPolicySet.Version` 值为空" | 检查是否传入了 `--PolicyVersion` | `PolicyVersion` 在实践中为必填,省略报错 | 先 `DescribeSecurityPolicies` 获取当前 `PolicyVersion`,删除时一并传入 | +| `DeleteSecurityPolicy` 返回 `InvalidParameter.Range`,提示 PolicyIndex 超出范围 | `tccli tcr DescribeSecurityPolicies --RegistryId --region ` 查询当前最大索引 | 引用了已不存在的 `PolicyIndex`(删除后索引被紧凑化重排) | 重新查询最新索引,引用当前列表中实际存在的 `PolicyIndex` | +| 删除最后一条策略后 Registry 无法访问 | `tccli tcr DescribeSecurityPolicies --RegistryId --region ` 确认列表为空 | 列表为空 = 对所有公网 IP 开放,但客户端可能来自未放通的 IP(此为误解,空列表应为全开放) | 空列表即全开放;若仍无法访问,检查实例 `Status`、CAM 权限或网络 ACL,与本策略无关 | +| 策略已配置但仍可从任意 IP 访问 | `tccli tcr DescribeSecurityPolicies` 检查是否含 `0.0.0.0/0` | 白名单中存在 `0.0.0.0/0`,等于放通所有 IP | 删除或收窄 `0.0.0.0/0` 策略,仅保留限定 CIDR | + +### 常见误操作预防 + +| 场景 | 预防措施 | +|------|---------| +| 误以为 `CreateSecurityPolicy` 返回 `PolicyIndex` | 该命令仅返回 `RegistryId` + `RequestId`,`PolicyIndex` 需 `DescribeSecurityPolicies` 查询 | +| 误以为 `CreateMultipleSecurityPolicy` 会替换全部策略 | 该命令为**追加**语义,不清空已有策略。需"替换全部"时先逐条删除再批量创建 | +| 引用旧 `PolicyIndex` 连续删除导致失败 | 删除后索引会紧凑化重排,每删一条都重新查询最新索引 | +| 漏传 `--PolicyVersion` 导致删除失败 | `PolicyVersion` 实践中必填,删除前先查询当前版本 | +| 删除最后一条策略使 Registry 公网全开放 | 生产环境保留至少一条限定 CIDR 的策略,清理前确认后果 | +| `0.0.0.0/0` 留在生产白名单 | 仅测试环境使用,生产环境应限定为已知出口 IP 网段 | + +## 下一步 + +- [创建企业版实例](../../create)(page_id `51110`) — 创建实例以配置白名单 +- [容器镜像安全扫描](../vulnerability-scan) — 镜像漏洞扫描(与访问控制独立) +- [容器镜像签名](../image-signing) — 镜像内容完整性校验 +- [环境准备](../index.md) — 返回 TCR 工具链入口 + +## 控制台替代 + +[容器镜像服务 → 实例管理 → 选择实例 → 网络安全](https://console.cloud.tencent.com/tcr/instance):选择地域与目标实例,进入"网络安全"页签,查看公网访问白名单列表。单击"新建"添加 CIDR 策略(填写网段与备注),单击已有策略行的"编辑"修改 CIDR/备注,单击"删除"移除策略。删除最后一条策略后实例恢复为对所有公网 IP 开放。 diff --git a/src/content/docs/cli/tcr/ops/image-security/deployment-block/examples/ns-cve-whitelist.json b/src/content/docs/cli/tcr/ops/image-security/deployment-block/examples/ns-cve-whitelist.json new file mode 100644 index 0000000..232db20 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-security/deployment-block/examples/ns-cve-whitelist.json @@ -0,0 +1,10 @@ +{ + "RegistryId": "tcr-example", + "NamespaceName": "production", + "IsPreventVUL": true, + "Severity": "high", + "CVEWhitelistItems": [ + {"CVEID": "CVE-2024-41110"}, + {"CVEID": "CVE-2024-24557"} + ] +} diff --git a/src/content/docs/cli/tcr/ops/image-security/deployment-block/examples/security-policies-create.json b/src/content/docs/cli/tcr/ops/image-security/deployment-block/examples/security-policies-create.json new file mode 100644 index 0000000..c49d4b7 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-security/deployment-block/examples/security-policies-create.json @@ -0,0 +1,13 @@ +{ + "RegistryId": "tcr-example", + "SecurityGroupPolicySet": [ + { + "CidrBlock": "10.100.0.0/16", + "Description": "测试集群 VPC" + }, + { + "CidrBlock": "203.0.113.0/24", + "Description": "CI/CD 出口 IP" + } + ] +} diff --git a/src/content/docs/cli/tcr/ops/image-security/deployment-block/examples/security-policies-delete.json b/src/content/docs/cli/tcr/ops/image-security/deployment-block/examples/security-policies-delete.json new file mode 100644 index 0000000..f021a55 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-security/deployment-block/examples/security-policies-delete.json @@ -0,0 +1,17 @@ +{ + "RegistryId": "tcr-example", + "SecurityGroupPolicySet": [ + { + "PolicyIndex": 2, + "Description": "测试集群 VPC", + "CidrBlock": "10.200.0.0/16", + "PolicyVersion": "" + }, + { + "PolicyIndex": 1, + "Description": "办公室网络", + "CidrBlock": "172.16.0.0/12", + "PolicyVersion": "" + } + ] +} diff --git a/src/content/docs/cli/tcr/ops/image-security/image-signing-tcrctl.md b/src/content/docs/cli/tcr/ops/image-security/image-signing-tcrctl.md new file mode 100644 index 0000000..cd92205 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-security/image-signing-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "容器镜像签名(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[容器镜像签名](https://cloud.tencent.com/document/product/1141/80862) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/image-security/image-signing.md b/src/content/docs/cli/tcr/ops/image-security/image-signing.md new file mode 100644 index 0000000..b320670 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-security/image-signing.md @@ -0,0 +1,440 @@ +--- +title: "容器镜像签名(tccli)" +description: "· page_id `80862`" +--- + +> 对照官方:[容器镜像签名](https://cloud.tencent.com/document/product/1141/80862) · page_id `80862` + +## 概述 + +通过 `tccli tcr CreateSignaturePolicy` 为 TCR 企业版实例的命名空间配置 KMS 镜像签名策略。签名策略绑定后,新推送至该命名空间的镜像将自动使用腾讯云密钥管理服务(KMS)的非对称密钥完成加签,保障镜像从分发到部署的全链路内容可信。对签名策略创建前已存在的镜像,需通过 `CreateSignature` 手动触发加签。 + +容器镜像签名功能仅支持 TCR 企业版**高级版(premium)**实例,当前为 Beta 功能。 + +### 为什么签名功能需要高级版(premium) + +签名功能依赖 premium 实例的独占资源链路,基础版(basic)和标准版(standard)均不支持,原因如下: + +1. **KMS 基础设施依赖**:镜像签名依赖腾讯云 KMS 的非对称签名验签能力。每次镜像推送触发签名、每次拉取触发验签,均为实时 KMS API 调用,产生 KMS 请求开销。premium 实例的架构预留了 KMS 集成通道。 +2. **计算资源差异**:basic 共享计算资源,不承载额外的 KMS 加解密链路。premium 提供独占计算资源配额,保障签名/验签的吞吐与低延迟。 +3. **产品功能分层**:basic = 核心镜像托管(push/pull),standard = + 安全扫描 + 部署阻断,premium = + 镜像签名 + 跨地域同步 + 复制实例。签名处于安全链最顶端,是标准版功能的增强,而非替代。 +4. **成本模型**:KMS API 调用按量计费,premium 定价中已内嵌 KMS 签名调用成本预算。basic/standard 的定价模型未包含此部分成本。 + +## 前置条件 + +- [环境准备](../../../index.md) +- 已完成 [创建企业版实例](../../create),实例 `RegistryType` 为 `premium`,`Status` 为 `Running` +- 已 [创建命名空间](../../image-creation/namespace) +- 已开通 [密钥管理服务(KMS)](https://cloud.tencent.com/document/product/573/38406) +- 已配置 `TCR_QCSRole` 角色对 KMS 的访问权限 +- 如使用子账号操作,需授予 `tcr:CreateSignaturePolicy`、`tcr:DeleteSignaturePolicy`、`tcr:CreateSignature`、`tcr:DescribeNamespaces` 权限,参见 [企业版授权方案示例](https://cloud.tencent.com/document/product/1141/41417) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域(如 ap-guangzhou) + +# 3. 确认实例类型为 premium(签名功能仅支持高级版) +tccli tcr DescribeInstances --Registryids '[""]' --region --output json | jq '.Registries[0].RegistryType' +# expected: "premium" + +# 4. 确认命名空间已存在 +tccli tcr DescribeNamespaces --RegistryId --NamespaceName --region +# expected: exit 0,目标命名空间在 NamespaceList 中 + +# 5. 确认 KMS 服务可用 +tccli kms ListKey --MatchKeyid "" --region --output json | jq '.TotalCount' +# expected: exit 0,返回 KMS 密钥总数(可为 0) +``` + +> 若步骤 3 返回 `"basic"` 或 `"standard"`,需先升级实例至 premium:`tccli tcr ModifyInstance --RegistryId --RegistryType premium --region `。升级后 `Status` 回到 `Running` 即可使用签名功能。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 / 参数 | 幂等 | +|-----------|------------------|:--:| +| 选择地域(控制台顶部菜单) | `--region ` | 是 | +| 查看实例类型 | `DescribeInstances --Registryids '[""]'` | 是 | +| 列出已绑定签名策略的命名空间 | `DescribeNamespaces --KmsSignPolicy true` | 是 | +| 查看命名空间签名策略详情 | `DescribeNamespaces --NamespaceName `(读 `Metadata` 中 `kms_sign` 字段) | 是 | +| 新建签名策略 | `CreateSignaturePolicy --Name --NamespaceName --KmsId --KmsRegion --Disabled` | 是(同名同参数重复创建成功) | +| 对已有镜像手动触发签名 | `CreateSignature --RepositoryName --ImageVersion` | 是 | +| 删除签名策略 | `DeleteSignaturePolicy --NamespaceName` | 是(删除不存在的策略也返回成功) | + +## 关键字段说明 + +以下说明 `CreateSignaturePolicy` 的主要参数。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | 企业版实例 ID,格式 `tcr-xxxxxxxx`,须为 premium 类型 | basic/standard 实例 → `UnsupportedOperation` | +| `Name` | String | 是 | 策略名称,2--50 字符,仅小写字母、数字及分隔符 `._-/`,不能以分隔符开头、结尾或连续 | 非法值 → `InvalidParameter` | +| `NamespaceName` | String | 是 | 已存在的命名空间名称。**一个命名空间仅支持一个签名策略** | 命名空间不存在 → `ResourceNotFound.TcrResourceNotFound`;已有策略 → 覆盖更新 | +| `KmsId` | String | 是 | KMS 密钥 ID。仅支持**非对称签名验签 RSA_2048**(`KeyUsage: ASYMMETRIC_SIGN_VERIFY_RSA_2048`) | 密钥用途为 `ENCRYPT_DECRYPT` → 签名无效;空值 → `InvalidParameter.ErrorTcrInvalidParameter` | +| `KmsRegion` | String | 是 | KMS 密钥所在地域,如 `ap-guangzhou`。建议与 TCR 实例同地域以降低延迟 | 不匹配 → 签名调用失败 | +| `Domain` | String | 否 | 用户自定义域名,为空时使用 TCR 实例默认域名生成签名 | — | +| `Disabled` | Boolean | 否 | 是否禁用策略,默认 `false`(启用)。`true` = 策略暂停生效,`false` = 策略启用 | — | + +> **注意**:`CreateSignaturePolicy` 的启用/禁用控制参数为 `--Disabled`(默认 `false` 即启用),**不是** `--Enabled`。`--Enabled` 不是有效参数,传入会报 `Unknown options: --Enabled`。 + +## 操作步骤 + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | 实例 ID | 格式 `tcr-xxxxxxxx`,须为 premium | `tccli tcr DescribeInstances` | +| `` | 命名空间名称 | 已存在,每个命名空间仅支持一个签名策略 | `tccli tcr DescribeNamespaces` | +| `` | KMS 密钥 ID | 用途必须为 `ASYMMETRIC_SIGN_VERIFY_RSA_2048` | `tccli kms CreateKey` 返回的 `KeyId` | +| `` | KMS 密钥所在地域 | 如 `ap-guangzhou`,建议与实例同地域 | 创建密钥时指定的 `--region` | +| `` | 签名策略名称 | 2--50 字符,仅小写字母、数字及 `._-/` | 自定义 | +| `` | TCR 实例地域 | 如 `ap-guangzhou` | `tccli tcr DescribeRegions` | +| `` | 镜像仓库名称 | 格式 `/` | `tccli tcr DescribeRepositories` | +| `` | 镜像版本 tag | 如 `v1.0.0` | `tccli tcr DescribeImages` | + +### 步骤1:创建 KMS 非对称签名验签密钥 + +容器签名功能要求 KMS 密钥用途为**非对称签名验签**,算法为 **RSA_2048**。普通加密密钥(`ENCRYPT_DECRYPT`)不可用。 + +```bash +tccli kms CreateKey \ + --KeyUsage ASYMMETRIC_SIGN_VERIFY_RSA_2048 \ + --Alias "tcr-signing-key" \ + --region \ + --output json +# expected: exit 0,返回 KeyId 和 KeyState: "Enabled" +``` + +**输出**: + +```json +{ + "KeyId": "7c99b7de-6afb-11f1-9b26-525400f27fe5", + "Alias": "tcr-signing-key", + "CreateTime": 1781776378, + "Description": "", + "KeyState": "Enabled", + "KeyUsage": "ASYMMETRIC_SIGN_VERIFY_RSA_2048", + "TagCode": 0, + "TagMsg": "", + "HsmClusterId": "", + "RequestId": "b6b7cf67-1aec-46d0-92a4-fc264f07bf89" +} +``` + +> 记录返回的 `KeyId`(示例 `7c99b7de-6afb-11f1-9b26-525400f27fe5`),后续 `CreateSignaturePolicy` 的 `--KmsId` 参数使用此值。 +> +> 也可在 [KMS 控制台](https://console.cloud.tencent.com/kms2) → **密钥管理 > 用户密钥** → **新建**,选择**非对称签名验签**用途、**RSA_2048** 算法。 +> +> 建议将 KMS 密钥和 TCR 实例放在相同地域,降低跨地域通信开销。 + +### 步骤2:授权 TCR_QCSRole 访问 KMS + +TCR 服务需要通过 `TCR_QCSRole` 角色调用 KMS API 完成签名。需在 CAM 中为该角色关联 `QcloudKMSFullAccess` 预设策略: + +```bash +tccli cam AttachRolePolicy \ + --AttachRoleName TCR_QCSRole \ + --PolicyName QcloudKMSFullAccess \ + --region \ + --output json +# expected: exit 0,返回 RequestId +``` + +**输出**: + +```json +{ + "RequestId": "0e93cff0-6aa7-4c6c-97ab-b403bc90bf74" +} +``` + +> 也可登录 [访问管理控制台](https://console.cloud.tencent.com/cam/overview) → **角色** → **TCR_QCSRole** → 关联预设策略 `QcloudKMSFullAccess`。 +> +> 此操作幂等——重复关联已绑定的策略不会报错。 + +### 步骤3:创建镜像签名策略 + +为命名空间创建签名策略。策略创建后对新推送的镜像**立即生效**——推送镜像到仓库时自动匹配策略并加签。 + +```bash +tccli tcr CreateSignaturePolicy \ + --RegistryId \ + --Name \ + --NamespaceName \ + --KmsId \ + --KmsRegion \ + --Disabled false \ + --region \ + --output json +# expected: exit 0,返回 RequestId +``` + +**输出**: + +```json +{ + "RequestId": "0b0d90ff-a9be-40b1-b801-111439dc357c" +} +``` + +> **注意**: +> +> - 启用参数为 `--Disabled false`(默认值,可省略)。`--Enabled` 不是有效参数,传入会报错。 +> - 签名策略对仓库中**已存在的镜像不生效**,需手动触发签名(见[步骤5](#步骤5对已有镜像手动触发签名))。 +> - 一个命名空间仅支持一个签名策略。对已有策略的命名空间再次创建,会覆盖更新原有策略。 + +也可使用 `--cli-input-json` 传入完整 JSON: + +```bash +tccli tcr CreateSignaturePolicy \ + --cli-input-json '{"RegistryId":"","Name":"","NamespaceName":"","KmsId":"","KmsRegion":"","Disabled":false}' \ + --region \ + --output json +# expected: exit 0,返回 RequestId +``` + +### 步骤4:验证策略已绑定(DescribeNamespaces --KmsSignPolicy true) + +TCR 没有 `DescribeSignaturePolicy` API(见[排障 - API 缺口](#api-缺口))。使用 `DescribeNamespaces --KmsSignPolicy true` 筛选已绑定签名策略的命名空间作为变通验证方案: + +```bash +tccli tcr DescribeNamespaces \ + --RegistryId \ + --KmsSignPolicy true \ + --region \ + --output json +# expected: exit 0,NamespaceList 含目标命名空间,TotalCount >= 1 +``` + +**输出**: + +```json +{ + "NamespaceList": [ + { + "Name": "spegel", + "CreationTime": "2026-05-20T12:37:32Z", + "Public": true, + "NamespaceId": 4, + "TagSpecification": { + "ResourceType": "namespace", + "Tags": [] + }, + "Metadata": [ + { + "Key": "prevent_vul", + "Value": "false" + }, + { + "Key": "public", + "Value": "true" + }, + { + "Key": "auto_scan", + "Value": "false" + }, + { + "Key": "kms_sign", + "Value": "{\"name\":\"tcr-sig-policy\",\"domain\":\"\",\"kms_id\":\"7c99b7de-6afb-11f1-9b26-525400f27fe5\",\"kms_region\":\"ap-guangzhou\",\"disabled\":false,\"create_at\":\"2026-06-18T17:53:08.725330486+08:00\"}" + } + ], + "CVEWhitelistItems": [], + "AutoScan": false, + "PreventVUL": false, + "Severity": "" + } + ], + "TotalCount": 1, + "RequestId": "e09fffbd-f280-4edd-a104-386e3ebfa6b8" +} +``` + +签名策略详情存储在命名空间的 `Metadata` 中,`Key` 为 `kms_sign`,`Value` 为 JSON 字符串: + +| kms_sign 字段 | 说明 | +|---------------|------| +| `name` | 策略名称(对应 `--Name` 参数) | +| `domain` | 签名域名(对应 `--Domain` 参数,空字符串表示使用实例默认域名) | +| `kms_id` | KMS 密钥 ID(对应 `--KmsId` 参数) | +| `kms_region` | KMS 密钥所在地域(对应 `--KmsRegion` 参数) | +| `disabled` | 策略是否禁用(对应 `--Disabled` 参数) | +| `create_at` | 策略创建时间 | + +> 也可通过 `--NamespaceName` 精确查询单个命名空间,在其 `Metadata` 中读取 `kms_sign` 字段确认策略绑定状态。 + +### 步骤5:对已有镜像手动触发签名 + +签名策略仅对新推送的镜像自动生效。对于策略创建前已推送至仓库的镜像,使用 `CreateSignature` 手动触发加签: + +```bash +tccli tcr CreateSignature \ + --RegistryId \ + --NamespaceName \ + --RepositoryName \ + --ImageVersion \ + --region \ + --output json +# expected: exit 0,返回 RequestId +``` + +**输出**: + +```json +{ + "RequestId": "f38dfd36-6abb-490c-b6d3-6fc00b6e089b" +} +``` + +> `CreateSignature` 为异步操作,返回 `RequestId` 后签名在后台进行。通过 `DescribeImages` 查看镜像的 `KmsSignature` 字段,非空即表示签名已完成。 +> +> **前置条件**:命名空间必须已绑定签名策略(步骤3),否则签名无法执行。 + +### 步骤6:删除签名策略 + +删除命名空间的签名策略。删除后该命名空间不再自动加签新镜像,且**存量签名数据一并删除**。 + +```bash +tccli tcr DeleteSignaturePolicy \ + --RegistryId \ + --NamespaceName \ + --region \ + --output json +# expected: exit 0,返回 RequestId +``` + +**输出**: + +```json +{ + "RequestId": "db9518fa-3914-4af5-a363-bab0f13e487b" +} +``` + +> **危险警告**:删除签名策略会同时删除该命名空间内已有的镜像签名数据,可能导致签名验证失败。请确认影响后再操作。 +> +> `DeleteSignaturePolicy` 是幂等的——删除不存在的策略也返回成功。 + +删除后验证策略已移除: + +```bash +tccli tcr DescribeNamespaces \ + --RegistryId \ + --KmsSignPolicy true \ + --region \ + --output json +# expected: TotalCount: 0,NamespaceList: [] +``` + +**输出**: + +```json +{ + "NamespaceList": [], + "TotalCount": 0, + "RequestId": "461baea5-ff3f-4f5e-ba78-602802c6cf61" +} +``` + +## 验证 + +### 控制面(tccli) + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 实例为 premium | `DescribeInstances --Registryids '[""]' --region --filter "Registries[0].RegistryType"` | `"premium"` | +| 签名策略已绑定 | `DescribeNamespaces --RegistryId --KmsSignPolicy true --region ` | `TotalCount >= 1`,目标命名空间在 `NamespaceList` 中 | +| 策略详情可读 | `DescribeNamespaces --RegistryId --NamespaceName --region ` | `Metadata` 含 `Key: "kms_sign"`,`Value` 为含策略名称、KmsId 等字段的 JSON | +| KMS 密钥用途正确 | `tccli kms DescribeKey --KeyId --region --filter "KeyMetadata.KeyUsage"` | `"ASYMMETRIC_SIGN_VERIFY_RSA_2048"` | +| 镜像已签名 | `DescribeImages --RegistryId --NamespaceName --RepositoryName --region ` | `ImageInfoList[].KmsSignature` 非空 | +| 策略已删除 | `DescribeNamespaces --RegistryId --KmsSignPolicy true --region ` | `TotalCount: 0`,`NamespaceList: []` | + +## 清理 + +### 1. 删除签名策略 + +```bash +tccli tcr DeleteSignaturePolicy \ + --RegistryId \ + --NamespaceName \ + --region \ + --output json +# expected: exit 0,返回 RequestId +``` + +### 2. 验证策略已删除 + +```bash +tccli tcr DescribeNamespaces \ + --RegistryId \ + --KmsSignPolicy true \ + --region \ + --output json +# expected: TotalCount: 0,NamespaceList: [] +``` + +### 3. 清理 KMS 密钥(可选) + +签名策略删除后,KMS 密钥仍保留。如不再使用,可计划删除: + +```bash +# 先禁用密钥 +tccli kms DisableKey --KeyId --region --output json +# expected: exit 0,返回 RequestId + +# 计划删除(7 天后执行) +tccli kms ScheduleKeyDeletion --KeyId --PendingWindowInDays 7 --region --output json +# expected: exit 0,返回 DeletionDate 和 KeyId +``` + +> KMS 密钥必须先 `DisableKey` 再 `ScheduleKeyDeletion`,直接计划删除启用状态的密钥会返回 `ResourceUnavailable.CmkShouldBeDisabled` 错误。 +> +> CAM 角色策略解绑(`DetachRolePolicy`)不属于 TCR 操作范畴,如需清理请在 [访问管理控制台](https://console.cloud.tencent.com/cam/overview) 完成。`QcloudKMSFullAccess` 为通用预设策略,其他服务可能也在使用,解绑前请确认影响。 + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `CreateSignaturePolicy` 返回 `UnsupportedOperation` | `tccli tcr DescribeInstances --Registryids '[""]' --region --filter "Registries[0].RegistryType"` 查看 `RegistryType` | 签名策略仅支持 premium 实例,basic/standard 不支持。这是产品能力分层设计——签名需 KMS 非对称密钥集成,依赖 premium 实例的独占计算资源 | 升级实例:`tccli tcr ModifyInstance --RegistryId --RegistryType premium --region `,确认 `Status: Running` 后重试 | +| `CreateSignaturePolicy` 返回 `ResourceNotFound.TcrResourceNotFound` 提示 "project not found" | `tccli tcr DescribeNamespaces --RegistryId --region ` 确认命名空间存在 | `--NamespaceName` 指定的命名空间不存在 | 先 `tccli tcr CreateNamespace` 创建命名空间,再创建签名策略 | +| `CreateSignaturePolicy` 返回 `InvalidParameter.ErrorTcrInvalidParameter` 提示 "KmsId: non zero value required" | 检查 `--KmsId` 是否为空 | KMS 密钥 ID 必填,不能为空字符串 | 先 `tccli kms CreateKey --KeyUsage ASYMMETRIC_SIGN_VERIFY_RSA_2048` 创建密钥,用返回的 `KeyId` 填入 `--KmsId` | +| `tccli` 返回 `Unknown options: --Enabled, true` | 检查命令参数 | `--Enabled` 不是有效参数。启用/禁用控制参数为 `--Disabled`(默认 `false` 即启用) | 使用 `--Disabled false` 启用策略,或 `--Disabled true` 禁用策略 | +| `CreateSignature` 返回错误提示命名空间未绑定策略 | `DescribeNamespaces --KmsSignPolicy true` 确认目标命名空间不在列表中 | 命名空间尚未创建签名策略 | 先执行步骤3创建签名策略,再调用 `CreateSignature` | +| CAM 权限拒绝(`UnauthorizedOperation`) | `tccli cam DescribeRoleList --Filters '{"RoleName":"TCR_QCSRole"}' --region ` 检查角色和策略 | 子账号缺少 TCR 操作权限,或 `TCR_QCSRole` 未授权 KMS 访问 | ① 确保子账号有 `tcr:*` 权限;② `tccli cam AttachRolePolicy --AttachRoleName TCR_QCSRole --PolicyName QcloudKMSFullAccess --region ` | +| KMS 密钥不可用或用途不对 | `tccli kms DescribeKey --KeyId --region --filter "KeyMetadata.KeyUsage"` 检查用途 | 签名策略要求 KMS 密钥用途为 `ASYMMETRIC_SIGN_VERIFY_RSA_2048`,`ENCRYPT_DECRYPT` 密钥不可用 | 重新创建正确用途的 KMS 密钥 | +| `ScheduleKeyDeletion` 返回 `ResourceUnavailable.CmkShouldBeDisabled` | 检查密钥状态 | KMS 密钥必须先禁用才能计划删除 | 先 `tccli kms DisableKey --KeyId --region `,再 `ScheduleKeyDeletion` | + +### API 缺口 + +| 问题 | 说明 | 应对方式 | +|------|------|---------| +| **无 `DescribeSignaturePolicy` API** | TCR API 清单中仅有 `CreateSignaturePolicy`、`DeleteSignaturePolicy` 和 `CreateSignature`,没有 Read/Describe 类接口。无法通过 CLI 直接查询实例下已创建的签名策略列表 | 变通方案:① `tccli tcr DescribeNamespaces --RegistryId --KmsSignPolicy true --region ` 列出已绑定签名策略的命名空间;② 通过 `--NamespaceName` 精确查询,读取 `Metadata` 中 `kms_sign` 字段获取策略详情(名称、KmsId、KmsRegion、disabled 状态、创建时间);③ 通过唯一策略名称自行维护策略清单 | + +### 读者常见错误 + +| 错误 | 后果 | 正确做法 | +|------|------|---------| +| 在 basic/standard 实例上调用 `CreateSignaturePolicy` | 返回 `UnsupportedOperation` | 先 `DescribeInstances` 确认 `RegistryType` 为 `premium`,若非则升级 | +| 使用 `--Enabled true` 参数 | 返回 `Unknown options: --Enabled, true` | 使用 `--Disabled false` 启用策略(`--Disabled` 默认 `false`,可省略) | +| 使用普通加密密钥(`ENCRYPT_DECRYPT`)创建签名策略 | 策略创建可能成功但签名无效 | 仅使用 `KeyUsage: ASYMMETRIC_SIGN_VERIFY_RSA_2048` 的 KMS 密钥 | +| 误以为签名策略对已有镜像生效 | 策略创建后旧镜像无签名 | 策略仅对新推送镜像自动生效;对已有镜像需手动调用 `CreateSignature` | +| 误以为可查询签名策略列表 | 无 `DescribeSignaturePolicy` API | 使用 `DescribeNamespaces --KmsSignPolicy true` 变通查询 | +| 删除策略后仍期望保留签名数据 | 存量签名数据被一并删除 | 删除前确认影响,必要时先导出签名信息 | + +## 下一步 + +- [容器镜像安全扫描](../vulnerability-scan) — 配置自动安全扫描,识别镜像内已知漏洞 +- [高危镜像部署阻断](../deployment-block)(page_id `63869`) — 基于扫描结果阻断高危镜像部署(需 standard/premium) +- [容器镜像签名验证](https://cloud.tencent.com/document/product/457/80899) — 在 TKE 集群中使用增强组件 Cerberus 进行自动验签,设置策略在验签失败时阻断镜像部署 +- [管理命名空间](../../image-creation/namespace) — 创建和管理命名空间 +- [环境准备](../../../index.md) — 返回 TCR 工具链入口 + +## 控制台替代 + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 选择目标 premium 实例 → 左侧导航栏选择**镜像安全** → **镜像签名** → 单击**新建**,填写策略名称、选择命名空间、选择 KMS 密钥、配置域名,单击**确认**完成创建。在**镜像仓库 > 版本管理**中可手动触发已有镜像的加签操作。在**镜像签名**页面选择策略行,单击**删除**可删除签名策略。 diff --git a/src/content/docs/cli/tcr/ops/image-security/image-signing/image-signing-tcrctl.md b/src/content/docs/cli/tcr/ops/image-security/image-signing/image-signing-tcrctl.md new file mode 100644 index 0000000..cd92205 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-security/image-signing/image-signing-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "容器镜像签名(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[容器镜像签名](https://cloud.tencent.com/document/product/1141/80862) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/image-security/image-signing/image-signing.md b/src/content/docs/cli/tcr/ops/image-security/image-signing/image-signing.md new file mode 100644 index 0000000..b320670 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-security/image-signing/image-signing.md @@ -0,0 +1,440 @@ +--- +title: "容器镜像签名(tccli)" +description: "· page_id `80862`" +--- + +> 对照官方:[容器镜像签名](https://cloud.tencent.com/document/product/1141/80862) · page_id `80862` + +## 概述 + +通过 `tccli tcr CreateSignaturePolicy` 为 TCR 企业版实例的命名空间配置 KMS 镜像签名策略。签名策略绑定后,新推送至该命名空间的镜像将自动使用腾讯云密钥管理服务(KMS)的非对称密钥完成加签,保障镜像从分发到部署的全链路内容可信。对签名策略创建前已存在的镜像,需通过 `CreateSignature` 手动触发加签。 + +容器镜像签名功能仅支持 TCR 企业版**高级版(premium)**实例,当前为 Beta 功能。 + +### 为什么签名功能需要高级版(premium) + +签名功能依赖 premium 实例的独占资源链路,基础版(basic)和标准版(standard)均不支持,原因如下: + +1. **KMS 基础设施依赖**:镜像签名依赖腾讯云 KMS 的非对称签名验签能力。每次镜像推送触发签名、每次拉取触发验签,均为实时 KMS API 调用,产生 KMS 请求开销。premium 实例的架构预留了 KMS 集成通道。 +2. **计算资源差异**:basic 共享计算资源,不承载额外的 KMS 加解密链路。premium 提供独占计算资源配额,保障签名/验签的吞吐与低延迟。 +3. **产品功能分层**:basic = 核心镜像托管(push/pull),standard = + 安全扫描 + 部署阻断,premium = + 镜像签名 + 跨地域同步 + 复制实例。签名处于安全链最顶端,是标准版功能的增强,而非替代。 +4. **成本模型**:KMS API 调用按量计费,premium 定价中已内嵌 KMS 签名调用成本预算。basic/standard 的定价模型未包含此部分成本。 + +## 前置条件 + +- [环境准备](../../../index.md) +- 已完成 [创建企业版实例](../../create),实例 `RegistryType` 为 `premium`,`Status` 为 `Running` +- 已 [创建命名空间](../../image-creation/namespace) +- 已开通 [密钥管理服务(KMS)](https://cloud.tencent.com/document/product/573/38406) +- 已配置 `TCR_QCSRole` 角色对 KMS 的访问权限 +- 如使用子账号操作,需授予 `tcr:CreateSignaturePolicy`、`tcr:DeleteSignaturePolicy`、`tcr:CreateSignature`、`tcr:DescribeNamespaces` 权限,参见 [企业版授权方案示例](https://cloud.tencent.com/document/product/1141/41417) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域(如 ap-guangzhou) + +# 3. 确认实例类型为 premium(签名功能仅支持高级版) +tccli tcr DescribeInstances --Registryids '[""]' --region --output json | jq '.Registries[0].RegistryType' +# expected: "premium" + +# 4. 确认命名空间已存在 +tccli tcr DescribeNamespaces --RegistryId --NamespaceName --region +# expected: exit 0,目标命名空间在 NamespaceList 中 + +# 5. 确认 KMS 服务可用 +tccli kms ListKey --MatchKeyid "" --region --output json | jq '.TotalCount' +# expected: exit 0,返回 KMS 密钥总数(可为 0) +``` + +> 若步骤 3 返回 `"basic"` 或 `"standard"`,需先升级实例至 premium:`tccli tcr ModifyInstance --RegistryId --RegistryType premium --region `。升级后 `Status` 回到 `Running` 即可使用签名功能。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 / 参数 | 幂等 | +|-----------|------------------|:--:| +| 选择地域(控制台顶部菜单) | `--region ` | 是 | +| 查看实例类型 | `DescribeInstances --Registryids '[""]'` | 是 | +| 列出已绑定签名策略的命名空间 | `DescribeNamespaces --KmsSignPolicy true` | 是 | +| 查看命名空间签名策略详情 | `DescribeNamespaces --NamespaceName `(读 `Metadata` 中 `kms_sign` 字段) | 是 | +| 新建签名策略 | `CreateSignaturePolicy --Name --NamespaceName --KmsId --KmsRegion --Disabled` | 是(同名同参数重复创建成功) | +| 对已有镜像手动触发签名 | `CreateSignature --RepositoryName --ImageVersion` | 是 | +| 删除签名策略 | `DeleteSignaturePolicy --NamespaceName` | 是(删除不存在的策略也返回成功) | + +## 关键字段说明 + +以下说明 `CreateSignaturePolicy` 的主要参数。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | 企业版实例 ID,格式 `tcr-xxxxxxxx`,须为 premium 类型 | basic/standard 实例 → `UnsupportedOperation` | +| `Name` | String | 是 | 策略名称,2--50 字符,仅小写字母、数字及分隔符 `._-/`,不能以分隔符开头、结尾或连续 | 非法值 → `InvalidParameter` | +| `NamespaceName` | String | 是 | 已存在的命名空间名称。**一个命名空间仅支持一个签名策略** | 命名空间不存在 → `ResourceNotFound.TcrResourceNotFound`;已有策略 → 覆盖更新 | +| `KmsId` | String | 是 | KMS 密钥 ID。仅支持**非对称签名验签 RSA_2048**(`KeyUsage: ASYMMETRIC_SIGN_VERIFY_RSA_2048`) | 密钥用途为 `ENCRYPT_DECRYPT` → 签名无效;空值 → `InvalidParameter.ErrorTcrInvalidParameter` | +| `KmsRegion` | String | 是 | KMS 密钥所在地域,如 `ap-guangzhou`。建议与 TCR 实例同地域以降低延迟 | 不匹配 → 签名调用失败 | +| `Domain` | String | 否 | 用户自定义域名,为空时使用 TCR 实例默认域名生成签名 | — | +| `Disabled` | Boolean | 否 | 是否禁用策略,默认 `false`(启用)。`true` = 策略暂停生效,`false` = 策略启用 | — | + +> **注意**:`CreateSignaturePolicy` 的启用/禁用控制参数为 `--Disabled`(默认 `false` 即启用),**不是** `--Enabled`。`--Enabled` 不是有效参数,传入会报 `Unknown options: --Enabled`。 + +## 操作步骤 + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | 实例 ID | 格式 `tcr-xxxxxxxx`,须为 premium | `tccli tcr DescribeInstances` | +| `` | 命名空间名称 | 已存在,每个命名空间仅支持一个签名策略 | `tccli tcr DescribeNamespaces` | +| `` | KMS 密钥 ID | 用途必须为 `ASYMMETRIC_SIGN_VERIFY_RSA_2048` | `tccli kms CreateKey` 返回的 `KeyId` | +| `` | KMS 密钥所在地域 | 如 `ap-guangzhou`,建议与实例同地域 | 创建密钥时指定的 `--region` | +| `` | 签名策略名称 | 2--50 字符,仅小写字母、数字及 `._-/` | 自定义 | +| `` | TCR 实例地域 | 如 `ap-guangzhou` | `tccli tcr DescribeRegions` | +| `` | 镜像仓库名称 | 格式 `/` | `tccli tcr DescribeRepositories` | +| `` | 镜像版本 tag | 如 `v1.0.0` | `tccli tcr DescribeImages` | + +### 步骤1:创建 KMS 非对称签名验签密钥 + +容器签名功能要求 KMS 密钥用途为**非对称签名验签**,算法为 **RSA_2048**。普通加密密钥(`ENCRYPT_DECRYPT`)不可用。 + +```bash +tccli kms CreateKey \ + --KeyUsage ASYMMETRIC_SIGN_VERIFY_RSA_2048 \ + --Alias "tcr-signing-key" \ + --region \ + --output json +# expected: exit 0,返回 KeyId 和 KeyState: "Enabled" +``` + +**输出**: + +```json +{ + "KeyId": "7c99b7de-6afb-11f1-9b26-525400f27fe5", + "Alias": "tcr-signing-key", + "CreateTime": 1781776378, + "Description": "", + "KeyState": "Enabled", + "KeyUsage": "ASYMMETRIC_SIGN_VERIFY_RSA_2048", + "TagCode": 0, + "TagMsg": "", + "HsmClusterId": "", + "RequestId": "b6b7cf67-1aec-46d0-92a4-fc264f07bf89" +} +``` + +> 记录返回的 `KeyId`(示例 `7c99b7de-6afb-11f1-9b26-525400f27fe5`),后续 `CreateSignaturePolicy` 的 `--KmsId` 参数使用此值。 +> +> 也可在 [KMS 控制台](https://console.cloud.tencent.com/kms2) → **密钥管理 > 用户密钥** → **新建**,选择**非对称签名验签**用途、**RSA_2048** 算法。 +> +> 建议将 KMS 密钥和 TCR 实例放在相同地域,降低跨地域通信开销。 + +### 步骤2:授权 TCR_QCSRole 访问 KMS + +TCR 服务需要通过 `TCR_QCSRole` 角色调用 KMS API 完成签名。需在 CAM 中为该角色关联 `QcloudKMSFullAccess` 预设策略: + +```bash +tccli cam AttachRolePolicy \ + --AttachRoleName TCR_QCSRole \ + --PolicyName QcloudKMSFullAccess \ + --region \ + --output json +# expected: exit 0,返回 RequestId +``` + +**输出**: + +```json +{ + "RequestId": "0e93cff0-6aa7-4c6c-97ab-b403bc90bf74" +} +``` + +> 也可登录 [访问管理控制台](https://console.cloud.tencent.com/cam/overview) → **角色** → **TCR_QCSRole** → 关联预设策略 `QcloudKMSFullAccess`。 +> +> 此操作幂等——重复关联已绑定的策略不会报错。 + +### 步骤3:创建镜像签名策略 + +为命名空间创建签名策略。策略创建后对新推送的镜像**立即生效**——推送镜像到仓库时自动匹配策略并加签。 + +```bash +tccli tcr CreateSignaturePolicy \ + --RegistryId \ + --Name \ + --NamespaceName \ + --KmsId \ + --KmsRegion \ + --Disabled false \ + --region \ + --output json +# expected: exit 0,返回 RequestId +``` + +**输出**: + +```json +{ + "RequestId": "0b0d90ff-a9be-40b1-b801-111439dc357c" +} +``` + +> **注意**: +> +> - 启用参数为 `--Disabled false`(默认值,可省略)。`--Enabled` 不是有效参数,传入会报错。 +> - 签名策略对仓库中**已存在的镜像不生效**,需手动触发签名(见[步骤5](#步骤5对已有镜像手动触发签名))。 +> - 一个命名空间仅支持一个签名策略。对已有策略的命名空间再次创建,会覆盖更新原有策略。 + +也可使用 `--cli-input-json` 传入完整 JSON: + +```bash +tccli tcr CreateSignaturePolicy \ + --cli-input-json '{"RegistryId":"","Name":"","NamespaceName":"","KmsId":"","KmsRegion":"","Disabled":false}' \ + --region \ + --output json +# expected: exit 0,返回 RequestId +``` + +### 步骤4:验证策略已绑定(DescribeNamespaces --KmsSignPolicy true) + +TCR 没有 `DescribeSignaturePolicy` API(见[排障 - API 缺口](#api-缺口))。使用 `DescribeNamespaces --KmsSignPolicy true` 筛选已绑定签名策略的命名空间作为变通验证方案: + +```bash +tccli tcr DescribeNamespaces \ + --RegistryId \ + --KmsSignPolicy true \ + --region \ + --output json +# expected: exit 0,NamespaceList 含目标命名空间,TotalCount >= 1 +``` + +**输出**: + +```json +{ + "NamespaceList": [ + { + "Name": "spegel", + "CreationTime": "2026-05-20T12:37:32Z", + "Public": true, + "NamespaceId": 4, + "TagSpecification": { + "ResourceType": "namespace", + "Tags": [] + }, + "Metadata": [ + { + "Key": "prevent_vul", + "Value": "false" + }, + { + "Key": "public", + "Value": "true" + }, + { + "Key": "auto_scan", + "Value": "false" + }, + { + "Key": "kms_sign", + "Value": "{\"name\":\"tcr-sig-policy\",\"domain\":\"\",\"kms_id\":\"7c99b7de-6afb-11f1-9b26-525400f27fe5\",\"kms_region\":\"ap-guangzhou\",\"disabled\":false,\"create_at\":\"2026-06-18T17:53:08.725330486+08:00\"}" + } + ], + "CVEWhitelistItems": [], + "AutoScan": false, + "PreventVUL": false, + "Severity": "" + } + ], + "TotalCount": 1, + "RequestId": "e09fffbd-f280-4edd-a104-386e3ebfa6b8" +} +``` + +签名策略详情存储在命名空间的 `Metadata` 中,`Key` 为 `kms_sign`,`Value` 为 JSON 字符串: + +| kms_sign 字段 | 说明 | +|---------------|------| +| `name` | 策略名称(对应 `--Name` 参数) | +| `domain` | 签名域名(对应 `--Domain` 参数,空字符串表示使用实例默认域名) | +| `kms_id` | KMS 密钥 ID(对应 `--KmsId` 参数) | +| `kms_region` | KMS 密钥所在地域(对应 `--KmsRegion` 参数) | +| `disabled` | 策略是否禁用(对应 `--Disabled` 参数) | +| `create_at` | 策略创建时间 | + +> 也可通过 `--NamespaceName` 精确查询单个命名空间,在其 `Metadata` 中读取 `kms_sign` 字段确认策略绑定状态。 + +### 步骤5:对已有镜像手动触发签名 + +签名策略仅对新推送的镜像自动生效。对于策略创建前已推送至仓库的镜像,使用 `CreateSignature` 手动触发加签: + +```bash +tccli tcr CreateSignature \ + --RegistryId \ + --NamespaceName \ + --RepositoryName \ + --ImageVersion \ + --region \ + --output json +# expected: exit 0,返回 RequestId +``` + +**输出**: + +```json +{ + "RequestId": "f38dfd36-6abb-490c-b6d3-6fc00b6e089b" +} +``` + +> `CreateSignature` 为异步操作,返回 `RequestId` 后签名在后台进行。通过 `DescribeImages` 查看镜像的 `KmsSignature` 字段,非空即表示签名已完成。 +> +> **前置条件**:命名空间必须已绑定签名策略(步骤3),否则签名无法执行。 + +### 步骤6:删除签名策略 + +删除命名空间的签名策略。删除后该命名空间不再自动加签新镜像,且**存量签名数据一并删除**。 + +```bash +tccli tcr DeleteSignaturePolicy \ + --RegistryId \ + --NamespaceName \ + --region \ + --output json +# expected: exit 0,返回 RequestId +``` + +**输出**: + +```json +{ + "RequestId": "db9518fa-3914-4af5-a363-bab0f13e487b" +} +``` + +> **危险警告**:删除签名策略会同时删除该命名空间内已有的镜像签名数据,可能导致签名验证失败。请确认影响后再操作。 +> +> `DeleteSignaturePolicy` 是幂等的——删除不存在的策略也返回成功。 + +删除后验证策略已移除: + +```bash +tccli tcr DescribeNamespaces \ + --RegistryId \ + --KmsSignPolicy true \ + --region \ + --output json +# expected: TotalCount: 0,NamespaceList: [] +``` + +**输出**: + +```json +{ + "NamespaceList": [], + "TotalCount": 0, + "RequestId": "461baea5-ff3f-4f5e-ba78-602802c6cf61" +} +``` + +## 验证 + +### 控制面(tccli) + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 实例为 premium | `DescribeInstances --Registryids '[""]' --region --filter "Registries[0].RegistryType"` | `"premium"` | +| 签名策略已绑定 | `DescribeNamespaces --RegistryId --KmsSignPolicy true --region ` | `TotalCount >= 1`,目标命名空间在 `NamespaceList` 中 | +| 策略详情可读 | `DescribeNamespaces --RegistryId --NamespaceName --region ` | `Metadata` 含 `Key: "kms_sign"`,`Value` 为含策略名称、KmsId 等字段的 JSON | +| KMS 密钥用途正确 | `tccli kms DescribeKey --KeyId --region --filter "KeyMetadata.KeyUsage"` | `"ASYMMETRIC_SIGN_VERIFY_RSA_2048"` | +| 镜像已签名 | `DescribeImages --RegistryId --NamespaceName --RepositoryName --region ` | `ImageInfoList[].KmsSignature` 非空 | +| 策略已删除 | `DescribeNamespaces --RegistryId --KmsSignPolicy true --region ` | `TotalCount: 0`,`NamespaceList: []` | + +## 清理 + +### 1. 删除签名策略 + +```bash +tccli tcr DeleteSignaturePolicy \ + --RegistryId \ + --NamespaceName \ + --region \ + --output json +# expected: exit 0,返回 RequestId +``` + +### 2. 验证策略已删除 + +```bash +tccli tcr DescribeNamespaces \ + --RegistryId \ + --KmsSignPolicy true \ + --region \ + --output json +# expected: TotalCount: 0,NamespaceList: [] +``` + +### 3. 清理 KMS 密钥(可选) + +签名策略删除后,KMS 密钥仍保留。如不再使用,可计划删除: + +```bash +# 先禁用密钥 +tccli kms DisableKey --KeyId --region --output json +# expected: exit 0,返回 RequestId + +# 计划删除(7 天后执行) +tccli kms ScheduleKeyDeletion --KeyId --PendingWindowInDays 7 --region --output json +# expected: exit 0,返回 DeletionDate 和 KeyId +``` + +> KMS 密钥必须先 `DisableKey` 再 `ScheduleKeyDeletion`,直接计划删除启用状态的密钥会返回 `ResourceUnavailable.CmkShouldBeDisabled` 错误。 +> +> CAM 角色策略解绑(`DetachRolePolicy`)不属于 TCR 操作范畴,如需清理请在 [访问管理控制台](https://console.cloud.tencent.com/cam/overview) 完成。`QcloudKMSFullAccess` 为通用预设策略,其他服务可能也在使用,解绑前请确认影响。 + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `CreateSignaturePolicy` 返回 `UnsupportedOperation` | `tccli tcr DescribeInstances --Registryids '[""]' --region --filter "Registries[0].RegistryType"` 查看 `RegistryType` | 签名策略仅支持 premium 实例,basic/standard 不支持。这是产品能力分层设计——签名需 KMS 非对称密钥集成,依赖 premium 实例的独占计算资源 | 升级实例:`tccli tcr ModifyInstance --RegistryId --RegistryType premium --region `,确认 `Status: Running` 后重试 | +| `CreateSignaturePolicy` 返回 `ResourceNotFound.TcrResourceNotFound` 提示 "project not found" | `tccli tcr DescribeNamespaces --RegistryId --region ` 确认命名空间存在 | `--NamespaceName` 指定的命名空间不存在 | 先 `tccli tcr CreateNamespace` 创建命名空间,再创建签名策略 | +| `CreateSignaturePolicy` 返回 `InvalidParameter.ErrorTcrInvalidParameter` 提示 "KmsId: non zero value required" | 检查 `--KmsId` 是否为空 | KMS 密钥 ID 必填,不能为空字符串 | 先 `tccli kms CreateKey --KeyUsage ASYMMETRIC_SIGN_VERIFY_RSA_2048` 创建密钥,用返回的 `KeyId` 填入 `--KmsId` | +| `tccli` 返回 `Unknown options: --Enabled, true` | 检查命令参数 | `--Enabled` 不是有效参数。启用/禁用控制参数为 `--Disabled`(默认 `false` 即启用) | 使用 `--Disabled false` 启用策略,或 `--Disabled true` 禁用策略 | +| `CreateSignature` 返回错误提示命名空间未绑定策略 | `DescribeNamespaces --KmsSignPolicy true` 确认目标命名空间不在列表中 | 命名空间尚未创建签名策略 | 先执行步骤3创建签名策略,再调用 `CreateSignature` | +| CAM 权限拒绝(`UnauthorizedOperation`) | `tccli cam DescribeRoleList --Filters '{"RoleName":"TCR_QCSRole"}' --region ` 检查角色和策略 | 子账号缺少 TCR 操作权限,或 `TCR_QCSRole` 未授权 KMS 访问 | ① 确保子账号有 `tcr:*` 权限;② `tccli cam AttachRolePolicy --AttachRoleName TCR_QCSRole --PolicyName QcloudKMSFullAccess --region ` | +| KMS 密钥不可用或用途不对 | `tccli kms DescribeKey --KeyId --region --filter "KeyMetadata.KeyUsage"` 检查用途 | 签名策略要求 KMS 密钥用途为 `ASYMMETRIC_SIGN_VERIFY_RSA_2048`,`ENCRYPT_DECRYPT` 密钥不可用 | 重新创建正确用途的 KMS 密钥 | +| `ScheduleKeyDeletion` 返回 `ResourceUnavailable.CmkShouldBeDisabled` | 检查密钥状态 | KMS 密钥必须先禁用才能计划删除 | 先 `tccli kms DisableKey --KeyId --region `,再 `ScheduleKeyDeletion` | + +### API 缺口 + +| 问题 | 说明 | 应对方式 | +|------|------|---------| +| **无 `DescribeSignaturePolicy` API** | TCR API 清单中仅有 `CreateSignaturePolicy`、`DeleteSignaturePolicy` 和 `CreateSignature`,没有 Read/Describe 类接口。无法通过 CLI 直接查询实例下已创建的签名策略列表 | 变通方案:① `tccli tcr DescribeNamespaces --RegistryId --KmsSignPolicy true --region ` 列出已绑定签名策略的命名空间;② 通过 `--NamespaceName` 精确查询,读取 `Metadata` 中 `kms_sign` 字段获取策略详情(名称、KmsId、KmsRegion、disabled 状态、创建时间);③ 通过唯一策略名称自行维护策略清单 | + +### 读者常见错误 + +| 错误 | 后果 | 正确做法 | +|------|------|---------| +| 在 basic/standard 实例上调用 `CreateSignaturePolicy` | 返回 `UnsupportedOperation` | 先 `DescribeInstances` 确认 `RegistryType` 为 `premium`,若非则升级 | +| 使用 `--Enabled true` 参数 | 返回 `Unknown options: --Enabled, true` | 使用 `--Disabled false` 启用策略(`--Disabled` 默认 `false`,可省略) | +| 使用普通加密密钥(`ENCRYPT_DECRYPT`)创建签名策略 | 策略创建可能成功但签名无效 | 仅使用 `KeyUsage: ASYMMETRIC_SIGN_VERIFY_RSA_2048` 的 KMS 密钥 | +| 误以为签名策略对已有镜像生效 | 策略创建后旧镜像无签名 | 策略仅对新推送镜像自动生效;对已有镜像需手动调用 `CreateSignature` | +| 误以为可查询签名策略列表 | 无 `DescribeSignaturePolicy` API | 使用 `DescribeNamespaces --KmsSignPolicy true` 变通查询 | +| 删除策略后仍期望保留签名数据 | 存量签名数据被一并删除 | 删除前确认影响,必要时先导出签名信息 | + +## 下一步 + +- [容器镜像安全扫描](../vulnerability-scan) — 配置自动安全扫描,识别镜像内已知漏洞 +- [高危镜像部署阻断](../deployment-block)(page_id `63869`) — 基于扫描结果阻断高危镜像部署(需 standard/premium) +- [容器镜像签名验证](https://cloud.tencent.com/document/product/457/80899) — 在 TKE 集群中使用增强组件 Cerberus 进行自动验签,设置策略在验签失败时阻断镜像部署 +- [管理命名空间](../../image-creation/namespace) — 创建和管理命名空间 +- [环境准备](../../../index.md) — 返回 TCR 工具链入口 + +## 控制台替代 + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 选择目标 premium 实例 → 左侧导航栏选择**镜像安全** → **镜像签名** → 单击**新建**,填写策略名称、选择命名空间、选择 KMS 密钥、配置域名,单击**确认**完成创建。在**镜像仓库 > 版本管理**中可手动触发已有镜像的加签操作。在**镜像签名**页面选择策略行,单击**删除**可删除签名策略。 diff --git a/src/content/docs/cli/tcr/ops/image-security/immutable-tags-tcrctl.md b/src/content/docs/cli/tcr/ops/image-security/immutable-tags-tcrctl.md new file mode 100644 index 0000000..9945d02 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-security/immutable-tags-tcrctl.md @@ -0,0 +1,10 @@ +--- +title: "镜像版本不可变(tcrctl)" +description: "P1 尚未交付。参见同目录下 tccli 操作./tccli%20操作.md(P0)。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> 页型 **T1(P1 stub)** · 对照官方:[镜像版本不可变](https://cloud.tencent.com/document/product/1141/58200) · page_id `58200` + +P1 尚未交付。参见同目录下 tccli 操作(P0)。 diff --git a/src/content/docs/cli/tcr/ops/image-security/immutable-tags.md b/src/content/docs/cli/tcr/ops/image-security/immutable-tags.md new file mode 100644 index 0000000..1cb66fa --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-security/immutable-tags.md @@ -0,0 +1,545 @@ +--- +title: "镜像版本不可变(tccli)" +description: "· page_id `58200`" +--- + +> 对照官方:[镜像版本不可变](https://cloud.tencent.com/document/product/1141/58200) · page_id `58200` + +## 概述 + +TCR 企业版支持对容器镜像版本启用不可变(immutable)保护。启用后,同一命名空间下匹配规则的镜像 Tag 仅能被成功推送一次,后续对同一 Tag 的 `docker push` 将被拒绝,有效防止生产环境中的版本覆盖事故。 + +通过 `tccli tcr CreateImmutableTagRules` 创建规则,通过 `RepositoryDecoration` 与 `TagDecoration` 两个维度组合控制规则生效范围: + +| 维度 | 枚举值 | 语义 | +|------|--------|------| +| `RepositoryDecoration` | `repoMatches` | **匹配**指定仓库模式生效(带 `repo` 前缀) | +| | `repoExcludes` | **排除**指定仓库模式生效(带 `repo` 前缀) | +| `TagDecoration` | `matches` | **匹配**指定 Tag 模式生效(不带 `repo` 前缀) | +| | `excludes` | **排除**指定 Tag 模式生效(不带 `repo` 前缀) | + +> **关键易错点(务必先读):** `RepositoryDecoration` 的枚举值为 `repoMatches`/`repoExcludes`(带 `repo` 前缀),**不是** `matches`/`excludes`。而 `TagDecoration` 的枚举值为 `matches`/`excludes`(**不带** `repo` 前缀)。两者不对称,传入裸 `matches` 给 `RepositoryDecoration` 将触发校验失败。详见 [排障](#排障)。 + +规则创建后默认立即生效(`Disabled: false`),同一命名空间可创建多条规则,重叠时以最严格条件为准(任一规则阻断即拒绝推送)。 + +## 前置条件 + +- 已 [创建企业版实例](../../create),实例状态为 `Running`。 +- 已配置 `tccli` 凭证(参见 [环境准备](../../../index.md))。 +- 实例内已有目标命名空间(参见 [管理命名空间](../../image-creation/namespace))。 +- 若使用子账号操作,需授予 `tcr:CreateImmutableTagRules`、`tcr:DescribeImmutableTagRules`、`tcr:ModifyImmutableTagRules`、`tcr:DeleteImmutableTagRules` 权限,参见 [企业版授权方案示例](https://cloud.tencent.com/document/product/1141/41417)。 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0(真机实测 3.1.107.1) + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域(如 ap-guangzhou) + +# 3. 检查实例状态为 Running +tccli tcr DescribeInstances --Registryids '[""]' --region --output json +# expected: Status == "Running" + +# 4. 检查目标命名空间已存在 +tccli tcr DescribeNamespaces --RegistryId --region --output json +# expected: NamespaceList 含目标命名空间名称 + +# 5. 验证 CAM 权限 — 需要以下 Action 名 +# tcr:DescribeImmutableTagRules, tcr:CreateImmutableTagRules +# tcr:ModifyImmutableTagRules, tcr:DeleteImmutableTagRules +# 验证:执行 DescribeImmutableTagRules 确认权限(via cli-input-json) +tccli tcr DescribeImmutableTagRules --cli-input-json file://check.json --region --output json +# expected: exit 0,返回 Rules 数组(可为空) +``` + +> `check.json` 内容为 `{"RegistryId":"","NamespaceName":""}`。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 / 参数 | 幂等 | +|-----------|------------------|:--:| +| 选择地域(控制台顶部菜单) | `--region ` | 是 | +| 选择实例 | `--RegistryId ` | — | +| 选择命名空间 | `--NamespaceName `(via `--cli-input-json`) | — | +| 查看版本不可变规则列表 | `DescribeImmutableTagRules` | 是 | +| 新建规则 — 仓库匹配/排除 | `--Rule.RepositoryDecoration repoMatches/repoExcludes` | 否(重复创建生成多条规则) | +| 新建规则 — 版本匹配/排除 | `--Rule.TagDecoration matches/excludes` | — | +| 新建规则 — 仓库过滤模式 | `--Rule.RepositoryPattern '**'` | — | +| 新建规则 — 版本过滤模式 | `--Rule.TagPattern '**'` | — | +| 新建规则 — 暂不启用 | `--Rule.Disabled true` | — | +| 新建规则(立即购买/确认) | `CreateImmutableTagRules` | 否(重复创建生成多条规则) | +| 编辑规则 | `ModifyImmutableTagRules --RuleId --Rule '{...}'` | 是(重复修改幂等) | +| 启用/禁用规则 | `ModifyImmutableTagRules --Rule.Disabled true/false` | 是 | +| 删除规则 | `DeleteImmutableTagRules --RuleId ` | 否 | + +## 关键字段说明 + +以下说明 `CreateImmutableTagRules` / `ModifyImmutableTagRules` 的主要参数。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | 实例 ID,格式 `tcr-xxxxxxxx` | 实例不存在 → `ResourceNotFound`: "registry ... not found" | +| `NamespaceName` | String | 是 | 目标命名空间名称(非 ID)。命名空间不可变,创建规则后不可修改 | 命名空间不存在 → `ResourceNotFound.TcrResourceNotFound`: "namespace ... not found" | +| `Rule.RepositoryPattern` | String | 是 | 仓库匹配模式,支持 `*`(单级)与 `**`(多级)通配符,如 `**` 表示全部仓库 | — | +| `Rule.TagPattern` | String | 是 | Tag 匹配模式,支持 `*`(单级)与 `**`(多级)通配符,如 `v*` 匹配 `v1.0.0` | — | +| `Rule.RepositoryDecoration` | String | 是 | **仅** `repoMatches` 或 `repoExcludes`(带 `repo` 前缀)。**不是** `matches`/`excludes` | 传 `matches` → `InvalidParameter.ErrorTcrInvalidParameter`: "RepositoryDecoration: matches does not validate as in(repoMatches\|repoExcludes)" | +| `Rule.TagDecoration` | String | 是 | `matches` 或 `excludes`(**不带** `repo` 前缀) | 传 `repoMatches` → 校验失败;缺省 → "TagDecoration: non zero value required" | +| `Rule.Disabled` | Boolean | 否 | `true` = 创建后暂不启用,`false` = 立即生效(默认) | — | +| `RuleId` | Integer | 修改/删除时必填 | 规则 ID,来自 `DescribeImmutableTagRules` 输出的 `Rules[].RuleId` | 缺省 → "RuleId: non zero value required" | + +## 操作步骤 + +### 步骤1:查看已有规则 + +创建前先查看当前命名空间下是否已有规则,避免重复创建(`CreateImmutableTagRules` 不做去重,重复创建会生成多条相同规则): + +```bash +# 推荐:via --cli-input-json(兼容所有 tccli 版本) +tccli tcr DescribeImmutableTagRules \ + --cli-input-json file://describe-rule.json \ + --region \ + --output json +# expected: exit 0,返回 Rules 数组(可为空) +``` + +`describe-rule.json`: + +```json +{ + "RegistryId": "", + "NamespaceName": "" +} +``` + +**输出(命名空间尚无规则)**: + +```json +{ + "Rules": [], + "EmptyNs": null, + "Total": 0, + "RequestId": "361f7ce4-fa42-4869-949f-1392a9e7dce1" +} +``` + +**输出(命名空间已有规则)**: + +```json +{ + "Rules": [ + { + "RepositoryPattern": "**", + "TagPattern": "**", + "RepositoryDecoration": "repoMatches", + "TagDecoration": "matches", + "Disabled": false, + "RuleId": 1, + "NsName": "" + } + ], + "EmptyNs": [ + "other-ns-without-rules" + ], + "Total": 1, + "RequestId": "9be15a35-d86c-4f96-b670-f10247562a2c" +} +``` + +> `EmptyNs` 列出当前实例下尚无任何不可变规则的命名空间。若目标命名空间已有规则,先确认是否需要保留,避免重复创建。 + +### 步骤2:创建版本不可变规则 + +`CreateImmutableTagRules` 必填字段较多(`RegistryId`、`NamespaceName`、`Rule` 内 4 个字段),且部分 tccli 版本不接受 `--NamespaceName` 作为直接 CLI flag(详见 [版本差异](#版本差异namespacename-与-namespceid))。推荐使用 `--cli-input-json` 桥接,兼容性最佳。 + +#### 版本差异:NamespaceName 与 NamespaceId + +| 传参方式 | 真机实测(tccli 3.1.107.1) | 说明 | +|---------|:--:|------| +| `--cli-input-json file://x.json`(JSON 含 `NamespaceName`) | 成功 | **推荐**,所有版本通用,API 接受 `NamespaceName` | +| `--NamespaceName `(直接 flag) | 失败:`Unknown options: --NamespaceName` | 部分 tccli 版本未将 `NamespaceName` 暴露为 CLI flag | +| `--NamespaceId `(直接 flag) | 失败:`Unknown options: --NamespaceId` | 部分 tccli 版本未将 `NamespaceId` 暴露为 CLI flag | + +> **结论:** 本指南所有命令统一采用 `--cli-input-json` 传参。若你的 tccli 版本支持 `--NamespaceName` 直接 flag,也可改用内联形式(见 [步骤2 示例2](#示例2内联---rule-json-方式)),但需自行验证版本兼容性。API 层面参数名始终为 `NamespaceName`。 + +#### 场景决策表 + +| 场景 | RepositoryDecoration | RepositoryPattern | TagDecoration | TagPattern | 典型用途 | +|------|---------------------|-------------------|---------------|------------|---------| +| 保护全部仓库全部版本 | `repoMatches` | `**` | `matches` | `**` | 命名空间内所有 Tag 仅可推送一次 | +| 保护全部仓库的正式版本 | `repoMatches` | `**` | `matches` | `v*` | 所有 `v1.0.0`、`v2.3.1` 等正式版本锁定 | +| 除 latest 外全部不可变 | `repoMatches` | `**` | `excludes` | `latest` | 仅 `latest` 可覆盖,其余 Tag 锁定 | +| 仅保护特定仓库 | `repoMatches` | `prod-*` | `matches` | `*` | `prod-*` 仓库内全部 Tag 锁定 | +| 排除测试仓库 | `repoExcludes` | `test-*` | `matches` | `*` | 除 `test-*` 外的全部 Tag 锁定 | +| 排除测试仓库的 latest | `repoExcludes` | `test-*` | `excludes` | `latest` | 除 `test-*` 仓库的 `latest` 外全部锁定 | + +> `RepositoryDecoration` 选 `repoMatches` 表示**匹配**指定模式的仓库生效,选 `repoExcludes` 表示**排除**指定模式的仓库。与 `RepositoryPattern` 通配符(`*` 单级、`**` 多级)组合使用。`TagDecoration` 语义同理,作用于 `TagPattern`。 + +#### 示例1:最小创建(保护全部仓库全部版本,via cli-input-json) + +仅含必填字段,匹配全部仓库的全部 Tag: + +`immutable-rule-minimal.json`: + +```json +{ + "RegistryId": "", + "NamespaceName": "", + "Rule": { + "RepositoryPattern": "**", + "TagPattern": "**", + "RepositoryDecoration": "repoMatches", + "TagDecoration": "matches", + "Disabled": false + } +} +``` + +```bash +tccli tcr CreateImmutableTagRules \ + --cli-input-json file://immutable-rule-minimal.json \ + --region \ + --output json +# expected: exit 0,返回 RequestId +``` + +**输出**: + +```json +{ + "RequestId": "5dbbfb07-595c-4e5f-9150-18b2fc741a4e" +} +``` + +> `CreateImmutableTagRules` 仅返回 `RequestId`,不返回 `RuleId`。需通过 `DescribeImmutableTagRules` 查询获取 `RuleId`(见 [步骤3](#步骤3查询已创建的规则))。 + +#### 示例2:内联 `--Rule` JSON 方式 + +若你的 tccli 版本支持 `--NamespaceName` / `--Rule` 直接 flag,可使用内联形式。**注意:tccli 3.1.107.1 实测不支持 `--NamespaceName` 直接 flag,此方式需自行验证版本兼容性。** + +```bash +tccli tcr CreateImmutableTagRules \ + --RegistryId \ + --NamespaceName \ + --Rule '{"RepositoryPattern":"**","TagPattern":"**","RepositoryDecoration":"repoMatches","TagDecoration":"matches","Disabled":false}' \ + --region +# expected: exit 0,返回 RequestId +``` + +> 内联 `--Rule` 的 JSON 必须用单引号包裹,内部双引号转义。若 shell 为 zsh,单引号内 JSON 无需额外转义。 + +#### 示例3:增强创建(除 latest 外全部锁定 + 暂不启用) + +`immutable-rule-enhanced.json`: + +```json +{ + "RegistryId": "", + "NamespaceName": "", + "Rule": { + "RepositoryPattern": "**", + "TagPattern": "latest", + "RepositoryDecoration": "repoMatches", + "TagDecoration": "excludes", + "Disabled": true + } +} +``` + +```bash +tccli tcr CreateImmutableTagRules \ + --cli-input-json file://immutable-rule-enhanced.json \ + --region \ + --output json +# expected: exit 0,返回 RequestId +``` + +> `Disabled: true` 下规则创建但不生效,适合先建立规则、确认配置无误后再手动启用(见 [步骤5](#步骤5启用禁用规则))。 + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | 实例 ID | 格式 `tcr-xxxxxxxx` | `tccli tcr DescribeInstances --region ` | +| `` | 命名空间名称 | 已存在于实例内 | `tccli tcr DescribeNamespaces --RegistryId --region ` | +| `` | 地域 | 如 `ap-guangzhou` | `tccli tcr DescribeRegions` 查看可用地域 | +| `` | 规则 ID | 整数,来自查询结果 | `tccli tcr DescribeImmutableTagRules` 输出的 `Rules[].RuleId` | + +### 步骤3:查询已创建的规则 + +创建后查询,获取 `RuleId` 供后续修改/删除使用: + +```bash +tccli tcr DescribeImmutableTagRules \ + --cli-input-json file://describe-rule.json \ + --region \ + --output json +# expected: exit 0,Rules 数组含新规则,RuleId 非零 +``` + +**输出**: + +```json +{ + "Rules": [ + { + "RepositoryPattern": "**", + "TagPattern": "**", + "RepositoryDecoration": "repoMatches", + "TagDecoration": "matches", + "Disabled": false, + "RuleId": 1, + "NsName": "" + } + ], + "EmptyNs": [ + "other-ns-without-rules" + ], + "Total": 1, + "RequestId": "9be15a35-d86c-4f96-b670-f10247562a2c" +} +``` + +> 记录返回的 `RuleId`(示例 `1`),后续修改和删除均依赖此 ID。`Rules` 数组每条规则字段:`RuleId`、`NsName`、`RepositoryPattern`、`TagPattern`、`RepositoryDecoration`、`TagDecoration`、`Disabled`。 + +仅提取 `RuleId` 与关键字段: + +```bash +tccli tcr DescribeImmutableTagRules \ + --cli-input-json file://describe-rule.json \ + --region \ + --output json \ + --filter "Rules[0].{RuleId:RuleId,Repo:RepositoryPattern,Tag:TagPattern,Disabled:Disabled}" +# expected: {"RuleId": 1, "Repo": "**", "Tag": "**", "Disabled": false} +``` + +### 步骤4:修改规则 + +通过 `ModifyImmutableTagRules` 更新规则配置。需同时传入 `RuleId` 和完整 `Rule` 对象(全量覆盖,非增量): + +`immutable-rule-modify.json`: + +```json +{ + "RegistryId": "", + "NamespaceName": "", + "RuleId": 1, + "Rule": { + "RepositoryPattern": "**", + "TagPattern": "stable-*", + "RepositoryDecoration": "repoMatches", + "TagDecoration": "matches", + "Disabled": false + } +} +``` + +```bash +tccli tcr ModifyImmutableTagRules \ + --cli-input-json file://immutable-rule-modify.json \ + --region \ + --output json +# expected: exit 0,返回 RequestId +``` + +**输出**: + +```json +{ + "RequestId": "c3d7b9d0-ccad-407f-84dc-a015cbeaf7dc" +} +``` + +> - `ModifyImmutableTagRules` 为全量覆盖:`Rule` 对象必须包含全部必填字段(`RepositoryPattern`、`TagPattern`、`RepositoryDecoration`、`TagDecoration`),遗漏字段会被清空。 +> - 不支持修改规则的生效命名空间(`NamespaceName` 不可变)。如需更换命名空间,需删除原规则后在新命名空间重新创建。 +> - 内联 `--Rule` 方式(需版本支持 `--NamespaceName` flag):`tccli tcr ModifyImmutableTagRules --RegistryId --NamespaceName --RuleId --Rule '{"RepositoryPattern":"**","TagPattern":"stable-*","RepositoryDecoration":"repoMatches","TagDecoration":"matches","Disabled":false}' --region ` + +### 步骤5:启用/禁用规则 + +通过 `ModifyImmutableTagRules` 切换 `Disabled` 字段。以下为禁用规则(`Disabled: true`): + +`immutable-rule-toggle.json`: + +```json +{ + "RegistryId": "", + "NamespaceName": "", + "RuleId": 1, + "Rule": { + "RepositoryPattern": "**", + "TagPattern": "stable-*", + "RepositoryDecoration": "repoMatches", + "TagDecoration": "matches", + "Disabled": true + } +} +``` + +```bash +tccli tcr ModifyImmutableTagRules \ + --cli-input-json file://immutable-rule-toggle.json \ + --region \ + --output json +# expected: exit 0,返回 RequestId +``` + +> 切换 `Disabled` 时仍需提供完整 `Rule` 对象(含原有 `RepositoryPattern`/`TagPattern` 等),否则字段会被清空。启用规则改为 `"Disabled": false` 即可。 + +### 步骤6:删除规则 + +```bash +tccli tcr DeleteImmutableTagRules \ + --cli-input-json file://delete-rule.json \ + --region \ + --output json +# expected: exit 0,返回 RequestId +``` + +`delete-rule.json`: + +```json +{ + "RegistryId": "", + "NamespaceName": "", + "RuleId": 1 +} +``` + +**输出**: + +```json +{ + "RequestId": "faf792f4-452b-493d-975a-0ec97bb1f035" +} +``` + +| 参数 | 类型 | 必填 | 说明 | +|------|------|:--:|------| +| `RegistryId` | String | 是 | 实例 ID | +| `NamespaceName` | String | 是 | 命名空间名称 | +| `RuleId` | Integer | 是 | 规则 ID,来自 `DescribeImmutableTagRules` 输出的 `RuleId` 字段 | + +> 内联方式(需版本支持 `--NamespaceName` flag):`tccli tcr DeleteImmutableTagRules --RegistryId --NamespaceName --RuleId --region ` + +### 步骤7:验证规则已生效(数据面) + +规则创建并启用后,推送已受保护版本的镜像,预期被拒绝: + +```bash +# expected: DENIED (rule blocks re-push of same tag) +docker tag //: +docker push //: +``` + +> `docker push` 返回 `DENIED` 即说明不可变规则已生效。**首次推送不受影响**,仅对同一 Tag 的二次及后续推送生效。 + +## 验证 + +### 控制面(tccli) + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 规则已创建 | `DescribeImmutableTagRules --cli-input-json file://describe-rule.json --region ` | `Rules` 数组含目标规则,`RuleId` 非零,`Total >= 1` | +| 规则字段正确 | 同上 | `RepositoryDecoration`、`TagDecoration`、`RepositoryPattern`、`TagPattern` 与创建参数一致 | +| 规则生效状态 | 同上,取 `Disabled` | `false`(若创建时未指定 `Disabled: true`) | +| 修改后字段更新 | 同上,取 `TagPattern` 等 | 与 `ModifyImmutableTagRules` 传入值一致(如 `stable-*`) | +| 规则已删除 | 同上 | `Total` 减少,`Rules` 数组不再含该 `RuleId` | +| 规则可删除 | `DeleteImmutableTagRules --cli-input-json file://delete-rule.json --region ` | 返回 `RequestId`,再 `Describe` 确认 `Total` 减少 | + +### 数据面 + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 受保护 Tag 二次推送被拒 | `docker push //:` | 返回 `DENIED` | +| 受保护 Tag 首次推送正常 | 同上(首次) | 推送成功 | +| 未匹配 Tag 推送正常 | `docker push //:` | 推送成功 | + +> 获取 `RegistryDomain`:`tccli tcr DescribeInstances --Registryids '[""]' --region --filter "Registries[0].PublicDomain"`。 + +## 清理 + +> **警告:** 删除不可变规则后,对应 Tag 的保护立即失效,后续推送相同 Tag 将正常覆盖镜像版本。删除前确认生产环境无依赖该规则的版本保护需求。 + +### 1. 清理前状态检查 + +```bash +tccli tcr DescribeImmutableTagRules \ + --cli-input-json file://describe-rule.json \ + --region \ + --output json +# expected: 确认待删除规则的 RuleId、NsName、RepositoryPattern、TagPattern +``` + +### 2. 删除规则 + +```bash +tccli tcr DeleteImmutableTagRules \ + --cli-input-json file://delete-rule.json \ + --region \ + --output json +# expected: exit 0,返回 RequestId +``` + +### 3. 验证已删除 + +```bash +tccli tcr DescribeImmutableTagRules \ + --cli-input-json file://describe-rule.json \ + --region \ + --output json +# expected: Total 减少,Rules 数组中不再包含该 RuleId 的条目 +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `CreateImmutableTagRules` 返回 `InvalidParameter.ErrorTcrInvalidParameter`: "RepositoryDecoration: matches does not validate as in(repoMatches\|repoExcludes)" | 检查 `Rule.RepositoryDecoration` 值 | `RepositoryDecoration` 必须带 `repo` 前缀,裸 `matches` 不合法 | 改为 `"RepositoryDecoration": "repoMatches"`(匹配)或 `"repoExcludes"`(排除) | +| `CreateImmutableTagRules` 返回 `InvalidParameter.ErrorTcrInvalidParameter`(TagDecoration 相关) | 检查 `Rule.TagDecoration` 值 | `TagDecoration` 仅接受 `matches` 或 `excludes`(**不带** `repo` 前缀) | 使用 `"TagDecoration": "matches"` 或 `"excludes"` | +| `CreateImmutableTagRules` 返回 "TagDecoration: non zero value required" | 检查 `Rule` JSON 是否包含 `TagDecoration` | `TagDecoration` 为**必填**字段,不可省略 | 添加 `"TagDecoration": "matches"` 或 `"excludes"` | +| `CreateImmutableTagRules` 返回 "NamespaceName: non zero value required" | 检查 JSON 顶层是否传入 `NamespaceName` | `NamespaceName` 为**必填**顶层参数 | 在 JSON 顶层添加 `"NamespaceName": ""` | +| `ModifyImmutableTagRules` 返回 "RuleId: non zero value required" | 检查 JSON 顶层是否含 `RuleId` 字段 | `ModifyImmutableTagRules` 需 `RuleId` 指定目标规则 | 添加 `"RuleId": <数字ID>`(来自 `DescribeImmutableTagRules` 输出) | +| `CreateImmutableTagRules` 返回 `ResourceNotFound`: "registry ... not found" | `tccli tcr DescribeInstances --region ` 确认实例存在 | `RegistryId` 错误或实例已销毁 | 核对 `RegistryId`,确认实例状态为 `Running` | +| `CreateImmutableTagRules` 返回 `ResourceNotFound.TcrResourceNotFound`: "namespace ... not found" | `tccli tcr DescribeNamespaces --RegistryId --region ` 确认命名空间存在 | `NamespaceName` 错误或命名空间未创建 | 核对命名空间名称,参考 [管理命名空间](../../image-creation/namespace) 创建 | +| CLI 返回 `Unknown options: --NamespaceName` | 检查 tccli 版本:`tccli --version` | 部分 tccli 版本未将 `NamespaceName` 暴露为直接 CLI flag | 改用 `--cli-input-json file://x.json` 传参(JSON 内含 `NamespaceName`) | +| CLI 返回 `Unknown options: --NamespaceId` | 同上 | 部分 tccli 版本未将 `NamespaceId` 暴露为直接 CLI flag | 同上,改用 `--cli-input-json` | +| CAM 权限拒绝 (`UnauthorizedOperation`) | — | 子账号缺少 TCR 不可变规则权限 | 主账号授予 `tcr:CreateImmutableTagRules` / `tcr:ModifyImmutableTagRules` / `tcr:DescribeImmutableTagRules` / `tcr:DeleteImmutableTagRules` | +| `CreateImmutableTagRules` 返回 `InternalError` / `FailedOperation` | 保留返回的 `RequestId` | 云端服务暂时不可用 | 稍后重试(间隔 30 秒以上);若持续失败,凭 `RequestId` [在线咨询](https://cloud.tencent.com/online-service?from=doc_1141) | + +### 行为异常(规则创建成功但未按预期生效) + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 规则已创建但推送相同 Tag 未被拒绝 | `DescribeImmutableTagRules` 检查 `Disabled` 字段 | `Disabled: true`,规则处于禁用状态 | `ModifyImmutableTagRules` 将 `Disabled` 改为 `false` | +| 规则已启用但特定 Tag 仍可覆盖 | `DescribeImmutableTagRules` 对比 `RepositoryPattern`/`TagPattern` 与实际仓库名和 Tag | 仓库名或 Tag 不匹配规则的过滤模式(如 `v*` 不匹配 `V1.0` 大小写;`prod-*` 不匹配 `prod/app`) | 调整 `RepositoryPattern` 或 `TagPattern` 覆盖目标仓库和 Tag | +| 重复创建生成多条相同规则 | `DescribeImmutableTagRules` 检查 `Total` 与规则列表 | `CreateImmutableTagRules` 不做去重,重复调用生成多条规则 | 创建前先 `Describe` 检查;多余规则用 `DeleteImmutableTagRules` 逐条删除 | +| 规则重叠时行为不明确 | `DescribeImmutableTagRules` 列出所有规则,逐条对比覆盖范围 | 多条规则重叠时以最严格条件为准(任一规则阻断即拒绝推送) | 确认重叠规则组合符合预期;删除多余规则 | +| `DescribeImmutableTagRules` 返回 `EmptyNs: ["ns"]` | — | 该命名空间尚无不可变规则,为正常状态 | 按 [步骤2](#步骤2创建版本不可变规则) 创建规则即可 | +| 修改规则后某些字段被清空 | `DescribeImmutableTagRules` 对比修改前后字段 | `ModifyImmutableTagRules` 为全量覆盖,`Rule` 对象遗漏字段会被清空 | 修改时 `Rule` 必须包含全部必填字段,建议从查询结果复制完整对象再改 | + +### 排障记录:RepositoryDecoration 枚举首次失败 + +> **真机排障记录:** 首次调用 `CreateImmutableTagRules` 时传入 `"RepositoryDecoration": "matches"`,API 返回 `InvalidParameter.ErrorTcrInvalidParameter`(RequestId: `c351d168-965a-481d-8760-770681a5ce47`),错误信息为: +> +> ```bash +> Rule.RepositoryDecoration: matches does not validate as in(repoMatches|repoExcludes) +> ``` +> +> 修正为 `"RepositoryDecoration": "repoMatches"` 后创建成功(RequestId: `bce40f14-44c5-4626-a40b-0de9ab0dc476`)。 +> +> **教训:** `RepositoryDecoration` 和 `TagDecoration` 的枚举值不对称——前者带 `repo` 前缀(`repoMatches`/`repoExcludes`),后者不带(`matches`/`excludes`)。创建前务必对照 [场景决策表](#场景决策表) 确认枚举值,切勿凭直觉填写。 + +## 下一步 + +- [容器镜像安全扫描](../vulnerability-scan) — 对镜像进行漏洞扫描与修复 +- [容器镜像签名](../image-signing) — 镜像签名与验签 +- [按策略自动删除镜像版本](../../image-cleanup/auto-delete) — 自动清理过期镜像版本 +- [触发器(Webhook)](../../devops/webhook) — 镜像推送/删除自动触发 CI/CD +- [环境准备](../../../index.md) — 返回 TCR 工具链入口 + +## 控制台替代 + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 选择实例 → 左侧导航栏 **版本管理** > **版本不可变** → 单击 **新建规则**,选择命名空间,配置仓库匹配(匹配/排除)和版本匹配(匹配/排除),填写过滤模式,确认创建。在规则列表行可启用/禁用、编辑或删除规则。 diff --git a/src/content/docs/cli/tcr/ops/image-security/immutable-tags/examples/immutable-rule-custom.json b/src/content/docs/cli/tcr/ops/image-security/immutable-tags/examples/immutable-rule-custom.json new file mode 100644 index 0000000..c74b1fd --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-security/immutable-tags/examples/immutable-rule-custom.json @@ -0,0 +1,11 @@ +{ + "RegistryId": "tcr-example", + "NamespaceName": "team-b", + "Rule": { + "RepositoryPattern": "prod-*", + "TagPattern": "v*", + "RepositoryDecoration": "repoMatches", + "TagDecoration": "matches", + "Disabled": false + } +} diff --git a/src/content/docs/cli/tcr/ops/image-security/immutable-tags/examples/immutable-rule-latest.json b/src/content/docs/cli/tcr/ops/image-security/immutable-tags/examples/immutable-rule-latest.json new file mode 100644 index 0000000..87ca297 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-security/immutable-tags/examples/immutable-rule-latest.json @@ -0,0 +1,11 @@ +{ + "RegistryId": "tcr-example", + "NamespaceName": "team-a", + "Rule": { + "RepositoryPattern": "**", + "TagPattern": "latest", + "RepositoryDecoration": "repoMatches", + "TagDecoration": "excludes", + "Disabled": false + } +} diff --git a/src/content/docs/cli/tcr/ops/image-security/immutable-tags/examples/immutable-rule-modify.json b/src/content/docs/cli/tcr/ops/image-security/immutable-tags/examples/immutable-rule-modify.json new file mode 100644 index 0000000..399a06f --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-security/immutable-tags/examples/immutable-rule-modify.json @@ -0,0 +1,12 @@ +{ + "RegistryId": "tcr-example", + "NamespaceName": "team-b", + "RuleId": 2, + "Rule": { + "RepositoryPattern": "prod-*", + "TagPattern": "stable-*", + "RepositoryDecoration": "repoMatches", + "TagDecoration": "matches", + "Disabled": false + } +} diff --git a/src/content/docs/cli/tcr/ops/image-security/immutable-tags/examples/immutable-rule-toggle.json b/src/content/docs/cli/tcr/ops/image-security/immutable-tags/examples/immutable-rule-toggle.json new file mode 100644 index 0000000..6b396c6 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-security/immutable-tags/examples/immutable-rule-toggle.json @@ -0,0 +1,12 @@ +{ + "RegistryId": "tcr-example", + "NamespaceName": "team-a", + "RuleId": 1, + "Rule": { + "RepositoryPattern": "**", + "TagPattern": "latest", + "RepositoryDecoration": "repoMatches", + "TagDecoration": "excludes", + "Disabled": true + } +} diff --git a/src/content/docs/cli/tcr/ops/image-security/immutable-tags/immutable-tags-tcrctl.md b/src/content/docs/cli/tcr/ops/image-security/immutable-tags/immutable-tags-tcrctl.md new file mode 100644 index 0000000..9945d02 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-security/immutable-tags/immutable-tags-tcrctl.md @@ -0,0 +1,10 @@ +--- +title: "镜像版本不可变(tcrctl)" +description: "P1 尚未交付。参见同目录下 tccli 操作./tccli%20操作.md(P0)。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> 页型 **T1(P1 stub)** · 对照官方:[镜像版本不可变](https://cloud.tencent.com/document/product/1141/58200) · page_id `58200` + +P1 尚未交付。参见同目录下 tccli 操作(P0)。 diff --git a/src/content/docs/cli/tcr/ops/image-security/immutable-tags/immutable-tags.md b/src/content/docs/cli/tcr/ops/image-security/immutable-tags/immutable-tags.md new file mode 100644 index 0000000..1cb66fa --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-security/immutable-tags/immutable-tags.md @@ -0,0 +1,545 @@ +--- +title: "镜像版本不可变(tccli)" +description: "· page_id `58200`" +--- + +> 对照官方:[镜像版本不可变](https://cloud.tencent.com/document/product/1141/58200) · page_id `58200` + +## 概述 + +TCR 企业版支持对容器镜像版本启用不可变(immutable)保护。启用后,同一命名空间下匹配规则的镜像 Tag 仅能被成功推送一次,后续对同一 Tag 的 `docker push` 将被拒绝,有效防止生产环境中的版本覆盖事故。 + +通过 `tccli tcr CreateImmutableTagRules` 创建规则,通过 `RepositoryDecoration` 与 `TagDecoration` 两个维度组合控制规则生效范围: + +| 维度 | 枚举值 | 语义 | +|------|--------|------| +| `RepositoryDecoration` | `repoMatches` | **匹配**指定仓库模式生效(带 `repo` 前缀) | +| | `repoExcludes` | **排除**指定仓库模式生效(带 `repo` 前缀) | +| `TagDecoration` | `matches` | **匹配**指定 Tag 模式生效(不带 `repo` 前缀) | +| | `excludes` | **排除**指定 Tag 模式生效(不带 `repo` 前缀) | + +> **关键易错点(务必先读):** `RepositoryDecoration` 的枚举值为 `repoMatches`/`repoExcludes`(带 `repo` 前缀),**不是** `matches`/`excludes`。而 `TagDecoration` 的枚举值为 `matches`/`excludes`(**不带** `repo` 前缀)。两者不对称,传入裸 `matches` 给 `RepositoryDecoration` 将触发校验失败。详见 [排障](#排障)。 + +规则创建后默认立即生效(`Disabled: false`),同一命名空间可创建多条规则,重叠时以最严格条件为准(任一规则阻断即拒绝推送)。 + +## 前置条件 + +- 已 [创建企业版实例](../../create),实例状态为 `Running`。 +- 已配置 `tccli` 凭证(参见 [环境准备](../../../index.md))。 +- 实例内已有目标命名空间(参见 [管理命名空间](../../image-creation/namespace))。 +- 若使用子账号操作,需授予 `tcr:CreateImmutableTagRules`、`tcr:DescribeImmutableTagRules`、`tcr:ModifyImmutableTagRules`、`tcr:DeleteImmutableTagRules` 权限,参见 [企业版授权方案示例](https://cloud.tencent.com/document/product/1141/41417)。 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0(真机实测 3.1.107.1) + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域(如 ap-guangzhou) + +# 3. 检查实例状态为 Running +tccli tcr DescribeInstances --Registryids '[""]' --region --output json +# expected: Status == "Running" + +# 4. 检查目标命名空间已存在 +tccli tcr DescribeNamespaces --RegistryId --region --output json +# expected: NamespaceList 含目标命名空间名称 + +# 5. 验证 CAM 权限 — 需要以下 Action 名 +# tcr:DescribeImmutableTagRules, tcr:CreateImmutableTagRules +# tcr:ModifyImmutableTagRules, tcr:DeleteImmutableTagRules +# 验证:执行 DescribeImmutableTagRules 确认权限(via cli-input-json) +tccli tcr DescribeImmutableTagRules --cli-input-json file://check.json --region --output json +# expected: exit 0,返回 Rules 数组(可为空) +``` + +> `check.json` 内容为 `{"RegistryId":"","NamespaceName":""}`。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 / 参数 | 幂等 | +|-----------|------------------|:--:| +| 选择地域(控制台顶部菜单) | `--region ` | 是 | +| 选择实例 | `--RegistryId ` | — | +| 选择命名空间 | `--NamespaceName `(via `--cli-input-json`) | — | +| 查看版本不可变规则列表 | `DescribeImmutableTagRules` | 是 | +| 新建规则 — 仓库匹配/排除 | `--Rule.RepositoryDecoration repoMatches/repoExcludes` | 否(重复创建生成多条规则) | +| 新建规则 — 版本匹配/排除 | `--Rule.TagDecoration matches/excludes` | — | +| 新建规则 — 仓库过滤模式 | `--Rule.RepositoryPattern '**'` | — | +| 新建规则 — 版本过滤模式 | `--Rule.TagPattern '**'` | — | +| 新建规则 — 暂不启用 | `--Rule.Disabled true` | — | +| 新建规则(立即购买/确认) | `CreateImmutableTagRules` | 否(重复创建生成多条规则) | +| 编辑规则 | `ModifyImmutableTagRules --RuleId --Rule '{...}'` | 是(重复修改幂等) | +| 启用/禁用规则 | `ModifyImmutableTagRules --Rule.Disabled true/false` | 是 | +| 删除规则 | `DeleteImmutableTagRules --RuleId ` | 否 | + +## 关键字段说明 + +以下说明 `CreateImmutableTagRules` / `ModifyImmutableTagRules` 的主要参数。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | 实例 ID,格式 `tcr-xxxxxxxx` | 实例不存在 → `ResourceNotFound`: "registry ... not found" | +| `NamespaceName` | String | 是 | 目标命名空间名称(非 ID)。命名空间不可变,创建规则后不可修改 | 命名空间不存在 → `ResourceNotFound.TcrResourceNotFound`: "namespace ... not found" | +| `Rule.RepositoryPattern` | String | 是 | 仓库匹配模式,支持 `*`(单级)与 `**`(多级)通配符,如 `**` 表示全部仓库 | — | +| `Rule.TagPattern` | String | 是 | Tag 匹配模式,支持 `*`(单级)与 `**`(多级)通配符,如 `v*` 匹配 `v1.0.0` | — | +| `Rule.RepositoryDecoration` | String | 是 | **仅** `repoMatches` 或 `repoExcludes`(带 `repo` 前缀)。**不是** `matches`/`excludes` | 传 `matches` → `InvalidParameter.ErrorTcrInvalidParameter`: "RepositoryDecoration: matches does not validate as in(repoMatches\|repoExcludes)" | +| `Rule.TagDecoration` | String | 是 | `matches` 或 `excludes`(**不带** `repo` 前缀) | 传 `repoMatches` → 校验失败;缺省 → "TagDecoration: non zero value required" | +| `Rule.Disabled` | Boolean | 否 | `true` = 创建后暂不启用,`false` = 立即生效(默认) | — | +| `RuleId` | Integer | 修改/删除时必填 | 规则 ID,来自 `DescribeImmutableTagRules` 输出的 `Rules[].RuleId` | 缺省 → "RuleId: non zero value required" | + +## 操作步骤 + +### 步骤1:查看已有规则 + +创建前先查看当前命名空间下是否已有规则,避免重复创建(`CreateImmutableTagRules` 不做去重,重复创建会生成多条相同规则): + +```bash +# 推荐:via --cli-input-json(兼容所有 tccli 版本) +tccli tcr DescribeImmutableTagRules \ + --cli-input-json file://describe-rule.json \ + --region \ + --output json +# expected: exit 0,返回 Rules 数组(可为空) +``` + +`describe-rule.json`: + +```json +{ + "RegistryId": "", + "NamespaceName": "" +} +``` + +**输出(命名空间尚无规则)**: + +```json +{ + "Rules": [], + "EmptyNs": null, + "Total": 0, + "RequestId": "361f7ce4-fa42-4869-949f-1392a9e7dce1" +} +``` + +**输出(命名空间已有规则)**: + +```json +{ + "Rules": [ + { + "RepositoryPattern": "**", + "TagPattern": "**", + "RepositoryDecoration": "repoMatches", + "TagDecoration": "matches", + "Disabled": false, + "RuleId": 1, + "NsName": "" + } + ], + "EmptyNs": [ + "other-ns-without-rules" + ], + "Total": 1, + "RequestId": "9be15a35-d86c-4f96-b670-f10247562a2c" +} +``` + +> `EmptyNs` 列出当前实例下尚无任何不可变规则的命名空间。若目标命名空间已有规则,先确认是否需要保留,避免重复创建。 + +### 步骤2:创建版本不可变规则 + +`CreateImmutableTagRules` 必填字段较多(`RegistryId`、`NamespaceName`、`Rule` 内 4 个字段),且部分 tccli 版本不接受 `--NamespaceName` 作为直接 CLI flag(详见 [版本差异](#版本差异namespacename-与-namespceid))。推荐使用 `--cli-input-json` 桥接,兼容性最佳。 + +#### 版本差异:NamespaceName 与 NamespaceId + +| 传参方式 | 真机实测(tccli 3.1.107.1) | 说明 | +|---------|:--:|------| +| `--cli-input-json file://x.json`(JSON 含 `NamespaceName`) | 成功 | **推荐**,所有版本通用,API 接受 `NamespaceName` | +| `--NamespaceName `(直接 flag) | 失败:`Unknown options: --NamespaceName` | 部分 tccli 版本未将 `NamespaceName` 暴露为 CLI flag | +| `--NamespaceId `(直接 flag) | 失败:`Unknown options: --NamespaceId` | 部分 tccli 版本未将 `NamespaceId` 暴露为 CLI flag | + +> **结论:** 本指南所有命令统一采用 `--cli-input-json` 传参。若你的 tccli 版本支持 `--NamespaceName` 直接 flag,也可改用内联形式(见 [步骤2 示例2](#示例2内联---rule-json-方式)),但需自行验证版本兼容性。API 层面参数名始终为 `NamespaceName`。 + +#### 场景决策表 + +| 场景 | RepositoryDecoration | RepositoryPattern | TagDecoration | TagPattern | 典型用途 | +|------|---------------------|-------------------|---------------|------------|---------| +| 保护全部仓库全部版本 | `repoMatches` | `**` | `matches` | `**` | 命名空间内所有 Tag 仅可推送一次 | +| 保护全部仓库的正式版本 | `repoMatches` | `**` | `matches` | `v*` | 所有 `v1.0.0`、`v2.3.1` 等正式版本锁定 | +| 除 latest 外全部不可变 | `repoMatches` | `**` | `excludes` | `latest` | 仅 `latest` 可覆盖,其余 Tag 锁定 | +| 仅保护特定仓库 | `repoMatches` | `prod-*` | `matches` | `*` | `prod-*` 仓库内全部 Tag 锁定 | +| 排除测试仓库 | `repoExcludes` | `test-*` | `matches` | `*` | 除 `test-*` 外的全部 Tag 锁定 | +| 排除测试仓库的 latest | `repoExcludes` | `test-*` | `excludes` | `latest` | 除 `test-*` 仓库的 `latest` 外全部锁定 | + +> `RepositoryDecoration` 选 `repoMatches` 表示**匹配**指定模式的仓库生效,选 `repoExcludes` 表示**排除**指定模式的仓库。与 `RepositoryPattern` 通配符(`*` 单级、`**` 多级)组合使用。`TagDecoration` 语义同理,作用于 `TagPattern`。 + +#### 示例1:最小创建(保护全部仓库全部版本,via cli-input-json) + +仅含必填字段,匹配全部仓库的全部 Tag: + +`immutable-rule-minimal.json`: + +```json +{ + "RegistryId": "", + "NamespaceName": "", + "Rule": { + "RepositoryPattern": "**", + "TagPattern": "**", + "RepositoryDecoration": "repoMatches", + "TagDecoration": "matches", + "Disabled": false + } +} +``` + +```bash +tccli tcr CreateImmutableTagRules \ + --cli-input-json file://immutable-rule-minimal.json \ + --region \ + --output json +# expected: exit 0,返回 RequestId +``` + +**输出**: + +```json +{ + "RequestId": "5dbbfb07-595c-4e5f-9150-18b2fc741a4e" +} +``` + +> `CreateImmutableTagRules` 仅返回 `RequestId`,不返回 `RuleId`。需通过 `DescribeImmutableTagRules` 查询获取 `RuleId`(见 [步骤3](#步骤3查询已创建的规则))。 + +#### 示例2:内联 `--Rule` JSON 方式 + +若你的 tccli 版本支持 `--NamespaceName` / `--Rule` 直接 flag,可使用内联形式。**注意:tccli 3.1.107.1 实测不支持 `--NamespaceName` 直接 flag,此方式需自行验证版本兼容性。** + +```bash +tccli tcr CreateImmutableTagRules \ + --RegistryId \ + --NamespaceName \ + --Rule '{"RepositoryPattern":"**","TagPattern":"**","RepositoryDecoration":"repoMatches","TagDecoration":"matches","Disabled":false}' \ + --region +# expected: exit 0,返回 RequestId +``` + +> 内联 `--Rule` 的 JSON 必须用单引号包裹,内部双引号转义。若 shell 为 zsh,单引号内 JSON 无需额外转义。 + +#### 示例3:增强创建(除 latest 外全部锁定 + 暂不启用) + +`immutable-rule-enhanced.json`: + +```json +{ + "RegistryId": "", + "NamespaceName": "", + "Rule": { + "RepositoryPattern": "**", + "TagPattern": "latest", + "RepositoryDecoration": "repoMatches", + "TagDecoration": "excludes", + "Disabled": true + } +} +``` + +```bash +tccli tcr CreateImmutableTagRules \ + --cli-input-json file://immutable-rule-enhanced.json \ + --region \ + --output json +# expected: exit 0,返回 RequestId +``` + +> `Disabled: true` 下规则创建但不生效,适合先建立规则、确认配置无误后再手动启用(见 [步骤5](#步骤5启用禁用规则))。 + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | 实例 ID | 格式 `tcr-xxxxxxxx` | `tccli tcr DescribeInstances --region ` | +| `` | 命名空间名称 | 已存在于实例内 | `tccli tcr DescribeNamespaces --RegistryId --region ` | +| `` | 地域 | 如 `ap-guangzhou` | `tccli tcr DescribeRegions` 查看可用地域 | +| `` | 规则 ID | 整数,来自查询结果 | `tccli tcr DescribeImmutableTagRules` 输出的 `Rules[].RuleId` | + +### 步骤3:查询已创建的规则 + +创建后查询,获取 `RuleId` 供后续修改/删除使用: + +```bash +tccli tcr DescribeImmutableTagRules \ + --cli-input-json file://describe-rule.json \ + --region \ + --output json +# expected: exit 0,Rules 数组含新规则,RuleId 非零 +``` + +**输出**: + +```json +{ + "Rules": [ + { + "RepositoryPattern": "**", + "TagPattern": "**", + "RepositoryDecoration": "repoMatches", + "TagDecoration": "matches", + "Disabled": false, + "RuleId": 1, + "NsName": "" + } + ], + "EmptyNs": [ + "other-ns-without-rules" + ], + "Total": 1, + "RequestId": "9be15a35-d86c-4f96-b670-f10247562a2c" +} +``` + +> 记录返回的 `RuleId`(示例 `1`),后续修改和删除均依赖此 ID。`Rules` 数组每条规则字段:`RuleId`、`NsName`、`RepositoryPattern`、`TagPattern`、`RepositoryDecoration`、`TagDecoration`、`Disabled`。 + +仅提取 `RuleId` 与关键字段: + +```bash +tccli tcr DescribeImmutableTagRules \ + --cli-input-json file://describe-rule.json \ + --region \ + --output json \ + --filter "Rules[0].{RuleId:RuleId,Repo:RepositoryPattern,Tag:TagPattern,Disabled:Disabled}" +# expected: {"RuleId": 1, "Repo": "**", "Tag": "**", "Disabled": false} +``` + +### 步骤4:修改规则 + +通过 `ModifyImmutableTagRules` 更新规则配置。需同时传入 `RuleId` 和完整 `Rule` 对象(全量覆盖,非增量): + +`immutable-rule-modify.json`: + +```json +{ + "RegistryId": "", + "NamespaceName": "", + "RuleId": 1, + "Rule": { + "RepositoryPattern": "**", + "TagPattern": "stable-*", + "RepositoryDecoration": "repoMatches", + "TagDecoration": "matches", + "Disabled": false + } +} +``` + +```bash +tccli tcr ModifyImmutableTagRules \ + --cli-input-json file://immutable-rule-modify.json \ + --region \ + --output json +# expected: exit 0,返回 RequestId +``` + +**输出**: + +```json +{ + "RequestId": "c3d7b9d0-ccad-407f-84dc-a015cbeaf7dc" +} +``` + +> - `ModifyImmutableTagRules` 为全量覆盖:`Rule` 对象必须包含全部必填字段(`RepositoryPattern`、`TagPattern`、`RepositoryDecoration`、`TagDecoration`),遗漏字段会被清空。 +> - 不支持修改规则的生效命名空间(`NamespaceName` 不可变)。如需更换命名空间,需删除原规则后在新命名空间重新创建。 +> - 内联 `--Rule` 方式(需版本支持 `--NamespaceName` flag):`tccli tcr ModifyImmutableTagRules --RegistryId --NamespaceName --RuleId --Rule '{"RepositoryPattern":"**","TagPattern":"stable-*","RepositoryDecoration":"repoMatches","TagDecoration":"matches","Disabled":false}' --region ` + +### 步骤5:启用/禁用规则 + +通过 `ModifyImmutableTagRules` 切换 `Disabled` 字段。以下为禁用规则(`Disabled: true`): + +`immutable-rule-toggle.json`: + +```json +{ + "RegistryId": "", + "NamespaceName": "", + "RuleId": 1, + "Rule": { + "RepositoryPattern": "**", + "TagPattern": "stable-*", + "RepositoryDecoration": "repoMatches", + "TagDecoration": "matches", + "Disabled": true + } +} +``` + +```bash +tccli tcr ModifyImmutableTagRules \ + --cli-input-json file://immutable-rule-toggle.json \ + --region \ + --output json +# expected: exit 0,返回 RequestId +``` + +> 切换 `Disabled` 时仍需提供完整 `Rule` 对象(含原有 `RepositoryPattern`/`TagPattern` 等),否则字段会被清空。启用规则改为 `"Disabled": false` 即可。 + +### 步骤6:删除规则 + +```bash +tccli tcr DeleteImmutableTagRules \ + --cli-input-json file://delete-rule.json \ + --region \ + --output json +# expected: exit 0,返回 RequestId +``` + +`delete-rule.json`: + +```json +{ + "RegistryId": "", + "NamespaceName": "", + "RuleId": 1 +} +``` + +**输出**: + +```json +{ + "RequestId": "faf792f4-452b-493d-975a-0ec97bb1f035" +} +``` + +| 参数 | 类型 | 必填 | 说明 | +|------|------|:--:|------| +| `RegistryId` | String | 是 | 实例 ID | +| `NamespaceName` | String | 是 | 命名空间名称 | +| `RuleId` | Integer | 是 | 规则 ID,来自 `DescribeImmutableTagRules` 输出的 `RuleId` 字段 | + +> 内联方式(需版本支持 `--NamespaceName` flag):`tccli tcr DeleteImmutableTagRules --RegistryId --NamespaceName --RuleId --region ` + +### 步骤7:验证规则已生效(数据面) + +规则创建并启用后,推送已受保护版本的镜像,预期被拒绝: + +```bash +# expected: DENIED (rule blocks re-push of same tag) +docker tag //: +docker push //: +``` + +> `docker push` 返回 `DENIED` 即说明不可变规则已生效。**首次推送不受影响**,仅对同一 Tag 的二次及后续推送生效。 + +## 验证 + +### 控制面(tccli) + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 规则已创建 | `DescribeImmutableTagRules --cli-input-json file://describe-rule.json --region ` | `Rules` 数组含目标规则,`RuleId` 非零,`Total >= 1` | +| 规则字段正确 | 同上 | `RepositoryDecoration`、`TagDecoration`、`RepositoryPattern`、`TagPattern` 与创建参数一致 | +| 规则生效状态 | 同上,取 `Disabled` | `false`(若创建时未指定 `Disabled: true`) | +| 修改后字段更新 | 同上,取 `TagPattern` 等 | 与 `ModifyImmutableTagRules` 传入值一致(如 `stable-*`) | +| 规则已删除 | 同上 | `Total` 减少,`Rules` 数组不再含该 `RuleId` | +| 规则可删除 | `DeleteImmutableTagRules --cli-input-json file://delete-rule.json --region ` | 返回 `RequestId`,再 `Describe` 确认 `Total` 减少 | + +### 数据面 + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 受保护 Tag 二次推送被拒 | `docker push //:` | 返回 `DENIED` | +| 受保护 Tag 首次推送正常 | 同上(首次) | 推送成功 | +| 未匹配 Tag 推送正常 | `docker push //:` | 推送成功 | + +> 获取 `RegistryDomain`:`tccli tcr DescribeInstances --Registryids '[""]' --region --filter "Registries[0].PublicDomain"`。 + +## 清理 + +> **警告:** 删除不可变规则后,对应 Tag 的保护立即失效,后续推送相同 Tag 将正常覆盖镜像版本。删除前确认生产环境无依赖该规则的版本保护需求。 + +### 1. 清理前状态检查 + +```bash +tccli tcr DescribeImmutableTagRules \ + --cli-input-json file://describe-rule.json \ + --region \ + --output json +# expected: 确认待删除规则的 RuleId、NsName、RepositoryPattern、TagPattern +``` + +### 2. 删除规则 + +```bash +tccli tcr DeleteImmutableTagRules \ + --cli-input-json file://delete-rule.json \ + --region \ + --output json +# expected: exit 0,返回 RequestId +``` + +### 3. 验证已删除 + +```bash +tccli tcr DescribeImmutableTagRules \ + --cli-input-json file://describe-rule.json \ + --region \ + --output json +# expected: Total 减少,Rules 数组中不再包含该 RuleId 的条目 +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `CreateImmutableTagRules` 返回 `InvalidParameter.ErrorTcrInvalidParameter`: "RepositoryDecoration: matches does not validate as in(repoMatches\|repoExcludes)" | 检查 `Rule.RepositoryDecoration` 值 | `RepositoryDecoration` 必须带 `repo` 前缀,裸 `matches` 不合法 | 改为 `"RepositoryDecoration": "repoMatches"`(匹配)或 `"repoExcludes"`(排除) | +| `CreateImmutableTagRules` 返回 `InvalidParameter.ErrorTcrInvalidParameter`(TagDecoration 相关) | 检查 `Rule.TagDecoration` 值 | `TagDecoration` 仅接受 `matches` 或 `excludes`(**不带** `repo` 前缀) | 使用 `"TagDecoration": "matches"` 或 `"excludes"` | +| `CreateImmutableTagRules` 返回 "TagDecoration: non zero value required" | 检查 `Rule` JSON 是否包含 `TagDecoration` | `TagDecoration` 为**必填**字段,不可省略 | 添加 `"TagDecoration": "matches"` 或 `"excludes"` | +| `CreateImmutableTagRules` 返回 "NamespaceName: non zero value required" | 检查 JSON 顶层是否传入 `NamespaceName` | `NamespaceName` 为**必填**顶层参数 | 在 JSON 顶层添加 `"NamespaceName": ""` | +| `ModifyImmutableTagRules` 返回 "RuleId: non zero value required" | 检查 JSON 顶层是否含 `RuleId` 字段 | `ModifyImmutableTagRules` 需 `RuleId` 指定目标规则 | 添加 `"RuleId": <数字ID>`(来自 `DescribeImmutableTagRules` 输出) | +| `CreateImmutableTagRules` 返回 `ResourceNotFound`: "registry ... not found" | `tccli tcr DescribeInstances --region ` 确认实例存在 | `RegistryId` 错误或实例已销毁 | 核对 `RegistryId`,确认实例状态为 `Running` | +| `CreateImmutableTagRules` 返回 `ResourceNotFound.TcrResourceNotFound`: "namespace ... not found" | `tccli tcr DescribeNamespaces --RegistryId --region ` 确认命名空间存在 | `NamespaceName` 错误或命名空间未创建 | 核对命名空间名称,参考 [管理命名空间](../../image-creation/namespace) 创建 | +| CLI 返回 `Unknown options: --NamespaceName` | 检查 tccli 版本:`tccli --version` | 部分 tccli 版本未将 `NamespaceName` 暴露为直接 CLI flag | 改用 `--cli-input-json file://x.json` 传参(JSON 内含 `NamespaceName`) | +| CLI 返回 `Unknown options: --NamespaceId` | 同上 | 部分 tccli 版本未将 `NamespaceId` 暴露为直接 CLI flag | 同上,改用 `--cli-input-json` | +| CAM 权限拒绝 (`UnauthorizedOperation`) | — | 子账号缺少 TCR 不可变规则权限 | 主账号授予 `tcr:CreateImmutableTagRules` / `tcr:ModifyImmutableTagRules` / `tcr:DescribeImmutableTagRules` / `tcr:DeleteImmutableTagRules` | +| `CreateImmutableTagRules` 返回 `InternalError` / `FailedOperation` | 保留返回的 `RequestId` | 云端服务暂时不可用 | 稍后重试(间隔 30 秒以上);若持续失败,凭 `RequestId` [在线咨询](https://cloud.tencent.com/online-service?from=doc_1141) | + +### 行为异常(规则创建成功但未按预期生效) + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 规则已创建但推送相同 Tag 未被拒绝 | `DescribeImmutableTagRules` 检查 `Disabled` 字段 | `Disabled: true`,规则处于禁用状态 | `ModifyImmutableTagRules` 将 `Disabled` 改为 `false` | +| 规则已启用但特定 Tag 仍可覆盖 | `DescribeImmutableTagRules` 对比 `RepositoryPattern`/`TagPattern` 与实际仓库名和 Tag | 仓库名或 Tag 不匹配规则的过滤模式(如 `v*` 不匹配 `V1.0` 大小写;`prod-*` 不匹配 `prod/app`) | 调整 `RepositoryPattern` 或 `TagPattern` 覆盖目标仓库和 Tag | +| 重复创建生成多条相同规则 | `DescribeImmutableTagRules` 检查 `Total` 与规则列表 | `CreateImmutableTagRules` 不做去重,重复调用生成多条规则 | 创建前先 `Describe` 检查;多余规则用 `DeleteImmutableTagRules` 逐条删除 | +| 规则重叠时行为不明确 | `DescribeImmutableTagRules` 列出所有规则,逐条对比覆盖范围 | 多条规则重叠时以最严格条件为准(任一规则阻断即拒绝推送) | 确认重叠规则组合符合预期;删除多余规则 | +| `DescribeImmutableTagRules` 返回 `EmptyNs: ["ns"]` | — | 该命名空间尚无不可变规则,为正常状态 | 按 [步骤2](#步骤2创建版本不可变规则) 创建规则即可 | +| 修改规则后某些字段被清空 | `DescribeImmutableTagRules` 对比修改前后字段 | `ModifyImmutableTagRules` 为全量覆盖,`Rule` 对象遗漏字段会被清空 | 修改时 `Rule` 必须包含全部必填字段,建议从查询结果复制完整对象再改 | + +### 排障记录:RepositoryDecoration 枚举首次失败 + +> **真机排障记录:** 首次调用 `CreateImmutableTagRules` 时传入 `"RepositoryDecoration": "matches"`,API 返回 `InvalidParameter.ErrorTcrInvalidParameter`(RequestId: `c351d168-965a-481d-8760-770681a5ce47`),错误信息为: +> +> ```bash +> Rule.RepositoryDecoration: matches does not validate as in(repoMatches|repoExcludes) +> ``` +> +> 修正为 `"RepositoryDecoration": "repoMatches"` 后创建成功(RequestId: `bce40f14-44c5-4626-a40b-0de9ab0dc476`)。 +> +> **教训:** `RepositoryDecoration` 和 `TagDecoration` 的枚举值不对称——前者带 `repo` 前缀(`repoMatches`/`repoExcludes`),后者不带(`matches`/`excludes`)。创建前务必对照 [场景决策表](#场景决策表) 确认枚举值,切勿凭直觉填写。 + +## 下一步 + +- [容器镜像安全扫描](../vulnerability-scan) — 对镜像进行漏洞扫描与修复 +- [容器镜像签名](../image-signing) — 镜像签名与验签 +- [按策略自动删除镜像版本](../../image-cleanup/auto-delete) — 自动清理过期镜像版本 +- [触发器(Webhook)](../../devops/webhook) — 镜像推送/删除自动触发 CI/CD +- [环境准备](../../../index.md) — 返回 TCR 工具链入口 + +## 控制台替代 + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 选择实例 → 左侧导航栏 **版本管理** > **版本不可变** → 单击 **新建规则**,选择命名空间,配置仓库匹配(匹配/排除)和版本匹配(匹配/排除),填写过滤模式,确认创建。在规则列表行可启用/禁用、编辑或删除规则。 diff --git a/src/content/docs/cli/tcr/ops/image-security/vulnerability-scan-tcrctl.md b/src/content/docs/cli/tcr/ops/image-security/vulnerability-scan-tcrctl.md new file mode 100644 index 0000000..8fe23dc --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-security/vulnerability-scan-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "容器镜像安全扫描(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[容器镜像安全扫描](https://cloud.tencent.com/document/product/1141/48185) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/image-security/vulnerability-scan.md b/src/content/docs/cli/tcr/ops/image-security/vulnerability-scan.md new file mode 100644 index 0000000..dc6ea55 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-security/vulnerability-scan.md @@ -0,0 +1,518 @@ +--- +title: "容器镜像安全扫描(tccli)" +description: "· page_id `48185`" +--- + +> 对照官方:[容器镜像安全扫描](https://cloud.tencent.com/document/product/1141/48185) · page_id `48185` + +## 概述 + +通过 `tccli tcr ModifyNamespace --IsAutoScan` 在命名空间级别配置 TCR 企业版镜像自动扫描策略。TCR 镜像安全扫描基于开源 Clair 方案,漏洞信息来自 CVE 官方漏洞库并定期同步。开启自动扫描后,**新推送**至该命名空间的 DockerImage 类型镜像将自动触发扫描,返回漏洞等级、CVE 编号及修复建议。 + +> **规格限制前置提示**:basic 实例**支持**自动扫描(`--IsAutoScan`)与漏洞详情查询,但**不支持**部署阻断(`--IsPreventVUL`)、阻断阈值(`--Severity`)与 CVE 白名单(`--CVEWhitelistItems`),调用返回 `OperationDenied`。部署阻断相关功能需 standard 或 premium 实例。详见[规格限制](#规格限制)与[排障](#排障)。 + +> **真机验证环境**:本文命令与输出基于实例 `tcr-nn8smeyj`(premium)、命名空间 `kerwinwjyan-test`、地域 `ap-guangzhou` 验证。 + +## 前置条件 + +- [环境准备](../../../index.md) +- 已完成 [创建企业版实例](../../create),实例 `Status` 为 `Running` +- 已 [创建命名空间](../../image-creation/namespace) +- 已 [创建镜像仓库并推送镜像](../../image-creation/repository)(查询扫描结果的前置条件,非配置扫描策略的必要条件) +- 如使用子账号操作,需授予对应实例的 `tcr:ModifyNamespace`、`tcr:DescribeNamespaces`、`tcr:DescribeImages`、`tcr:DescribeImageManifests`、`tcr:DescribeImageVulnerabilityDetails` 权限,参见 [企业版授权方案示例](https://cloud.tencent.com/document/product/1141/41417) +- **部署阻断(`IsPreventVUL`)**:仅 standard / premium 实例支持,basic 实例调用返回 `OperationDenied` + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域(如 ap-guangzhou) + +# 3. 确认实例存在且状态正常 +tccli tcr DescribeInstances --Registryids '[""]' --region --output json +# expected: exit 0,Status 为 "Running" + +# 4. 确认命名空间已存在 +tccli tcr DescribeNamespaces --RegistryId '' --NamespaceName '' --region --output json +# expected: exit 0,目标命名空间在 NamespaceList 中 +``` + +### 资源检查 + +```bash +# 5. 确认实例规格,决定可用的安全功能 +tccli tcr DescribeInstances --Registryids '[""]' --region --output json | jq '.Registries[0].RegistryType' +# expected: "basic" / "standard" / "premium"(basic 不支持部署阻断) + +# 6. 确认目标镜像仓库已存在(查询扫描结果的前置) +tccli tcr DescribeRepositories --RegistryId '' --NamespaceName '' --region --output json | jq '.TotalCount' +# expected: >= 1(仓库存在) +``` + +### 规格限制 + +| 能力 | basic | standard | premium | +|------|:-----:|:--------:|:-------:| +| 自动扫描(`--IsAutoScan`) | 是 | 是 | 是 | +| 手动触发单次扫描(控制台) | 是 | 是 | 是 | +| 漏洞详情查询(`DescribeImageVulnerabilityDetails`) | 是 | 是 | 是 | +| 部署阻断(`--IsPreventVUL` / `--Severity`) | **否** | 是 | 是 | +| CVE 白名单(`--CVEWhitelistItems`) | **否** | 是 | 是 | + +> 手动触发单次扫描**无公开 tccli API**,仅支持控制台操作。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 / 参数 | 幂等 | 规格限制 | +|-----------|------------------|:--:|------| +| 查看命名空间扫描配置 | `DescribeNamespaces` → 读 `AutoScan` / `PreventVUL` 字段 | 是 | 无 | +| 开启自动扫描 | `ModifyNamespace --IsAutoScan true` | 是 | 无 | +| 关闭自动扫描 | `ModifyNamespace --IsAutoScan false` | 是 | 无 | +| 手动触发单次扫描 | **无公开 tccli API**(仅控制台) | — | 无 | +| 查看镜像版本列表 | `DescribeImages` | 是 | 无 | +| 查看镜像 Manifest | `DescribeImageManifests` | 是 | 无 | +| 查看镜像漏洞详情 | `DescribeImageVulnerabilityDetails` | 是 | 无 | +| 配置部署阻断 | `ModifyNamespace --IsPreventVUL true --Severity <等级>` | 是 | standard / premium | +| 配置 CVE 白名单 | `ModifyNamespace --CVEWhitelistItems` | 是 | standard / premium | + +## 关键字段说明 + +`ModifyNamespace` 安全扫描相关参数如下(完整字段以 `tccli tcr ModifyNamespace help` 为准): + +| 字段 | 类型 | 必填 | 取值与约束 | basic 实例行为 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | 实例 ID,格式 `tcr-xxxxxxxx` | — | +| `NamespaceName` | String | 是 | 已存在的命名空间名称 | — | +| `IsAutoScan` | Boolean | 否 | `true`(自动扫描)/ `false`(手动扫描)。**仅对新推送镜像触发扫描,已有镜像不补扫** | **可用**,`DescribeNamespaces` 返回 `AutoScan: true` | +| `IsPreventVUL` | Boolean | 否 | `true`(开启部署阻断)。开启后严重等级 ≥ `Severity` 的镜像不可部署 | **不可用**,返回 `OperationDenied` | +| `Severity` | String | 否 | `low` / `medium` / `high`(小写)。配合 `IsPreventVUL` 使用 | **不可用**,与 `IsPreventVUL` 同返回 `OperationDenied` | +| `CVEWhitelistItems` | Array | 否 | JSON 数组,每项含 `CVEID` 字段,如 `[{"CVEID":"CVE-2024-0001"}]` | **不可用**,与 `IsPreventVUL` 同返回 `OperationDenied` | + +> **参数名易错点**:参数名带 `Is` 前缀——`--IsAutoScan`、`--IsPreventVUL`。误用 `--AutoScan` / `--PreventVUL` 返回 `Unknown options`(见[排障](#排障))。 + +## 操作步骤 + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | 实例 ID | 格式 `tcr-xxxxxxxx` | `tccli tcr DescribeInstances --region ` | +| `` | 命名空间名称 | 已存在 | `tccli tcr DescribeNamespaces --RegistryId --region ` | +| `` | 镜像仓库名称 | 已存在 | `tccli tcr DescribeRepositories --RegistryId --NamespaceName --region ` | +| `` | 镜像版本(Tag) | 已推送至仓库 | `tccli tcr DescribeImages --RegistryId --NamespaceName --RepositoryName --region ` | +| `` | 地域 | 如 `ap-guangzhou` | `tccli tcr DescribeRegions` | + +### 步骤1:查看当前扫描配置 + +查询命名空间当前的安全扫描和部署阻断设置: + +```bash +tccli tcr DescribeNamespaces \ + --RegistryId '' \ + --NamespaceName '' \ + --region \ + --output json +# expected: exit 0,NamespaceList 含目标命名空间,AutoScan / PreventVUL 反映当前状态 +``` + +**输出**(自动扫描已开启): + +```json +{ + "NamespaceList": [ + { + "Name": "kerwinwjyan-test", + "Public": false, + "NamespaceId": 2, + "AutoScan": true, + "PreventVUL": false, + "Severity": "", + "CVEWhitelistItems": [], + "Metadata": [ + { + "Key": "auto_scan", + "Value": "true" + }, + { + "Key": "prevent_vul", + "Value": "false" + } + ] + } + ], + "TotalCount": 1, + "RequestId": "a0c88b58-ade2-4756-99fb-e1a21dc0fe17" +} +``` + +关注字段: + +| 字段 | 说明 | +|------|------| +| `AutoScan` | `true` = 自动扫描已开启,新推送镜像自动触发扫描 | +| `PreventVUL` | `true` = 部署阻断已开启(仅 standard/premium 可设为 `true`) | +| `Severity` | 部署阻断阈值,`""` 表示未配置 | +| `CVEWhitelistItems` | CVE 白名单数组,`[]` 表示无白名单 | +| `Metadata[].Key = "auto_scan"` | 字符串形式的扫描开关状态 | +| `Metadata[].Key = "prevent_vul"` | 字符串形式的阻断开关状态 | + +### 步骤2:开启自动扫描 + +新镜像推送至该命名空间时自动触发安全扫描: + +```bash +tccli tcr ModifyNamespace \ + --RegistryId '' \ + --NamespaceName '' \ + --IsAutoScan true \ + --region \ + --output json +# expected: exit 0,返回非空 RequestId +``` + +**输出**: + +```json +{ + "RequestId": "2599749f-b0f5-4364-9fcd-c0b7133eef5c" +} +``` + +> 真机验证:`ModifyNamespace --IsAutoScan true` 执行成功(exit 0),`DescribeNamespaces` 确认 `AutoScan: true` 已持久化。basic 实例同样支持此参数。 + +### 步骤3:验证自动扫描已生效 + +```bash +tccli tcr DescribeNamespaces \ + --RegistryId '' \ + --NamespaceName '' \ + --region \ + --output json | jq '.NamespaceList[0] | {AutoScan, PreventVUL, Metadata}' +# expected: {"AutoScan": true, "PreventVUL": false, "Metadata": [{"Key":"auto_scan","Value":"true"}, ...]} +``` + +**输出**: + +```json +{ + "AutoScan": true, + "PreventVUL": false, + "Metadata": [ + { + "Key": "auto_scan", + "Value": "true" + }, + { + "Key": "prevent_vul", + "Value": "false" + } + ] +} +``` + +### 步骤4:查看镜像版本列表 + +`DescribeImages` 返回镜像的基本信息(版本、Digest、大小),**不包含漏洞详情**: + +```bash +tccli tcr DescribeImages \ + --RegistryId '' \ + --NamespaceName '' \ + --RepositoryName '' \ + --region \ + --output json +# expected: exit 0,TotalCount >= 1,ImageInfoList 含目标镜像版本 +``` + +**输出**(仓库内无镜像时): + +```json +{ + "ImageInfoList": [], + "TotalCount": 0, + "RequestId": "da668a99-0af3-4a9f-9d7e-b1c376e637bd" +} +``` + +**输出**(有镜像时): + +```json +{ + "ImageInfoList": [ + { + "ImageVersion": "v1.0.0", + "Digest": "sha256:a1b2c3d4e5f67890abcdef1234567890abcdef1234567890abcdef1234567890", + "Size": 50343936, + "UpdateTime": "2026-06-18T10:00:00Z", + "Kind": "DockerManifest", + "KmsSignature": "" + } + ], + "TotalCount": 1, + "RequestId": "" +} +``` + +> `Kind` 为 `DockerManifest` 的镜像才支持安全扫描,OCI 制品等其他类型暂不支持。 + +### 步骤5:查看镜像 Manifest 详情 + +```bash +tccli tcr DescribeImageManifests \ + --RegistryId '' \ + --NamespaceName '' \ + --RepositoryName '' \ + --ImageVersion '' \ + --region \ + --output json +# expected: exit 0,返回 Manifest / Config / Labels / Size +``` + +**输出**(无指定 tag 镜像时): + +```bash +An API error has occurred: tag not found in / +# exit: 255 +``` + +**输出**(有镜像时): + +```json +{ + "Manifest": "{...}", + "Config": "{...}", + "Labels": [], + "Size": 50343936, + "RequestId": "" +} +``` + +> `DescribeImageManifests` 返回镜像 Manifest 与 Config 元数据,**不含扫描结果字段**。漏洞详情需走 `DescribeImageVulnerabilityDetails`。 + +### 步骤6:查看镜像漏洞详情 + +`DescribeImageVulnerabilityDetails` 是获取扫描结果的 API。**前置条件**:指定的镜像版本已完成安全扫描(自动或手动触发): + +```bash +tccli tcr DescribeImageVulnerabilityDetails \ + --RegistryId '' \ + --NamespaceName '' \ + --RepositoryName '' \ + --ImageVersion '' \ + --region \ + --output json +# expected: exit 0,返回漏洞列表(含 CVE 编号、严重等级、修复建议) +``` + +> **验证说明**:本指南真机环境实例内暂无已扫描镜像,此命令需有已完成扫描的镜像版本方可返回漏洞详情。该 API 为官方公开接口,参数经真机校验,漏洞字段以实际返回为准。 + +### 步骤7:手动触发扫描(仅控制台) + +**TCR 当前无公开 tccli API 用于触发单次镜像安全扫描**。手动触发仅支持控制台操作: + +1. 登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) +2. 进入目标镜像仓库的**版本管理**页 +3. 选择指定镜像版本,单击**扫描** +4. 安全级别显示为「扫描中」,等待扫描完成 + +> **触发时机总结**: +> - 开启 `IsAutoScan` 的命名空间内**新推送**镜像 → 自动触发扫描 +> - 控制台手动点击「扫描」→ 服务端触发 +> - **已有镜像不会因开启 `IsAutoScan` 而自动补扫**,需重新推送或手动触发 + +### 步骤8:配置部署阻断(仅 standard / premium) + +在命名空间层级阻断高危镜像部署,基于扫描结果的严重等级设定阻断阈值,支持 CVE 白名单排除特定漏洞。 + +**basic 实例不支持此功能**,调用返回 `OperationDenied`: + +```bash +# basic 实例执行此命令将报错 +tccli tcr ModifyNamespace \ + --RegistryId '' \ + --NamespaceName '' \ + --IsPreventVUL true \ + --Severity high \ + --region \ + --output json +# expected: 返回 OperationDenied — deployment blocking only supports standard and premium +``` + +**standard / premium 实例**开启部署阻断(阻断 high 及以上等级漏洞,白名单排除 CVE-2024-0001): + +```bash +tccli tcr ModifyNamespace \ + --RegistryId '' \ + --NamespaceName '' \ + --IsPreventVUL true \ + --Severity high \ + --CVEWhitelistItems '[{"CVEID":"CVE-2024-0001"}]' \ + --region \ + --output json +# expected: exit 0,返回非空 RequestId +``` + +**输出**: + +```json +{ + "RequestId": "" +} +``` + +**关闭部署阻断**: + +```bash +tccli tcr ModifyNamespace \ + --RegistryId '' \ + --NamespaceName '' \ + --IsPreventVUL false \ + --region \ + --output json +# expected: exit 0,返回非空 RequestId +``` + +### 步骤9:关闭自动扫描 + +恢复为手动扫描模式(需在控制台手动触发每个镜像版本): + +```bash +tccli tcr ModifyNamespace \ + --RegistryId '' \ + --NamespaceName '' \ + --IsAutoScan false \ + --region \ + --output json +# expected: exit 0,返回非空 RequestId +``` + +**输出**: + +```json +{ + "RequestId": "" +} +``` + +## 验证 + +### 控制面(tccli) + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 自动扫描已开启 | `DescribeNamespaces --NamespaceName --region ` | `AutoScan: true`,`Metadata` 含 `{"Key":"auto_scan","Value":"true"}` | +| 自动扫描已关闭 | 同上 | `AutoScan: false`,`Metadata` 含 `{"Key":"auto_scan","Value":"false"}` | +| `ModifyNamespace` 成功 | 返回 `RequestId` | exit 0,非空 `RequestId` | +| 部署阻断已开启(standard/premium) | `DescribeNamespaces --NamespaceName ` | `PreventVUL: true`,`Severity` 为设定值 | +| 部署阻断不可用(basic) | `ModifyNamespace --IsPreventVUL true` | 返回 `OperationDenied`(符合预期,非环境异常) | + +```bash +# 验证扫描配置(确认 AutoScan 和 PreventVUL 与预期一致) +tccli tcr DescribeNamespaces \ + --RegistryId '' \ + --NamespaceName '' \ + --region \ + --output json +# expected: AutoScan / PreventVUL 与配置一致 +``` + +### 数据面 + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 镜像已推送 | `DescribeImages --RepositoryName ` | `TotalCount >= 1`,`ImageInfoList` 非空 | +| 漏洞详情可查(有镜像且已扫描) | `DescribeImageVulnerabilityDetails --ImageVersion ` | 返回 CVE 列表、严重等级、修复建议 | + +## 清理 + +安全扫描配置为幂等操作,不产生额外计费资源。如需恢复默认关闭状态: + +```bash +tccli tcr ModifyNamespace \ + --RegistryId '' \ + --NamespaceName '' \ + --IsAutoScan false \ + --region \ + --output json +# expected: exit 0,返回非空 RequestId +``` + +```bash +# 验证已关闭 +tccli tcr DescribeNamespaces \ + --RegistryId '' \ + --NamespaceName '' \ + --region \ + --output json | jq '.NamespaceList[0].AutoScan' +# expected: false +``` + +如配置了部署阻断,一并关闭: + +```bash +tccli tcr ModifyNamespace \ + --RegistryId '' \ + --NamespaceName '' \ + --IsPreventVUL false \ + --region \ + --output json +# expected: exit 0,返回非空 RequestId +``` + +## 排障 + +### 平台限制说明 + +basic 实例在安全扫描场景存在以下**真实平台限制**(非 bug 或故障): + +| 限制 | 说明 | 影响 | +|------|------|------| +| 不支持 `PreventVUL` | 部署阻断为 standard/premium 专属功能 | basic 实例无法通过 CLI 或控制台开启部署阻断 | +| 不支持 `Severity` / `CVEWhitelistItems` | 配合 `PreventVUL` 的参数,同受规格限制 | basic 实例 `ModifyNamespace` 不得传递这些参数 | +| 无手动扫描 API | TCR API 清单无触发单次扫描的公开 Action | 已有镜像的重新扫描只能通过控制台手动触发 | +| `DescribeImages` 不含漏洞详情 | 镜像列表 API 仅返回基本元信息 | 漏洞结果需走 `DescribeImageVulnerabilityDetails` 或控制台 | + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ModifyNamespace` 返回 `Unknown options: --AutoScan, true` | `tccli tcr ModifyNamespace help` 查看合法参数 | 参数名错误。正确参数名为 `--IsAutoScan`(带 `Is` 前缀),非 `--AutoScan` | 修正参数名:`--IsAutoScan true` | +| `ModifyNamespace` 返回 `Unknown options: --PreventVUL, true` | 同上 | 参数名错误。正确参数名为 `--IsPreventVUL`(带 `Is` 前缀),非 `--PreventVUL` | 修正参数名:`--IsPreventVUL true` | +| `ModifyNamespace --IsPreventVUL true` 返回 `OperationDenied` | `tccli tcr DescribeInstances --Registryids '[""]' --region --output json \| jq '.Registries[0].RegistryType'` 查看 `RegistryType` | basic 实例不支持部署阻断功能。此为平台规格限制,调用符合预期 | 升级实例至 standard 或 premium:`tccli tcr ModifyInstance --RegistryId --RegistryType standard --region ` | +| `ModifyNamespace --Severity` 返回 `InvalidParameter` | 检查 `Severity` 值大小写和拼写 | 有效值为 `low`、`medium`、`high`(小写) | 使用合法枚举值,注意小写 | +| `ModifyNamespace --CVEWhitelistItems` 返回 `InvalidParameter` | 检查 JSON 格式是否为 `[{"CVEID":"CVE-XXXX-XXXXX"}]` | JSON 格式错误或 CVE ID 格式不正确 | 确保为有效 JSON 数组,每项含 `CVEID` 字段 | +| `DescribeImageManifests` 返回 `tag not found` | `tccli tcr DescribeImages --RegistryId --NamespaceName --RepositoryName --region ` 确认镜像是否已推送 | 命名空间/仓库内无指定 tag 的镜像 | 先推送镜像后再查询 | +| `DescribeImages` 返回空列表 | 同上 | 仓库内无镜像版本 | 推送镜像后重试 | +| `DescribeImageVulnerabilityDetails` 返回空或报错 | `DescribeImages` 确认镜像存在,并确认控制台已触发过扫描 | 镜像尚未完成扫描,或 `IsAutoScan` 开启前推送的旧镜像未补扫 | 控制台手动触发扫描后重试,或重新推送镜像触发自动扫描 | +| `ModifyNamespace` 返回 `UnauthorizedOperation` | `tccli tcr DescribeNamespaces --RegistryId --region ` 验证 list 权限 | 子账号缺少 `tcr:ModifyNamespace` 权限 | 联系主账号授予 `QcloudTCRFullAccess` 或最小权限策略(含 `tcr:ModifyNamespace`) | + +### 操作成功但行为不符合预期 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 开启自动扫描后已有镜像未触发扫描 | `DescribeNamespaces` 确认 `AutoScan: true` | `IsAutoScan` 仅对新推送的镜像生效,历史镜像不会自动补扫 | 重新推送镜像,或前往控制台手动触发扫描 | +| 自动扫描开启后新推送镜像仍未扫描 | `DescribeImages` 查看镜像 `Kind` 字段 | 镜像扫描仅支持 `DockerManifest` 类型,OCI 制品等其他类型暂不支持 | 确认推送的是 DockerImage 类型镜像 | + +### Tier 升级路径 + +| 场景 | CLI 命令 | 说明 | +|------|---------|------| +| basic → standard(解锁 PreventVUL + 完整扫描) | `tccli tcr ModifyInstance --RegistryId --RegistryType standard --region ` | 升级后即可配置部署阻断、CVE 白名单等完整安全功能 | +| standard → premium(解锁签名策略等) | `tccli tcr ModifyInstance --RegistryId --RegistryType premium --region ` | 解锁复制实例、跨账号同步等高级功能 | +| 升级零预付扣费 | `PayMod=0` 模式下升级不触发预付扣费,按量计费单价随规格提升 | — | +| 升级耗时 | 约 30--60 秒,`DescribeInstanceStatus` 返回 `Running` 即完成 | — | + +## 下一步 + +- [高危镜像部署阻断](../deployment-block)(page_id `63869`) — 基于扫描结果阻断高危镜像部署(需 standard/premium) +- [镜像版本不可变](../immutable-tags)(page_id `58200`) — 锁定重要镜像版本防覆盖 +- [管理镜像仓库](../../image-creation/repository) — 推送镜像以触发安全扫描 +- [创建企业版实例](../../create) — 升级实例规格以解锁部署阻断 +- [环境准备](../../../index.md) — 返回 TCR 工具链入口 + +## 控制台替代 + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 选择实例 → **命名空间** → 单击实例名进入详情 → 基本信息页开启/关闭自动扫描。在 **镜像仓库** → 版本管理页选择指定版本 → 单击 **扫描** 手动触发,扫描完成后查看漏洞详情(安全级别、CVE 列表、修复建议)。部署阻断与 CVE 白名单在命名空间详情页配置(仅 standard/premium 可见)。 diff --git a/src/content/docs/cli/tcr/ops/image-security/vulnerability-scan/vulnerability-scan-tcrctl.md b/src/content/docs/cli/tcr/ops/image-security/vulnerability-scan/vulnerability-scan-tcrctl.md new file mode 100644 index 0000000..8fe23dc --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-security/vulnerability-scan/vulnerability-scan-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "容器镜像安全扫描(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[容器镜像安全扫描](https://cloud.tencent.com/document/product/1141/48185) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/image-security/vulnerability-scan/vulnerability-scan.md b/src/content/docs/cli/tcr/ops/image-security/vulnerability-scan/vulnerability-scan.md new file mode 100644 index 0000000..dc6ea55 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/image-security/vulnerability-scan/vulnerability-scan.md @@ -0,0 +1,518 @@ +--- +title: "容器镜像安全扫描(tccli)" +description: "· page_id `48185`" +--- + +> 对照官方:[容器镜像安全扫描](https://cloud.tencent.com/document/product/1141/48185) · page_id `48185` + +## 概述 + +通过 `tccli tcr ModifyNamespace --IsAutoScan` 在命名空间级别配置 TCR 企业版镜像自动扫描策略。TCR 镜像安全扫描基于开源 Clair 方案,漏洞信息来自 CVE 官方漏洞库并定期同步。开启自动扫描后,**新推送**至该命名空间的 DockerImage 类型镜像将自动触发扫描,返回漏洞等级、CVE 编号及修复建议。 + +> **规格限制前置提示**:basic 实例**支持**自动扫描(`--IsAutoScan`)与漏洞详情查询,但**不支持**部署阻断(`--IsPreventVUL`)、阻断阈值(`--Severity`)与 CVE 白名单(`--CVEWhitelistItems`),调用返回 `OperationDenied`。部署阻断相关功能需 standard 或 premium 实例。详见[规格限制](#规格限制)与[排障](#排障)。 + +> **真机验证环境**:本文命令与输出基于实例 `tcr-nn8smeyj`(premium)、命名空间 `kerwinwjyan-test`、地域 `ap-guangzhou` 验证。 + +## 前置条件 + +- [环境准备](../../../index.md) +- 已完成 [创建企业版实例](../../create),实例 `Status` 为 `Running` +- 已 [创建命名空间](../../image-creation/namespace) +- 已 [创建镜像仓库并推送镜像](../../image-creation/repository)(查询扫描结果的前置条件,非配置扫描策略的必要条件) +- 如使用子账号操作,需授予对应实例的 `tcr:ModifyNamespace`、`tcr:DescribeNamespaces`、`tcr:DescribeImages`、`tcr:DescribeImageManifests`、`tcr:DescribeImageVulnerabilityDetails` 权限,参见 [企业版授权方案示例](https://cloud.tencent.com/document/product/1141/41417) +- **部署阻断(`IsPreventVUL`)**:仅 standard / premium 实例支持,basic 实例调用返回 `OperationDenied` + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域(如 ap-guangzhou) + +# 3. 确认实例存在且状态正常 +tccli tcr DescribeInstances --Registryids '[""]' --region --output json +# expected: exit 0,Status 为 "Running" + +# 4. 确认命名空间已存在 +tccli tcr DescribeNamespaces --RegistryId '' --NamespaceName '' --region --output json +# expected: exit 0,目标命名空间在 NamespaceList 中 +``` + +### 资源检查 + +```bash +# 5. 确认实例规格,决定可用的安全功能 +tccli tcr DescribeInstances --Registryids '[""]' --region --output json | jq '.Registries[0].RegistryType' +# expected: "basic" / "standard" / "premium"(basic 不支持部署阻断) + +# 6. 确认目标镜像仓库已存在(查询扫描结果的前置) +tccli tcr DescribeRepositories --RegistryId '' --NamespaceName '' --region --output json | jq '.TotalCount' +# expected: >= 1(仓库存在) +``` + +### 规格限制 + +| 能力 | basic | standard | premium | +|------|:-----:|:--------:|:-------:| +| 自动扫描(`--IsAutoScan`) | 是 | 是 | 是 | +| 手动触发单次扫描(控制台) | 是 | 是 | 是 | +| 漏洞详情查询(`DescribeImageVulnerabilityDetails`) | 是 | 是 | 是 | +| 部署阻断(`--IsPreventVUL` / `--Severity`) | **否** | 是 | 是 | +| CVE 白名单(`--CVEWhitelistItems`) | **否** | 是 | 是 | + +> 手动触发单次扫描**无公开 tccli API**,仅支持控制台操作。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 / 参数 | 幂等 | 规格限制 | +|-----------|------------------|:--:|------| +| 查看命名空间扫描配置 | `DescribeNamespaces` → 读 `AutoScan` / `PreventVUL` 字段 | 是 | 无 | +| 开启自动扫描 | `ModifyNamespace --IsAutoScan true` | 是 | 无 | +| 关闭自动扫描 | `ModifyNamespace --IsAutoScan false` | 是 | 无 | +| 手动触发单次扫描 | **无公开 tccli API**(仅控制台) | — | 无 | +| 查看镜像版本列表 | `DescribeImages` | 是 | 无 | +| 查看镜像 Manifest | `DescribeImageManifests` | 是 | 无 | +| 查看镜像漏洞详情 | `DescribeImageVulnerabilityDetails` | 是 | 无 | +| 配置部署阻断 | `ModifyNamespace --IsPreventVUL true --Severity <等级>` | 是 | standard / premium | +| 配置 CVE 白名单 | `ModifyNamespace --CVEWhitelistItems` | 是 | standard / premium | + +## 关键字段说明 + +`ModifyNamespace` 安全扫描相关参数如下(完整字段以 `tccli tcr ModifyNamespace help` 为准): + +| 字段 | 类型 | 必填 | 取值与约束 | basic 实例行为 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | 实例 ID,格式 `tcr-xxxxxxxx` | — | +| `NamespaceName` | String | 是 | 已存在的命名空间名称 | — | +| `IsAutoScan` | Boolean | 否 | `true`(自动扫描)/ `false`(手动扫描)。**仅对新推送镜像触发扫描,已有镜像不补扫** | **可用**,`DescribeNamespaces` 返回 `AutoScan: true` | +| `IsPreventVUL` | Boolean | 否 | `true`(开启部署阻断)。开启后严重等级 ≥ `Severity` 的镜像不可部署 | **不可用**,返回 `OperationDenied` | +| `Severity` | String | 否 | `low` / `medium` / `high`(小写)。配合 `IsPreventVUL` 使用 | **不可用**,与 `IsPreventVUL` 同返回 `OperationDenied` | +| `CVEWhitelistItems` | Array | 否 | JSON 数组,每项含 `CVEID` 字段,如 `[{"CVEID":"CVE-2024-0001"}]` | **不可用**,与 `IsPreventVUL` 同返回 `OperationDenied` | + +> **参数名易错点**:参数名带 `Is` 前缀——`--IsAutoScan`、`--IsPreventVUL`。误用 `--AutoScan` / `--PreventVUL` 返回 `Unknown options`(见[排障](#排障))。 + +## 操作步骤 + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | 实例 ID | 格式 `tcr-xxxxxxxx` | `tccli tcr DescribeInstances --region ` | +| `` | 命名空间名称 | 已存在 | `tccli tcr DescribeNamespaces --RegistryId --region ` | +| `` | 镜像仓库名称 | 已存在 | `tccli tcr DescribeRepositories --RegistryId --NamespaceName --region ` | +| `` | 镜像版本(Tag) | 已推送至仓库 | `tccli tcr DescribeImages --RegistryId --NamespaceName --RepositoryName --region ` | +| `` | 地域 | 如 `ap-guangzhou` | `tccli tcr DescribeRegions` | + +### 步骤1:查看当前扫描配置 + +查询命名空间当前的安全扫描和部署阻断设置: + +```bash +tccli tcr DescribeNamespaces \ + --RegistryId '' \ + --NamespaceName '' \ + --region \ + --output json +# expected: exit 0,NamespaceList 含目标命名空间,AutoScan / PreventVUL 反映当前状态 +``` + +**输出**(自动扫描已开启): + +```json +{ + "NamespaceList": [ + { + "Name": "kerwinwjyan-test", + "Public": false, + "NamespaceId": 2, + "AutoScan": true, + "PreventVUL": false, + "Severity": "", + "CVEWhitelistItems": [], + "Metadata": [ + { + "Key": "auto_scan", + "Value": "true" + }, + { + "Key": "prevent_vul", + "Value": "false" + } + ] + } + ], + "TotalCount": 1, + "RequestId": "a0c88b58-ade2-4756-99fb-e1a21dc0fe17" +} +``` + +关注字段: + +| 字段 | 说明 | +|------|------| +| `AutoScan` | `true` = 自动扫描已开启,新推送镜像自动触发扫描 | +| `PreventVUL` | `true` = 部署阻断已开启(仅 standard/premium 可设为 `true`) | +| `Severity` | 部署阻断阈值,`""` 表示未配置 | +| `CVEWhitelistItems` | CVE 白名单数组,`[]` 表示无白名单 | +| `Metadata[].Key = "auto_scan"` | 字符串形式的扫描开关状态 | +| `Metadata[].Key = "prevent_vul"` | 字符串形式的阻断开关状态 | + +### 步骤2:开启自动扫描 + +新镜像推送至该命名空间时自动触发安全扫描: + +```bash +tccli tcr ModifyNamespace \ + --RegistryId '' \ + --NamespaceName '' \ + --IsAutoScan true \ + --region \ + --output json +# expected: exit 0,返回非空 RequestId +``` + +**输出**: + +```json +{ + "RequestId": "2599749f-b0f5-4364-9fcd-c0b7133eef5c" +} +``` + +> 真机验证:`ModifyNamespace --IsAutoScan true` 执行成功(exit 0),`DescribeNamespaces` 确认 `AutoScan: true` 已持久化。basic 实例同样支持此参数。 + +### 步骤3:验证自动扫描已生效 + +```bash +tccli tcr DescribeNamespaces \ + --RegistryId '' \ + --NamespaceName '' \ + --region \ + --output json | jq '.NamespaceList[0] | {AutoScan, PreventVUL, Metadata}' +# expected: {"AutoScan": true, "PreventVUL": false, "Metadata": [{"Key":"auto_scan","Value":"true"}, ...]} +``` + +**输出**: + +```json +{ + "AutoScan": true, + "PreventVUL": false, + "Metadata": [ + { + "Key": "auto_scan", + "Value": "true" + }, + { + "Key": "prevent_vul", + "Value": "false" + } + ] +} +``` + +### 步骤4:查看镜像版本列表 + +`DescribeImages` 返回镜像的基本信息(版本、Digest、大小),**不包含漏洞详情**: + +```bash +tccli tcr DescribeImages \ + --RegistryId '' \ + --NamespaceName '' \ + --RepositoryName '' \ + --region \ + --output json +# expected: exit 0,TotalCount >= 1,ImageInfoList 含目标镜像版本 +``` + +**输出**(仓库内无镜像时): + +```json +{ + "ImageInfoList": [], + "TotalCount": 0, + "RequestId": "da668a99-0af3-4a9f-9d7e-b1c376e637bd" +} +``` + +**输出**(有镜像时): + +```json +{ + "ImageInfoList": [ + { + "ImageVersion": "v1.0.0", + "Digest": "sha256:a1b2c3d4e5f67890abcdef1234567890abcdef1234567890abcdef1234567890", + "Size": 50343936, + "UpdateTime": "2026-06-18T10:00:00Z", + "Kind": "DockerManifest", + "KmsSignature": "" + } + ], + "TotalCount": 1, + "RequestId": "" +} +``` + +> `Kind` 为 `DockerManifest` 的镜像才支持安全扫描,OCI 制品等其他类型暂不支持。 + +### 步骤5:查看镜像 Manifest 详情 + +```bash +tccli tcr DescribeImageManifests \ + --RegistryId '' \ + --NamespaceName '' \ + --RepositoryName '' \ + --ImageVersion '' \ + --region \ + --output json +# expected: exit 0,返回 Manifest / Config / Labels / Size +``` + +**输出**(无指定 tag 镜像时): + +```bash +An API error has occurred: tag not found in / +# exit: 255 +``` + +**输出**(有镜像时): + +```json +{ + "Manifest": "{...}", + "Config": "{...}", + "Labels": [], + "Size": 50343936, + "RequestId": "" +} +``` + +> `DescribeImageManifests` 返回镜像 Manifest 与 Config 元数据,**不含扫描结果字段**。漏洞详情需走 `DescribeImageVulnerabilityDetails`。 + +### 步骤6:查看镜像漏洞详情 + +`DescribeImageVulnerabilityDetails` 是获取扫描结果的 API。**前置条件**:指定的镜像版本已完成安全扫描(自动或手动触发): + +```bash +tccli tcr DescribeImageVulnerabilityDetails \ + --RegistryId '' \ + --NamespaceName '' \ + --RepositoryName '' \ + --ImageVersion '' \ + --region \ + --output json +# expected: exit 0,返回漏洞列表(含 CVE 编号、严重等级、修复建议) +``` + +> **验证说明**:本指南真机环境实例内暂无已扫描镜像,此命令需有已完成扫描的镜像版本方可返回漏洞详情。该 API 为官方公开接口,参数经真机校验,漏洞字段以实际返回为准。 + +### 步骤7:手动触发扫描(仅控制台) + +**TCR 当前无公开 tccli API 用于触发单次镜像安全扫描**。手动触发仅支持控制台操作: + +1. 登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) +2. 进入目标镜像仓库的**版本管理**页 +3. 选择指定镜像版本,单击**扫描** +4. 安全级别显示为「扫描中」,等待扫描完成 + +> **触发时机总结**: +> - 开启 `IsAutoScan` 的命名空间内**新推送**镜像 → 自动触发扫描 +> - 控制台手动点击「扫描」→ 服务端触发 +> - **已有镜像不会因开启 `IsAutoScan` 而自动补扫**,需重新推送或手动触发 + +### 步骤8:配置部署阻断(仅 standard / premium) + +在命名空间层级阻断高危镜像部署,基于扫描结果的严重等级设定阻断阈值,支持 CVE 白名单排除特定漏洞。 + +**basic 实例不支持此功能**,调用返回 `OperationDenied`: + +```bash +# basic 实例执行此命令将报错 +tccli tcr ModifyNamespace \ + --RegistryId '' \ + --NamespaceName '' \ + --IsPreventVUL true \ + --Severity high \ + --region \ + --output json +# expected: 返回 OperationDenied — deployment blocking only supports standard and premium +``` + +**standard / premium 实例**开启部署阻断(阻断 high 及以上等级漏洞,白名单排除 CVE-2024-0001): + +```bash +tccli tcr ModifyNamespace \ + --RegistryId '' \ + --NamespaceName '' \ + --IsPreventVUL true \ + --Severity high \ + --CVEWhitelistItems '[{"CVEID":"CVE-2024-0001"}]' \ + --region \ + --output json +# expected: exit 0,返回非空 RequestId +``` + +**输出**: + +```json +{ + "RequestId": "" +} +``` + +**关闭部署阻断**: + +```bash +tccli tcr ModifyNamespace \ + --RegistryId '' \ + --NamespaceName '' \ + --IsPreventVUL false \ + --region \ + --output json +# expected: exit 0,返回非空 RequestId +``` + +### 步骤9:关闭自动扫描 + +恢复为手动扫描模式(需在控制台手动触发每个镜像版本): + +```bash +tccli tcr ModifyNamespace \ + --RegistryId '' \ + --NamespaceName '' \ + --IsAutoScan false \ + --region \ + --output json +# expected: exit 0,返回非空 RequestId +``` + +**输出**: + +```json +{ + "RequestId": "" +} +``` + +## 验证 + +### 控制面(tccli) + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 自动扫描已开启 | `DescribeNamespaces --NamespaceName --region ` | `AutoScan: true`,`Metadata` 含 `{"Key":"auto_scan","Value":"true"}` | +| 自动扫描已关闭 | 同上 | `AutoScan: false`,`Metadata` 含 `{"Key":"auto_scan","Value":"false"}` | +| `ModifyNamespace` 成功 | 返回 `RequestId` | exit 0,非空 `RequestId` | +| 部署阻断已开启(standard/premium) | `DescribeNamespaces --NamespaceName ` | `PreventVUL: true`,`Severity` 为设定值 | +| 部署阻断不可用(basic) | `ModifyNamespace --IsPreventVUL true` | 返回 `OperationDenied`(符合预期,非环境异常) | + +```bash +# 验证扫描配置(确认 AutoScan 和 PreventVUL 与预期一致) +tccli tcr DescribeNamespaces \ + --RegistryId '' \ + --NamespaceName '' \ + --region \ + --output json +# expected: AutoScan / PreventVUL 与配置一致 +``` + +### 数据面 + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 镜像已推送 | `DescribeImages --RepositoryName ` | `TotalCount >= 1`,`ImageInfoList` 非空 | +| 漏洞详情可查(有镜像且已扫描) | `DescribeImageVulnerabilityDetails --ImageVersion ` | 返回 CVE 列表、严重等级、修复建议 | + +## 清理 + +安全扫描配置为幂等操作,不产生额外计费资源。如需恢复默认关闭状态: + +```bash +tccli tcr ModifyNamespace \ + --RegistryId '' \ + --NamespaceName '' \ + --IsAutoScan false \ + --region \ + --output json +# expected: exit 0,返回非空 RequestId +``` + +```bash +# 验证已关闭 +tccli tcr DescribeNamespaces \ + --RegistryId '' \ + --NamespaceName '' \ + --region \ + --output json | jq '.NamespaceList[0].AutoScan' +# expected: false +``` + +如配置了部署阻断,一并关闭: + +```bash +tccli tcr ModifyNamespace \ + --RegistryId '' \ + --NamespaceName '' \ + --IsPreventVUL false \ + --region \ + --output json +# expected: exit 0,返回非空 RequestId +``` + +## 排障 + +### 平台限制说明 + +basic 实例在安全扫描场景存在以下**真实平台限制**(非 bug 或故障): + +| 限制 | 说明 | 影响 | +|------|------|------| +| 不支持 `PreventVUL` | 部署阻断为 standard/premium 专属功能 | basic 实例无法通过 CLI 或控制台开启部署阻断 | +| 不支持 `Severity` / `CVEWhitelistItems` | 配合 `PreventVUL` 的参数,同受规格限制 | basic 实例 `ModifyNamespace` 不得传递这些参数 | +| 无手动扫描 API | TCR API 清单无触发单次扫描的公开 Action | 已有镜像的重新扫描只能通过控制台手动触发 | +| `DescribeImages` 不含漏洞详情 | 镜像列表 API 仅返回基本元信息 | 漏洞结果需走 `DescribeImageVulnerabilityDetails` 或控制台 | + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ModifyNamespace` 返回 `Unknown options: --AutoScan, true` | `tccli tcr ModifyNamespace help` 查看合法参数 | 参数名错误。正确参数名为 `--IsAutoScan`(带 `Is` 前缀),非 `--AutoScan` | 修正参数名:`--IsAutoScan true` | +| `ModifyNamespace` 返回 `Unknown options: --PreventVUL, true` | 同上 | 参数名错误。正确参数名为 `--IsPreventVUL`(带 `Is` 前缀),非 `--PreventVUL` | 修正参数名:`--IsPreventVUL true` | +| `ModifyNamespace --IsPreventVUL true` 返回 `OperationDenied` | `tccli tcr DescribeInstances --Registryids '[""]' --region --output json \| jq '.Registries[0].RegistryType'` 查看 `RegistryType` | basic 实例不支持部署阻断功能。此为平台规格限制,调用符合预期 | 升级实例至 standard 或 premium:`tccli tcr ModifyInstance --RegistryId --RegistryType standard --region ` | +| `ModifyNamespace --Severity` 返回 `InvalidParameter` | 检查 `Severity` 值大小写和拼写 | 有效值为 `low`、`medium`、`high`(小写) | 使用合法枚举值,注意小写 | +| `ModifyNamespace --CVEWhitelistItems` 返回 `InvalidParameter` | 检查 JSON 格式是否为 `[{"CVEID":"CVE-XXXX-XXXXX"}]` | JSON 格式错误或 CVE ID 格式不正确 | 确保为有效 JSON 数组,每项含 `CVEID` 字段 | +| `DescribeImageManifests` 返回 `tag not found` | `tccli tcr DescribeImages --RegistryId --NamespaceName --RepositoryName --region ` 确认镜像是否已推送 | 命名空间/仓库内无指定 tag 的镜像 | 先推送镜像后再查询 | +| `DescribeImages` 返回空列表 | 同上 | 仓库内无镜像版本 | 推送镜像后重试 | +| `DescribeImageVulnerabilityDetails` 返回空或报错 | `DescribeImages` 确认镜像存在,并确认控制台已触发过扫描 | 镜像尚未完成扫描,或 `IsAutoScan` 开启前推送的旧镜像未补扫 | 控制台手动触发扫描后重试,或重新推送镜像触发自动扫描 | +| `ModifyNamespace` 返回 `UnauthorizedOperation` | `tccli tcr DescribeNamespaces --RegistryId --region ` 验证 list 权限 | 子账号缺少 `tcr:ModifyNamespace` 权限 | 联系主账号授予 `QcloudTCRFullAccess` 或最小权限策略(含 `tcr:ModifyNamespace`) | + +### 操作成功但行为不符合预期 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 开启自动扫描后已有镜像未触发扫描 | `DescribeNamespaces` 确认 `AutoScan: true` | `IsAutoScan` 仅对新推送的镜像生效,历史镜像不会自动补扫 | 重新推送镜像,或前往控制台手动触发扫描 | +| 自动扫描开启后新推送镜像仍未扫描 | `DescribeImages` 查看镜像 `Kind` 字段 | 镜像扫描仅支持 `DockerManifest` 类型,OCI 制品等其他类型暂不支持 | 确认推送的是 DockerImage 类型镜像 | + +### Tier 升级路径 + +| 场景 | CLI 命令 | 说明 | +|------|---------|------| +| basic → standard(解锁 PreventVUL + 完整扫描) | `tccli tcr ModifyInstance --RegistryId --RegistryType standard --region ` | 升级后即可配置部署阻断、CVE 白名单等完整安全功能 | +| standard → premium(解锁签名策略等) | `tccli tcr ModifyInstance --RegistryId --RegistryType premium --region ` | 解锁复制实例、跨账号同步等高级功能 | +| 升级零预付扣费 | `PayMod=0` 模式下升级不触发预付扣费,按量计费单价随规格提升 | — | +| 升级耗时 | 约 30--60 秒,`DescribeInstanceStatus` 返回 `Running` 即完成 | — | + +## 下一步 + +- [高危镜像部署阻断](../deployment-block)(page_id `63869`) — 基于扫描结果阻断高危镜像部署(需 standard/premium) +- [镜像版本不可变](../immutable-tags)(page_id `58200`) — 锁定重要镜像版本防覆盖 +- [管理镜像仓库](../../image-creation/repository) — 推送镜像以触发安全扫描 +- [创建企业版实例](../../create) — 升级实例规格以解锁部署阻断 +- [环境准备](../../../index.md) — 返回 TCR 工具链入口 + +## 控制台替代 + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 选择实例 → **命名空间** → 单击实例名进入详情 → 基本信息页开启/关闭自动扫描。在 **镜像仓库** → 版本管理页选择指定版本 → 单击 **扫描** 手动触发,扫描完成后查看漏洞详情(安全级别、CVE 列表、修复建议)。部署阻断与 CVE 白名单在命名空间详情页配置(仅 standard/premium 可见)。 diff --git a/src/content/docs/cli/tcr/ops/instance-lifecycle/-tcrctl.md b/src/content/docs/cli/tcr/ops/instance-lifecycle/-tcrctl.md new file mode 100644 index 0000000..253bfde --- /dev/null +++ b/src/content/docs/cli/tcr/ops/instance-lifecycle/-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "销毁退还实例(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[销毁退还实例](https://cloud.tencent.com/document/product/1141/51111) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/instance-lifecycle/.md b/src/content/docs/cli/tcr/ops/instance-lifecycle/.md new file mode 100644 index 0000000..9edcf4c --- /dev/null +++ b/src/content/docs/cli/tcr/ops/instance-lifecycle/.md @@ -0,0 +1,323 @@ +--- +title: "销毁退还实例(tccli)" +description: "· page_id `51111`" +--- + +> 对照官方:[销毁退还实例](https://cloud.tencent.com/document/product/1141/51111) · page_id `51111` + +## 概述 + +通过 `tccli tcr DeleteInstance` 销毁退还 TCR **企业版**实例。 + +> **危险操作:实例删除将永久清除其下的所有命名空间、镜像仓库、Helm Chart、安全策略、访问令牌、触发器、复制规则、内部访问端点、自定义域名配置及关联的 COS 存储数据。数据不可恢复!执行前务必完成全部前置检查。** + +按量计费实例销毁后不再产生费用;包年包月实例按使用时长比例退还至腾讯云账户(含现金和赠送金部分)。详见[退费说明](https://cloud.tencent.com/document/product/1141/53319)。 + +## 前置条件 + +- 完成 [环境准备](../index.md)(`tccli` 已安装并配置凭证)。 +- 待销毁实例存在且当前操作账号具有 `tcr:DeleteInstance` 权限。 +- **实例下的所有命名空间和仓库已清空**,否则 `DeleteInstance` 将返回错误。 +- **实例的跨地域同步复制规则已全部删除**,否则 `DeleteInstance` 将返回 `please delete the replication rule first` 错误。 +- **若当前实例是从(被复制)实例或有从实例,需先删除所有从实例**,否则删除将被阻止。 +- 若实例开启了**删除保护**(`DeletionProtection`),需先关闭(见步骤2)。 + +### 销毁前检查清单 + +| 检查项 | 命令 | 通过标准 | +|--------|------|---------| +| 实例是否开启删除保护 | `DescribeInstances` 查看 `DeletionProtection` | 必须为 `false`(若为 `true` 先执行 `ModifyInstance` 关闭) | +| 命名空间是否已清空 | `DescribeNamespaces` | `NamespaceList` 为空或 `TotalCount` 为 `0` | +| 仓库是否已清空 | `DescribeRepositories` | `TotalCount` 为 `0` | +| 镜像是否已清空 | `DescribeImages` | `ImageInfoList` 为空,`TotalCount` 为 `0` | +| Helm Chart 是否已清理 | 删除所有仓库后自动清空 | 仓库清空后 Chart 数据一同删除 | +| 访问令牌是否已清理 | `DescribeInstanceToken` | `TotalCount` 为 `0` | +| 安全策略是否已清理 | `DescribeSecurityPolicies` | 白名单为空 | +| 跨地域同步复制是否已停止 | `DescribeReplicationInstances` | `TotalCount` 为 `0`,`ReplicationRegistries` 为 `null` | +| 触发器是否已删除 | `DescribeWebhookTrigger` | `TotalCount` 为 `0` | +| 外部/内部访问端点是否已关闭 | `ManageExternalEndpoint` / `ManageInternalEndpoint` | 所有端点已关闭 | +| 自定义域名是否已解绑 | `DescribeInstanceCustomizedDomain` | `TotalCount` 为 `0` | + +## 控制台与 CLI 参数映射 + +| 控制台操作 | CLI | 幂等 | +|-----------|-----|------| +| 实例列表 → 选择实例 | `DescribeInstances` 定位 `RegistryId` | 是 | +| 更多 → 销毁/退还 | `DeleteInstance` | 是(重复删除不存在的实例返回 `ResourceNotFound`) | +| 勾选「随实例删除 COS 存储桶」 | `--DeleteBucket true` | — | +| 检查实例状态 | `DescribeInstanceStatus` | 是 | +| 退还款项(控制台自动处理) | API 无退款参数 | — | + +## 操作步骤 + +### 步骤1:确认待销毁实例的 RegistryId + +列出当前地域所有企业版实例: + +```bash +tccli tcr DescribeInstances --region ap-guangzhou --output json +``` + +**Output:** + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-0e2hz15l", + "RegistryName": "kerwinwjyan-rewrite-001", + "RegistryType": "basic", + "Status": "Running", + "PublicDomain": "kerwinwjyan-rewrite-001.tencentcloudcr.com", + "InternalEndpoint": "10.1.67.13", + "EnableAnonymous": true, + "TokenValidTime": 87600, + "PayMod": 0, + "DeletionProtection": false, + "EnableCosMAZ": false, + "EnableCosVersioning": false + } + ], + "RequestId": "5dcfc309-f2f9-4038-b831-f75acd4fa794" +} +``` + +按名称精确筛选: + +```bash +tccli tcr DescribeInstances --region ap-guangzhou --output json \ + --filter "Registries[?RegistryName=='']" +``` + +记录待销毁实例的 `RegistryId`。 + +### 步骤2:检查并关闭删除保护 + +查看实例是否开启删除保护: + +```bash +tccli tcr DescribeInstances --Registryids '[""]' --region ap-guangzhou --output json \ + --filter "Registries[?RegistryId==''] | [0].DeletionProtection" +``` + +若返回 `true`,需先关闭: + +```bash +tccli tcr ModifyInstance \ + --RegistryId '' \ + --DeletionProtection false \ + --region ap-guangzhou \ + --output json +``` + +> 关闭删除保护后,建议再次执行查询确认 `DeletionProtection` 已变为 `false`。 + +### 步骤3:检查跨地域同步复制 + +确认实例无复制规则和从实例。若有,需先全部删除,否则 `DeleteInstance` 将被阻止: + +```bash +tccli tcr DescribeReplicationInstances --RegistryId '' --region ap-guangzhou --output json +``` + +**Output(无复制关系):** + +```json +{ + "TotalCount": 0, + "ReplicationRegistries": null, + "RequestId": "d94482cb-5f85-43f3-80a5-f0263faff798" +} +``` + +> 若 `TotalCount > 0`,需逐条删除复制规则,直至本命令返回 `TotalCount: 0`。详见[排障](#排障)。 + +### 步骤4:检查实例状态 + +确认实例当前状态正常: + +```bash +tccli tcr DescribeInstanceStatus --RegistryIds '[""]' --region ap-guangzhou --output json +``` + +**Output:** + +```json +{ + "RegistryStatusSet": [ + { + "RegistryId": "tcr-xxxxxxxx", + "Status": "Running", + "Conditions": [ + { + "Type": "", + "Status": "Running", + "Reason": "" + } + ] + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +> **注意:** 销毁退还实例是高危操作。请确保已删除实例下的所有命名空间、仓库、镜像和 Helm Chart。删除命名空间与仓库参见[管理命名空间](https://cloud.tencent.com/document/product/1141/41803)与[管理镜像仓库](https://cloud.tencent.com/document/product/1141/41811)。 + +### 步骤5:执行销毁退还实例 + +> **以下命令仅作文档示例,未在验证实例上实际执行。实例保留用于后续文档验证。** + +#### 仅删除实例(保留 COS 存储桶) + +```bash +tccli tcr DeleteInstance --RegistryId '' --region ap-guangzhou --output json +``` + +**Output:** + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `RegistryId` | String | **是** | 待销毁的实例 ID | +| `DeleteBucket` | Boolean | 否 | 是否同时删除关联 COS 存储桶,默认 `false` | + +#### 同时删除后端 COS 存储桶 + +如确认不再需要实例中的镜像、Chart 等底层数据,可一并删除 COS 桶避免产生后续费用: + +```bash +tccli tcr DeleteInstance --RegistryId '' --DeleteBucket true --region ap-guangzhou --output json +``` + +> **严重警告(请逐条确认后再执行):** +> +> - **带 `--DeleteBucket true` 的删除不可逆!** COS 桶中所有镜像层(layer blob)、Helm Chart 包、安全扫描结果、GC 历史记录将被永久清除,无法通过任何方式恢复。 +> - **级联删除范围:** `DeleteInstance` 将一并清除以下全部资源(单次 API 调用即可生效,无二次确认机会): +> - 所有命名空间(含自动创建和手动创建的) +> - 所有镜像仓库及其中全部 tag +> - 所有 Helm Chart(OCI 制品) +> - 所有安全策略(白名单、黑名单) +> - 所有访问令牌(长期/临时 token) +> - 所有触发器(webhook trigger) +> - 所有 tag 保留规则与不可变规则 +> - 所有自定义域名绑定 +> - 内部/外部访问端点配置 +> - 服务账号及其权限 +> - 跨地域同步复制配置 +> - 若指定 `--DeleteBucket true`,COS 桶及其所有对象一并删除 +> - 若账户已欠费,COS 不允许直接删除关联 Bucket,此时不要使用 `--DeleteBucket true`。正常删除实例后,前往 [COS 控制台](https://console.cloud.tencent.com/cos) 手动管理该 Bucket。 +> - 包年包月实例销毁后,按购买时支付的现金及赠送金按使用时长比例退还,详见[退费说明](https://cloud.tencent.com/document/product/1141/53319)。 +> - **建议在执行删除前,先用 `DescribeInstances` + `DescribeRepositories` + `DescribeImages` + `DescribeNamespaces` 完整审计实例下的资源清单,确保无遗漏。** + +## 验证 + +### Control plane (tccli) + +确认实例已从列表中消失: + +```bash +tccli tcr DescribeInstances --Registryids '[""]' --region ap-guangzhou --output json +``` + +预期 `TotalCount: 0`,`Registries` 为空数组: + +```json +{ + "TotalCount": 0, + "Registries": [], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +| 验证项 | 命令 | 期望结果 | +|--------|------|---------| +| 实例已删除 | `DescribeInstances --Registryids '[""]'` | `TotalCount: 0`,`Registries: []` | +| COS 桶已删除(若指定) | COS 控制台确认 | 关联 COS 桶不再出现在实例关联的存储桶列表中 | +| 不再产生费用 | 费用中心 → 账单详情 | 按量计费实例删除后该 `RegistryId` 不再有扣费记录 | + +### Data plane + +实例删除后,其域名将立即失效: + +```bash +docker login .tencentcloudcr.com +# 预期:Login failed — 域名解析或服务不可用 +``` + +## 清理 + +`DeleteInstance` 本身即为清理操作。以下为删除后仍需关注的事项: + +### 若保留了 COS Bucket(未携带 `--DeleteBucket`) + +前往 [COS 控制台](https://console.cloud.tencent.com/cos) 手动评估和清理。COS 桶保留期间仍会产生存储费用。查找方法: + +1. 桶命名规则:`tcr--` +2. 确认桶内无其他实例共享数据后,可在 COS 控制台直接删除 + +### 若指定了 `--DeleteBucket true` + +无需额外操作,COS 桶已随实例一并删除。但仍建议登录 COS 控制台确认桶列表中无残留。 + +### 清理验证清单 + +| 验证项 | 方法 | 说明 | +|--------|------|------| +| 实例记录已消失 | `DescribeInstances --Registryids '[""]' --region ` | 返回 `TotalCount: 0` | +| COS 桶已清理(若指定) | [COS 控制台](https://console.cloud.tencent.com/cos) 桶列表 | 无 `tcr--*` 前缀的桶 | +| 自定义域名已释放 | DNS 管理控制台 | CNAME 记录指向的 TCR 域名已失效,可删除该 DNS 记录 | +| VPC 内网解析已清除 | PrivateDNS 控制台 | 实例关联的私有域自动释放 | + +## 排障 + +### 删除前检查失败 + +| 现象 | 诊断命令 | 根因 | 修复 | +|------|---------|------|------| +| 实例不在列表中 | `DescribeInstances --Registryids '[""]' --region ` | 实例已被他人删除或 RegistryId 错误 | 确认 RegistryId 是否正确;若已删除无需再操作 | +| `DeletionProtection` 为 `true` | `DescribeInstances` 查看 `DeletionProtection` 字段 | 实例开启了删除保护 | `ModifyInstance --DeletionProtection false` 关闭保护后重试 | +| `DescribeNamespaces` 返回 `TotalCount > 0` | `DescribeNamespaces --RegistryId --region ` | 实例下仍有命名空间(含自动创建的) | 逐一 `DeleteNamespace` 清空所有命名空间 | +| `DescribeRepositories` 返回 `TotalCount > 0` | `DescribeRepositories --RegistryId --region ` | 命名空间下仍有仓库 | 逐一 `DeleteRepository` 清空所有仓库 | +| `DescribeReplicationInstances` 返回 `TotalCount > 0` | `DescribeReplicationInstances --RegistryId --region ` | 存在跨地域同步从实例或复制规则 | 先删除所有从实例和复制规则 | + +### 删除命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `DeleteInstance` 返回 `ResourceNotFound` | — | 实例已不存在或被他人删除 | 无需再次操作;确认是否误填了 RegistryId | +| 删除报错「please delete the replication rule first」 | `tccli tcr DescribeReplicationInstances --RegistryId --region ` 查看从实例列表 | 实例存在跨地域同步复制规则 | 先逐一删除所有复制规则和从实例,直至 `DescribeReplicationInstances` 返回 `TotalCount: 0` | +| 删除报错「has N replication registry」 | 同上 | 当前实例为主实例,仍有 N 个从实例未删除 | 在控制台或通过 API 逐一 `DeleteReplicationInstance` 删除所有从实例,确认 `DescribeReplicationInstances` 返回空后再删主实例 | +| 删除报错「please delete namespace or repository first」 | `tccli tcr DescribeNamespaces --RegistryId --region ` 检查残留 | 实例下仍有命名空间或仓库(含自动创建的) | 先 `DeleteNamespace` / `DeleteRepository` 清空所有资源 | +| 欠费状态下 `--DeleteBucket true` 失败 | — | COS 拒绝删除欠费账户的 Bucket | 不携带 `--DeleteBucket`,正常删除实例后前往 [COS 控制台](https://console.cloud.tencent.com/cos) 手动清理 Bucket | +| `DeleteInstance` 被拒绝,无明确错误信息 | `DescribeInstances` 检查 `DeletionProtection` | 实例开启了 `DeletionProtection` | 执行 `ModifyInstance --DeletionProtection false` 关闭保护后重试 | +| 实例删除耗时过长 | `DescribeInstances` 轮询确认状态 | 后端清理异步进行,涉及 COS 桶、PrivateDNS、VPC 端点等多资源回收 | 等待数分钟后通过 `DescribeInstances` 确认;若超过 10 分钟仍未消失,[在线咨询](https://cloud.tencent.com/online-service?from=doc_1141) | +| `UnauthorizedOperation` | — | 子账号缺少 `tcr:DeleteInstance` 权限 | 联系主账号授予 `QcloudTCRFullAccess` 或包含 `tcr:DeleteInstance` 的最小权限策略 | +| 状态异常无法删除 | `DescribeInstanceStatus` 检查当前状态 | 实例处于 `Deploying`、`Unhealthy` 等非 `Running` 状态 | 等待状态恢复为 `Running`;若长时间处于异常状态,[在线咨询](https://cloud.tencent.com/online-service?from=doc_1141) | +| `FailedOperation.EmptyCoreBody` | — | 后端服务内部异常 | 稍后重试;若持续失败,联系技术支持 | + +### 常见误操作预防 + +| 场景 | 预防措施 | +|------|---------| +| 误删生产实例 | 生产实例开启 `DeletionProtection: true`(`ModifyInstance`),需双重确认才能删除 | +| 误删 COS 桶导致镜像数据丢失 | 不携带 `--DeleteBucket`,保留 COS 桶作为最后的数据备份 | +| 误删有从实例的主实例 | 删除主实例前,`DescribeReplicationInstances` 确认无复制关系 | +| 包年包月实例误删产生退费损失 | 确认退费金额(参考[退费说明](https://cloud.tencent.com/document/product/1141/53319))后再操作 | + +## 下一步 + +- [创建企业版实例](../创建企业版实例/tccli 操作.md)(page_id `51110`) — 重新购买实例 +- [企业版快速入门](../../快速入门/企业版快速入门/tccli 操作.md)(page_id `39287`) — 完整端到端流程 +- [退费说明](https://cloud.tencent.com/document/product/1141/53319) — 包年包月退费计算规则 +- [ModifyInstance](https://cloud.tencent.com/document/api/1141/57155) — 实例属性修改 API 参考 +- [环境准备](../index.md) — 返回 TCR 工具链入口 + +## 控制台替代 + +[容器镜像服务控制台 → 实例管理](https://console.cloud.tencent.com/tcr/instance):选择目标实例右侧 **更多** > **销毁/退还** → 系统将自动检查实例下是否存在命名空间/仓库/复制实例等残留资源 → 按需勾选「随实例删除关联的 COS 存储桶」→ 确认操作。 diff --git a/src/content/docs/cli/tcr/ops/instances/-tcrctl.md b/src/content/docs/cli/tcr/ops/instances/-tcrctl.md new file mode 100644 index 0000000..253bfde --- /dev/null +++ b/src/content/docs/cli/tcr/ops/instances/-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "销毁退还实例(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[销毁退还实例](https://cloud.tencent.com/document/product/1141/51111) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/instances/.md b/src/content/docs/cli/tcr/ops/instances/.md new file mode 100644 index 0000000..9edcf4c --- /dev/null +++ b/src/content/docs/cli/tcr/ops/instances/.md @@ -0,0 +1,323 @@ +--- +title: "销毁退还实例(tccli)" +description: "· page_id `51111`" +--- + +> 对照官方:[销毁退还实例](https://cloud.tencent.com/document/product/1141/51111) · page_id `51111` + +## 概述 + +通过 `tccli tcr DeleteInstance` 销毁退还 TCR **企业版**实例。 + +> **危险操作:实例删除将永久清除其下的所有命名空间、镜像仓库、Helm Chart、安全策略、访问令牌、触发器、复制规则、内部访问端点、自定义域名配置及关联的 COS 存储数据。数据不可恢复!执行前务必完成全部前置检查。** + +按量计费实例销毁后不再产生费用;包年包月实例按使用时长比例退还至腾讯云账户(含现金和赠送金部分)。详见[退费说明](https://cloud.tencent.com/document/product/1141/53319)。 + +## 前置条件 + +- 完成 [环境准备](../index.md)(`tccli` 已安装并配置凭证)。 +- 待销毁实例存在且当前操作账号具有 `tcr:DeleteInstance` 权限。 +- **实例下的所有命名空间和仓库已清空**,否则 `DeleteInstance` 将返回错误。 +- **实例的跨地域同步复制规则已全部删除**,否则 `DeleteInstance` 将返回 `please delete the replication rule first` 错误。 +- **若当前实例是从(被复制)实例或有从实例,需先删除所有从实例**,否则删除将被阻止。 +- 若实例开启了**删除保护**(`DeletionProtection`),需先关闭(见步骤2)。 + +### 销毁前检查清单 + +| 检查项 | 命令 | 通过标准 | +|--------|------|---------| +| 实例是否开启删除保护 | `DescribeInstances` 查看 `DeletionProtection` | 必须为 `false`(若为 `true` 先执行 `ModifyInstance` 关闭) | +| 命名空间是否已清空 | `DescribeNamespaces` | `NamespaceList` 为空或 `TotalCount` 为 `0` | +| 仓库是否已清空 | `DescribeRepositories` | `TotalCount` 为 `0` | +| 镜像是否已清空 | `DescribeImages` | `ImageInfoList` 为空,`TotalCount` 为 `0` | +| Helm Chart 是否已清理 | 删除所有仓库后自动清空 | 仓库清空后 Chart 数据一同删除 | +| 访问令牌是否已清理 | `DescribeInstanceToken` | `TotalCount` 为 `0` | +| 安全策略是否已清理 | `DescribeSecurityPolicies` | 白名单为空 | +| 跨地域同步复制是否已停止 | `DescribeReplicationInstances` | `TotalCount` 为 `0`,`ReplicationRegistries` 为 `null` | +| 触发器是否已删除 | `DescribeWebhookTrigger` | `TotalCount` 为 `0` | +| 外部/内部访问端点是否已关闭 | `ManageExternalEndpoint` / `ManageInternalEndpoint` | 所有端点已关闭 | +| 自定义域名是否已解绑 | `DescribeInstanceCustomizedDomain` | `TotalCount` 为 `0` | + +## 控制台与 CLI 参数映射 + +| 控制台操作 | CLI | 幂等 | +|-----------|-----|------| +| 实例列表 → 选择实例 | `DescribeInstances` 定位 `RegistryId` | 是 | +| 更多 → 销毁/退还 | `DeleteInstance` | 是(重复删除不存在的实例返回 `ResourceNotFound`) | +| 勾选「随实例删除 COS 存储桶」 | `--DeleteBucket true` | — | +| 检查实例状态 | `DescribeInstanceStatus` | 是 | +| 退还款项(控制台自动处理) | API 无退款参数 | — | + +## 操作步骤 + +### 步骤1:确认待销毁实例的 RegistryId + +列出当前地域所有企业版实例: + +```bash +tccli tcr DescribeInstances --region ap-guangzhou --output json +``` + +**Output:** + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-0e2hz15l", + "RegistryName": "kerwinwjyan-rewrite-001", + "RegistryType": "basic", + "Status": "Running", + "PublicDomain": "kerwinwjyan-rewrite-001.tencentcloudcr.com", + "InternalEndpoint": "10.1.67.13", + "EnableAnonymous": true, + "TokenValidTime": 87600, + "PayMod": 0, + "DeletionProtection": false, + "EnableCosMAZ": false, + "EnableCosVersioning": false + } + ], + "RequestId": "5dcfc309-f2f9-4038-b831-f75acd4fa794" +} +``` + +按名称精确筛选: + +```bash +tccli tcr DescribeInstances --region ap-guangzhou --output json \ + --filter "Registries[?RegistryName=='']" +``` + +记录待销毁实例的 `RegistryId`。 + +### 步骤2:检查并关闭删除保护 + +查看实例是否开启删除保护: + +```bash +tccli tcr DescribeInstances --Registryids '[""]' --region ap-guangzhou --output json \ + --filter "Registries[?RegistryId==''] | [0].DeletionProtection" +``` + +若返回 `true`,需先关闭: + +```bash +tccli tcr ModifyInstance \ + --RegistryId '' \ + --DeletionProtection false \ + --region ap-guangzhou \ + --output json +``` + +> 关闭删除保护后,建议再次执行查询确认 `DeletionProtection` 已变为 `false`。 + +### 步骤3:检查跨地域同步复制 + +确认实例无复制规则和从实例。若有,需先全部删除,否则 `DeleteInstance` 将被阻止: + +```bash +tccli tcr DescribeReplicationInstances --RegistryId '' --region ap-guangzhou --output json +``` + +**Output(无复制关系):** + +```json +{ + "TotalCount": 0, + "ReplicationRegistries": null, + "RequestId": "d94482cb-5f85-43f3-80a5-f0263faff798" +} +``` + +> 若 `TotalCount > 0`,需逐条删除复制规则,直至本命令返回 `TotalCount: 0`。详见[排障](#排障)。 + +### 步骤4:检查实例状态 + +确认实例当前状态正常: + +```bash +tccli tcr DescribeInstanceStatus --RegistryIds '[""]' --region ap-guangzhou --output json +``` + +**Output:** + +```json +{ + "RegistryStatusSet": [ + { + "RegistryId": "tcr-xxxxxxxx", + "Status": "Running", + "Conditions": [ + { + "Type": "", + "Status": "Running", + "Reason": "" + } + ] + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +> **注意:** 销毁退还实例是高危操作。请确保已删除实例下的所有命名空间、仓库、镜像和 Helm Chart。删除命名空间与仓库参见[管理命名空间](https://cloud.tencent.com/document/product/1141/41803)与[管理镜像仓库](https://cloud.tencent.com/document/product/1141/41811)。 + +### 步骤5:执行销毁退还实例 + +> **以下命令仅作文档示例,未在验证实例上实际执行。实例保留用于后续文档验证。** + +#### 仅删除实例(保留 COS 存储桶) + +```bash +tccli tcr DeleteInstance --RegistryId '' --region ap-guangzhou --output json +``` + +**Output:** + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `RegistryId` | String | **是** | 待销毁的实例 ID | +| `DeleteBucket` | Boolean | 否 | 是否同时删除关联 COS 存储桶,默认 `false` | + +#### 同时删除后端 COS 存储桶 + +如确认不再需要实例中的镜像、Chart 等底层数据,可一并删除 COS 桶避免产生后续费用: + +```bash +tccli tcr DeleteInstance --RegistryId '' --DeleteBucket true --region ap-guangzhou --output json +``` + +> **严重警告(请逐条确认后再执行):** +> +> - **带 `--DeleteBucket true` 的删除不可逆!** COS 桶中所有镜像层(layer blob)、Helm Chart 包、安全扫描结果、GC 历史记录将被永久清除,无法通过任何方式恢复。 +> - **级联删除范围:** `DeleteInstance` 将一并清除以下全部资源(单次 API 调用即可生效,无二次确认机会): +> - 所有命名空间(含自动创建和手动创建的) +> - 所有镜像仓库及其中全部 tag +> - 所有 Helm Chart(OCI 制品) +> - 所有安全策略(白名单、黑名单) +> - 所有访问令牌(长期/临时 token) +> - 所有触发器(webhook trigger) +> - 所有 tag 保留规则与不可变规则 +> - 所有自定义域名绑定 +> - 内部/外部访问端点配置 +> - 服务账号及其权限 +> - 跨地域同步复制配置 +> - 若指定 `--DeleteBucket true`,COS 桶及其所有对象一并删除 +> - 若账户已欠费,COS 不允许直接删除关联 Bucket,此时不要使用 `--DeleteBucket true`。正常删除实例后,前往 [COS 控制台](https://console.cloud.tencent.com/cos) 手动管理该 Bucket。 +> - 包年包月实例销毁后,按购买时支付的现金及赠送金按使用时长比例退还,详见[退费说明](https://cloud.tencent.com/document/product/1141/53319)。 +> - **建议在执行删除前,先用 `DescribeInstances` + `DescribeRepositories` + `DescribeImages` + `DescribeNamespaces` 完整审计实例下的资源清单,确保无遗漏。** + +## 验证 + +### Control plane (tccli) + +确认实例已从列表中消失: + +```bash +tccli tcr DescribeInstances --Registryids '[""]' --region ap-guangzhou --output json +``` + +预期 `TotalCount: 0`,`Registries` 为空数组: + +```json +{ + "TotalCount": 0, + "Registries": [], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +| 验证项 | 命令 | 期望结果 | +|--------|------|---------| +| 实例已删除 | `DescribeInstances --Registryids '[""]'` | `TotalCount: 0`,`Registries: []` | +| COS 桶已删除(若指定) | COS 控制台确认 | 关联 COS 桶不再出现在实例关联的存储桶列表中 | +| 不再产生费用 | 费用中心 → 账单详情 | 按量计费实例删除后该 `RegistryId` 不再有扣费记录 | + +### Data plane + +实例删除后,其域名将立即失效: + +```bash +docker login .tencentcloudcr.com +# 预期:Login failed — 域名解析或服务不可用 +``` + +## 清理 + +`DeleteInstance` 本身即为清理操作。以下为删除后仍需关注的事项: + +### 若保留了 COS Bucket(未携带 `--DeleteBucket`) + +前往 [COS 控制台](https://console.cloud.tencent.com/cos) 手动评估和清理。COS 桶保留期间仍会产生存储费用。查找方法: + +1. 桶命名规则:`tcr--` +2. 确认桶内无其他实例共享数据后,可在 COS 控制台直接删除 + +### 若指定了 `--DeleteBucket true` + +无需额外操作,COS 桶已随实例一并删除。但仍建议登录 COS 控制台确认桶列表中无残留。 + +### 清理验证清单 + +| 验证项 | 方法 | 说明 | +|--------|------|------| +| 实例记录已消失 | `DescribeInstances --Registryids '[""]' --region ` | 返回 `TotalCount: 0` | +| COS 桶已清理(若指定) | [COS 控制台](https://console.cloud.tencent.com/cos) 桶列表 | 无 `tcr--*` 前缀的桶 | +| 自定义域名已释放 | DNS 管理控制台 | CNAME 记录指向的 TCR 域名已失效,可删除该 DNS 记录 | +| VPC 内网解析已清除 | PrivateDNS 控制台 | 实例关联的私有域自动释放 | + +## 排障 + +### 删除前检查失败 + +| 现象 | 诊断命令 | 根因 | 修复 | +|------|---------|------|------| +| 实例不在列表中 | `DescribeInstances --Registryids '[""]' --region ` | 实例已被他人删除或 RegistryId 错误 | 确认 RegistryId 是否正确;若已删除无需再操作 | +| `DeletionProtection` 为 `true` | `DescribeInstances` 查看 `DeletionProtection` 字段 | 实例开启了删除保护 | `ModifyInstance --DeletionProtection false` 关闭保护后重试 | +| `DescribeNamespaces` 返回 `TotalCount > 0` | `DescribeNamespaces --RegistryId --region ` | 实例下仍有命名空间(含自动创建的) | 逐一 `DeleteNamespace` 清空所有命名空间 | +| `DescribeRepositories` 返回 `TotalCount > 0` | `DescribeRepositories --RegistryId --region ` | 命名空间下仍有仓库 | 逐一 `DeleteRepository` 清空所有仓库 | +| `DescribeReplicationInstances` 返回 `TotalCount > 0` | `DescribeReplicationInstances --RegistryId --region ` | 存在跨地域同步从实例或复制规则 | 先删除所有从实例和复制规则 | + +### 删除命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `DeleteInstance` 返回 `ResourceNotFound` | — | 实例已不存在或被他人删除 | 无需再次操作;确认是否误填了 RegistryId | +| 删除报错「please delete the replication rule first」 | `tccli tcr DescribeReplicationInstances --RegistryId --region ` 查看从实例列表 | 实例存在跨地域同步复制规则 | 先逐一删除所有复制规则和从实例,直至 `DescribeReplicationInstances` 返回 `TotalCount: 0` | +| 删除报错「has N replication registry」 | 同上 | 当前实例为主实例,仍有 N 个从实例未删除 | 在控制台或通过 API 逐一 `DeleteReplicationInstance` 删除所有从实例,确认 `DescribeReplicationInstances` 返回空后再删主实例 | +| 删除报错「please delete namespace or repository first」 | `tccli tcr DescribeNamespaces --RegistryId --region ` 检查残留 | 实例下仍有命名空间或仓库(含自动创建的) | 先 `DeleteNamespace` / `DeleteRepository` 清空所有资源 | +| 欠费状态下 `--DeleteBucket true` 失败 | — | COS 拒绝删除欠费账户的 Bucket | 不携带 `--DeleteBucket`,正常删除实例后前往 [COS 控制台](https://console.cloud.tencent.com/cos) 手动清理 Bucket | +| `DeleteInstance` 被拒绝,无明确错误信息 | `DescribeInstances` 检查 `DeletionProtection` | 实例开启了 `DeletionProtection` | 执行 `ModifyInstance --DeletionProtection false` 关闭保护后重试 | +| 实例删除耗时过长 | `DescribeInstances` 轮询确认状态 | 后端清理异步进行,涉及 COS 桶、PrivateDNS、VPC 端点等多资源回收 | 等待数分钟后通过 `DescribeInstances` 确认;若超过 10 分钟仍未消失,[在线咨询](https://cloud.tencent.com/online-service?from=doc_1141) | +| `UnauthorizedOperation` | — | 子账号缺少 `tcr:DeleteInstance` 权限 | 联系主账号授予 `QcloudTCRFullAccess` 或包含 `tcr:DeleteInstance` 的最小权限策略 | +| 状态异常无法删除 | `DescribeInstanceStatus` 检查当前状态 | 实例处于 `Deploying`、`Unhealthy` 等非 `Running` 状态 | 等待状态恢复为 `Running`;若长时间处于异常状态,[在线咨询](https://cloud.tencent.com/online-service?from=doc_1141) | +| `FailedOperation.EmptyCoreBody` | — | 后端服务内部异常 | 稍后重试;若持续失败,联系技术支持 | + +### 常见误操作预防 + +| 场景 | 预防措施 | +|------|---------| +| 误删生产实例 | 生产实例开启 `DeletionProtection: true`(`ModifyInstance`),需双重确认才能删除 | +| 误删 COS 桶导致镜像数据丢失 | 不携带 `--DeleteBucket`,保留 COS 桶作为最后的数据备份 | +| 误删有从实例的主实例 | 删除主实例前,`DescribeReplicationInstances` 确认无复制关系 | +| 包年包月实例误删产生退费损失 | 确认退费金额(参考[退费说明](https://cloud.tencent.com/document/product/1141/53319))后再操作 | + +## 下一步 + +- [创建企业版实例](../创建企业版实例/tccli 操作.md)(page_id `51110`) — 重新购买实例 +- [企业版快速入门](../../快速入门/企业版快速入门/tccli 操作.md)(page_id `39287`) — 完整端到端流程 +- [退费说明](https://cloud.tencent.com/document/product/1141/53319) — 包年包月退费计算规则 +- [ModifyInstance](https://cloud.tencent.com/document/api/1141/57155) — 实例属性修改 API 参考 +- [环境准备](../index.md) — 返回 TCR 工具链入口 + +## 控制台替代 + +[容器镜像服务控制台 → 实例管理](https://console.cloud.tencent.com/tcr/instance):选择目标实例右侧 **更多** > **销毁/退还** → 系统将自动检查实例下是否存在命名空间/仓库/复制实例等残留资源 → 按需勾选「随实例删除关联的 COS 存储桶」→ 确认操作。 diff --git a/src/content/docs/cli/tcr/ops/instances/create-tcrctl.md b/src/content/docs/cli/tcr/ops/instances/create-tcrctl.md new file mode 100644 index 0000000..7201ec7 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/instances/create-tcrctl.md @@ -0,0 +1,26 @@ +--- +title: "创建企业版实例(tcrctl)" +description: "· page_id `51110`" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> 对照官方:[创建企业版实例](https://cloud.tencent.com/document/product/1141/51110) · page_id `51110` +> +> **P1 — 占位符** · 待 P0 全量完成后填充 `tcrctl` 操作体。 + +## Overview + +(P1 待撰写:`tcrctl create instance` / `tcrctl instance create` 等产品 CLI 命令封装 `CreateInstance`,封装实例等待与状态轮询。) + +## Before you begin + +参见同目录 [tccli 操作.md](.) 的 Before you begin。 + +## Procedure + +> P1 待撰写。 + +## Next steps + +参见同目录 [tccli 操作.md](.) 的 Next steps。 diff --git a/src/content/docs/cli/tcr/ops/instances/create.md b/src/content/docs/cli/tcr/ops/instances/create.md new file mode 100644 index 0000000..3d31354 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/instances/create.md @@ -0,0 +1,636 @@ +--- +title: "创建企业版实例(tccli)" +description: "· page_id `51110` · tccli ≥3.1.107.1 · API 2019-09-24" +--- + +> 对照官方:[创建企业版实例](https://cloud.tencent.com/document/product/1141/51110) · page_id `51110` · tccli ≥3.1.107.1 · API 2019-09-24 + +## 概述 + +通过 `tccli tcr CreateInstance` 创建 TCR 企业版实例。企业版实例是 TCR 的核心资源,提供容器镜像托管、安全扫描、分发与生命周期管理。创建实例时系统自动关联 COS 存储桶存放镜像数据,并生成专用 Registry 域名(格式 `.tencentcloudcr.com`),供 `docker login` / `docker push` / `docker pull` 使用。 + +实例规格分为三档:**基础版**(basic)、**标准版**(standard)、**高级版**(premium),不同规格功能差异如下,详见[计费概述](https://cloud.tencent.com/document/product/1141/40540)。 + +| 规格 | 适用场景 | 安全扫描 | 跨地域同步 | 自定义域名 | +|------|---------|:---:|:---:|:---:| +| basic | 个人开发、小型团队入门 | 不支持 | 不支持 | 不支持 | +| standard | 中型团队、企业测试 | 支持 | 不支持 | 不支持 | +| premium | 生产环境、企业级 | 支持 | 支持 | 支持 | + +创建为异步操作,典型耗时约 1--2 分钟(基础版),需轮询 `DescribeInstances` 或 `DescribeInstanceStatus` 直至 `Status` 为 `Running`。 + +## 前置条件 + +- [环境准备](../index.md) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 +``` + +```bash +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域(如 ap-guangzhou) +``` + +```bash +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tcr:CreateInstance, tcr:DescribeInstances, tcr:DescribeInstanceStatus +# tcr:CheckInstanceName, tcr:DescribeRegions +# tcr:ModifyInstance(如后续需升级规格或关闭删除保护) +# tcr:DeleteInstance(如需清理测试实例) +# 验证:执行 DescribeInstances 确认权限 +tccli tcr DescribeInstances --region ap-guangzhou +# expected: exit 0,返回实例列表(可为空) +``` + +**输出**: + +```json +{ + "TotalCount": 0, + "Registries": [], + "RequestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" +} +``` + +```bash +# 4. 检查关联服务权限 +tccli vpc DescribeVpcs --region ap-guangzhou +# expected: exit 0,返回 VPC 列表(可为空) +``` + +**输出**: + +```json +{ + "TotalCount": 0, + "VpcSet": [], + "RequestId": "b2c3d4e5-f6a7-8901-bcde-f12345678901" +} +``` + +### 资源检查 + +```bash +# 5. 查询 TCR 企业版实例数量(确认未达配额上限) +tccli tcr DescribeInstances --region ap-guangzhou --output json | jq '.TotalCount' +# expected: 当前实例数 < 配额上限(默认每地域 10 个) +``` + +**输出**: + +```text +18 +``` + +```bash +# 6. 验证目标实例名可用 +tccli tcr CheckInstanceName --RegistryName '' --region ap-guangzhou --output json +# expected: exit 0, "IsValidated": true, "DetailCode": 0(名称可用) +``` + +**输出**: + +```json +{ + "IsValidated": true, + "DetailCode": 0, + "RequestId": "72379508-75b5-477c-96fd-3aaa855e5b59" +} +``` + +```bash +# 7. 确认目标地域支持企业版 +tccli tcr DescribeRegions --region ap-guangzhou --output json | jq '.Regions[] | select(.RegionName=="ap-guangzhou")' +# expected: {"Alias": "gz", "RegionId": 1, "RegionName": "ap-guangzhou", "Status": "alluser"} +``` + +**输出**: + +```json +{ + "Alias": "gz", + "RegionId": 1, + "RegionName": "ap-guangzhou", + "Status": "alluser" +} +``` + +### 计费与命名决策 + +- **计费类型**:默认选择按量计费(`RegistryChargeType=0`),按小时计费,测试完成后销毁实例即停止费用。包年包月(`RegistryChargeType=1`)适合长期稳定运行的生产环境,但需额外填写 `RegistryChargePrepaid` 参数。 +- **实例名**:全局唯一,创建后不可修改。建议组合公司缩写、地域、项目名,如 `myco-gz-dev`。 +- **地域**:选择 `ap-guangzhou`(广州),应与容器集群所在地一致。实例地域购买后不可更改。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 / 参数 | 幂等 | +|-----------|------------------|:--:| +| 选择地域(控制台顶部菜单) | `--region ap-guangzhou` | 是 | +| 查询可用地域列表 | `DescribeRegions` | 是 | +| 检查实例名是否可用 | `CheckInstanceName --RegistryName ` | 是 | +| 输入实例名(全局唯一) | `--RegistryName` | — | +| 选择实例规格 | `--RegistryType basic/standard/premium` | — | +| 计费类型选择 | `--RegistryChargeType 0/1` | — | +| 启用多 AZ 存储 | `--EnableCosMAZ` | — | +| 启用 COS 版本控制 | `--EnableCosVersioning` | — | +| 开启删除保护 | `--DeletionProtection` | — | +| 同步标签至 COS 桶 | `--SyncTag` | — | +| 添加实例标签 | `--TagSpecification` | — | +| 勾选协议 / 立即购买 | `CreateInstance` | 否(重复同名报错) | +| 查看实例进度至"运行中" | `DescribeInstances` / `DescribeInstanceStatus` 轮询 | 是 | +| 升级实例规格 | `ModifyInstance --RegistryType premium` | 是(重复升级幂等) | + +## 关键字段说明 + +以下说明 `CreateInstance` 的主要参数。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryName` | String | 是 | 全局唯一,2-255 字符,创建后不可修改。自动生成域名 `.tencentcloudcr.com` | 名称被占用 → `ResourceAlreadyExists.InstanceName`;`CheckInstanceName` 预验证可规避 | +| `RegistryType` | String | 是 | `basic`(基础版)/ `standard`(标准版)/ `premium`(高级版)。不同规格功能差异见[计费概述](https://cloud.tencent.com/document/product/1141/40540)。**仅支持升级,不支持降级** | 填写非法值 → `InvalidParameter.RegistryType` | +| `RegistryChargeType` | Integer | 否 | `0` = 按量计费(默认),`1` = 预付费(包年包月)。若为 `1` 必须同时填 `RegistryChargePrepaid` | 预付费模式缺 `RegistryChargePrepaid` → `MissingParameter.RegistryChargePrepaid` | +| `RegistryChargePrepaid` | Object | 条件 | `RegistryChargeType=1` 时必填。`Period`: 购买月数(1/3/6/12),`RenewFlag`: `0`=手动续费/`1`=自动续费/`2`=不续费 | 未填 → 参数校验失败 | +| `DeletionProtection` | Boolean | 否 | 默认 `false`。`true` 后需先 `ModifyInstance --DeletionProtection false` 才能 `DeleteInstance` | 忘开 → 可能误删生产实例 | +| `EnableCosMAZ` | Boolean | 否 | 默认 `false`。`true` = COS 多 AZ 冗余存储(容灾,费用较高) | 创建后不可更改;非必要勿开 | +| `EnableCosVersioning` | Boolean | 否 | 默认 `false`。`true` = COS 桶多版本控制 | 创建后不可更改 | +| `SyncTag` | Boolean | 否 | 默认 `false`。`true` = 实例标签自动同步至关联 COS 桶 | — | +| `TagSpecification` | Object | 否 | `ResourceType`: `"instance"`,`Tags`: `[{"Key": "env", "Value": "prod"}]` | — | + +## 操作步骤 + +### 步骤1:选择地域 + +查询 TCR 企业版支持的地域列表: + +```bash +tccli tcr DescribeRegions --region ap-guangzhou --output json +# expected: exit 0,Regions 数组各条目 Status: "alluser" +``` + +**输出**: + +```json +{ + "TotalCount": 19, + "Regions": [ + { + "Alias": "gz", + "RegionId": 1, + "RegionName": "ap-guangzhou", + "Status": "alluser" + } + ], + "RequestId": "eac6b301-a322-493a-8e36-83b295459397" +} +``` + +> 各地域 `Status` 为 `alluser` 表示所有用户均可使用。选择实例地域后,后续所有操作需使用相同的 `--region` 值。 + +### 步骤2:检查实例名可用性 + +创建实例前,验证目标实例名是否已被占用: + +```bash +tccli tcr CheckInstanceName --RegistryName '' --region ap-guangzhou --output json +# expected: exit 0, "IsValidated": true(名称可用) +``` + +**名称可用时输出**: + +```json +{ + "IsValidated": true, + "DetailCode": 0, + "RequestId": "72379508-75b5-477c-96fd-3aaa855e5b59" +} +``` + +> 若名称已被占用,返回 `ResourceAlreadyExists.InstanceName`(见[排障](#排障))。建议在实例名后缀加随机字符规避冲突。 + +### 步骤3:创建实例(最简模式) + +#### 选择依据 + +- **RegistryType 选 `basic`**:basic 类型 PayMod=0 后付费(`RegistryChargeType=0`),无预付扣费。开发测试场景最小化成本,满足镜像托管基本需求。需要 premium 功能时可在线升级(`ModifyInstance --RegistryType premium`),但**不支持降级**,升级前确认确实需要高级功能。 +- **RegistryChargeType 选 `0`(按量计费)**:按小时计费,测试完成后销毁实例即停止费用。预付费(`RegistryChargeType=1`)需额外填写 `RegistryChargePrepaid` 参数(`Period` + `RenewFlag`),适合长期生产环境。 +- **region 选 `ap-guangzhou`**:TCR 在广州 region 可用(`DescribeRegions` 返回 19 个 `alluser` region),与容器集群所在地一致以降低内网拉取延迟。可通过 `tccli tcr DescribeRegions --region ap-guangzhou | jq '.Regions[].RegionName'` 确认完整可用列表。 + +#### 最简创建 + +仅含必填字段的最小可运行命令: + +```bash +tccli tcr CreateInstance \ + --RegistryName \ + --RegistryType basic \ + --RegistryChargeType 0 \ + --region +# expected: exit 0,返回 RegistryId +``` + +**输出**: + +```json +{ + "RegistryId": "tcr-example", + "RequestId": "610fa61b-8d81-4cf4-8b24-2295ae96d0f2" +} +``` + +> 记录返回的 `RegistryId`(示例 `tcr-example`),后续所有实例操作均依赖此 ID。 + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | 实例名称 | 全局唯一,2-255 字符 | 自定义,创建前 `CheckInstanceName` 验证 | +| `` | 地域 | 如 `ap-guangzhou` | `tccli tcr DescribeRegions` 查看可用地域 | + +### 步骤4:增强配置(可选) + +#### 4a. 开启删除保护 + +开启删除保护可防止误删生产实例。关闭保护需先执行 `ModifyInstance --DeletionProtection false`: + +```bash +tccli tcr CreateInstance \ + --RegistryName \ + --RegistryType basic \ + --RegistryChargeType 0 \ + --DeletionProtection true \ + --region +# expected: exit 0,返回 RegistryId +``` + +#### 4b. COS 多 AZ + 版本控制(standard+) + +> `EnableCosMAZ` 和 `EnableCosVersioning` 创建后不可更改,非必要勿开。 + +```bash +tccli tcr CreateInstance \ + --RegistryName \ + --RegistryType standard \ + --RegistryChargeType 0 \ + --EnableCosMAZ true \ + --EnableCosVersioning true \ + --region +# expected: exit 0,返回 RegistryId +``` + +#### 4c. 完整增强创建(含标签 + 保护) + +```bash +tccli tcr CreateInstance \ + --RegistryName \ + --RegistryType standard \ + --RegistryChargeType 0 \ + --DeletionProtection true \ + --EnableCosMAZ true \ + --EnableCosVersioning true \ + --SyncTag true \ + --TagSpecification '{"ResourceType":"instance","Tags":[{"Key":"env","Value":"prod"}]}' \ + --region +# expected: exit 0,返回 RegistryId +``` + +#### 4d. 预付费(包年包月) + +预付费(`RegistryChargeType=1`)时必须同时提供 `RegistryChargePrepaid` 参数,指定购买月数和续费方式: + +```bash +tccli tcr CreateInstance \ + --RegistryName \ + --RegistryType standard \ + --RegistryChargeType 1 \ + --RegistryChargePrepaid '{"Period":1,"RenewFlag":0}' \ + --DeletionProtection true \ + --region +# expected: exit 0,返回 RegistryId +``` + +> 预付费实例销毁时按使用时长比例退还至腾讯云账户(含现金和赠送金),详见[退费说明](https://cloud.tencent.com/document/product/1141/53319)。 + +### 步骤5:升级实例规格(basic → premium) + +basic 后付费实例可在线升级至 premium,无需预先创建高级版实例。升级为异步操作,按量计费模式下不触发预付扣费,仅单价随 tier 提升而提高: + +```bash +tccli tcr ModifyInstance \ + --RegistryId \ + --RegistryType premium \ + --region +# expected: exit 0,返回 RequestId +``` + +**输出**: + +```json +{ + "RequestId": "d2656698-9594-48a2-8039-c2e736bb7b95" +} +``` + +| 场景 | CLI 命令 | 说明 | +|------|---------|------| +| basic → standard | `ModifyInstance --RegistryId --RegistryType standard --region ` | 解锁安全扫描、自动扫描、镜像加速等标准版功能 | +| standard → premium | `ModifyInstance --RegistryId --RegistryType premium --region ` | 解锁复制实例、签名策略、跨账号同步等高级版功能 | +| 升级耗时 | 约 30--60 秒,轮询至 `Status: Running` | 升级期间实例正常可用 | +| 降级 | **不支持降级** | 升级前确认确实需要高级功能 | + +### 步骤6:查询实例列表(确认创建) + +查询当前地域所有实例: + +```bash +tccli tcr DescribeInstances --region +# expected: exit 0,TotalCount > 0 +``` + +**输出**: + +```json +{ + "TotalCount": 18, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "tcr-example", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "tcr-example.tencentcloudcr.com", + "InternalEndpoint": "10.1.67.13", + "EnableAnonymous": true, + "TokenValidTime": 87600, + "PayMod": 0, + "DeletionProtection": false, + "EnableCosMAZ": false, + "EnableCosVersioning": false, + "CreatedAt": "2026-06-18T17:29:53+08:00" + } + ], + "RequestId": "5dcfc309-f2f9-4038-b831-f75acd4fa794" +} +``` + +按名称精确过滤查询: + +```bash +tccli tcr DescribeInstances \ + --region \ + --Filters '[{"Name":"RegistryName","Values":[""]}]' +# expected: 返回匹配实例的完整详情 +``` + +**输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "tcr-example", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "tcr-example.tencentcloudcr.com", + "CreatedAt": "2026-06-18T17:29:53+08:00" + } + ], + "RequestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" +} +``` + +> 创建后实例初始状态为 `Deploying`,等待 1--2 分钟后变为 `Running`。 + +### 步骤7:轮询实例状态至 Running + +使用 `DescribeInstanceStatus` 精确获取创建进度: + +```bash +tccli tcr DescribeInstanceStatus \ + --RegistryIds '[""]' \ + --region \ + --output json +# expected: Status: "Running" +``` + +**输出(创建完成 — `Running`)**: + +```json +{ + "RegistryStatusSet": [ + { + "RegistryId": "tcr-example", + "Status": "Running", + "Conditions": [ + { + "Type": "", + "Status": "Running", + "Reason": "" + } + ] + } + ], + "RequestId": "eac6b301-a322-493a-8e36-83b295459397" +} +``` + +> 若 `Status` 为 `Deploying`,等待 15--30 秒后再次轮询。真机实测:基础版实例 7 次轮询后进入 `Running`,累计约 60 秒。 + +也可用 JMESPath 过滤器仅返回状态字段: + +```bash +tccli tcr DescribeInstanceStatus \ + --RegistryIds '[""]' \ + --region \ + --output json \ + --filter "RegistryStatusSet[0].Status" +# expected: "Running" +``` + +### 步骤8:获取实例访问域名 + +实例状态 `Running` 后,记录 `PublicDomain` 供后续 `docker login` / `docker push` / `docker pull` 使用: + +```bash +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region \ + --output json \ + --filter "Registries[0].{PublicDomain:PublicDomain,InternalEndpoint:InternalEndpoint}" +# expected: PublicDomain 非空,InternalEndpoint 非空 +``` + +**输出**: + +```json +{ + "PublicDomain": "tcr-example.tencentcloudcr.com", + "InternalEndpoint": "10.1.67.13" +} +``` + +- `PublicDomain`:公网访问域名,格式 `.tencentcloudcr.com` +- `InternalEndpoint`:内网访问 IP,用于 VPC 内免密拉取 + +## 验证 + +### 控制面(tccli) + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 实例名可用 | `CheckInstanceName --RegistryName '' --region ` | `IsValidated: true`,无 Error | +| 地域支持 | `DescribeRegions --region --output json \| jq '.Regions[] \| select(.RegionName=="ap-guangzhou")'` | `Status: "alluser"` | +| 实例已创建 | `DescribeInstances --Registryids '[""]' --region ` | `TotalCount >= 1`,`Registries` 含目标实例 | +| 实例状态 Running | `DescribeInstanceStatus --RegistryIds '[""]' --region ` | `Status: "Running"` | +| RegistryType 正确 | `DescribeInstances --Registryids '[""]' --region --filter "Registries[0].RegistryType"` | 与创建参数一致(如 `"basic"` 或 `"premium"`) | +| 公网域名就绪 | `DescribeInstances --Registryids '[""]' --region --filter "Registries[0].PublicDomain"` | `".tencentcloudcr.com"` | +| 内网端点就绪 | 同上,取 `InternalEndpoint` | 非空 IP 地址 | +| 删除保护状态 | 同上,取 `DeletionProtection` | 与创建参数一致 | + +### 数据面 + +实例创建后无法直接推送镜像,还需后续配置: + +- [访问配置](../access/permissions/cam-subaccount) — 配置公网/内网访问控制策略(白名单) +- `docker login ` — 使用 `CreateInstanceToken` 获取长期登录凭证 +- [镜像创建](../image-creation/namespace) — 创建命名空间与镜像仓库 +- `docker push` / `docker pull` — 推送/拉取镜像 + +## 清理 + +> **危险警告:`DeleteInstance` 将不可逆清除实例下所有命名空间、镜像仓库、Helm Chart、访问令牌、安全策略、同步复制规则及关联 COS 桶数据。数据不可恢复。** + +### 1. 清理前状态检查 + +```bash +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region \ + --output json +# expected: 确认是待删除的目标实例,记录 RegistryId、RegistryName、DeletionProtection +``` + +**输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "tcr-example", + "RegistryType": "basic", + "Status": "Running", + "DeletionProtection": false, + "CreatedAt": "2026-06-18T17:29:53+08:00" + } + ], + "RequestId": "c3d4e5f6-a7b8-9012-cdef-123456789012" +} +``` + +### 2. 关闭删除保护(若开启) + +若实例开启了 `DeletionProtection`,需先关闭: + +```bash +tccli tcr ModifyInstance \ + --RegistryId \ + --DeletionProtection false \ + --region \ + --output json +# expected: exit 0,返回 RequestId +``` + +### 3. 删除实例 + +```bash +tccli tcr DeleteInstance \ + --RegistryId \ + --region \ + --output json +# expected: exit 0,返回 RequestId +``` + +| 参数 | 类型 | 必填 | 说明 | +|------|------|:--:|------| +| `` | String | 是 | 待删除的实例 ID | +| `--DeleteBucket` | Boolean | 否 | 是否同时删除关联 COS 存储桶。默认 `false`(保留 COS 桶,需手动前往 [COS 控制台](https://console.cloud.tencent.com/cos) 清理) | + +> **计费警告**:按量计费实例删除后停止计费;包年包月实例按使用时长比例退还至腾讯云账户(含现金和赠送金),详见[退费说明](https://cloud.tencent.com/document/product/1141/53319)。 +> +> **级联删除警告**:`DeleteInstance` 将同时删除实例下所有命名空间、仓库、令牌、安全策略、同步复制规则。如使用了 `--DeleteBucket true`,关联 COS 桶及其中全部镜像数据将被永久删除且不可恢复。 +> +> **主从实例依赖警告**:如果实例有关联的复制实例(ReplicationInstance),必须先删除复制实例再删除主实例。使用 `tccli tcr DescribeReplicationInstances --RegistryId --region ` 检查是否存在从实例,确认 `TotalCount` 为 `0` 后再删除。 + +### 4. 验证已删除 + +```bash +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region \ + --output json +# expected: TotalCount: 0,Registries: [] +``` + +**输出**: + +```json +{ + "TotalCount": 0, + "Registries": [], + "RequestId": "d4e5f6a7-b8c9-0123-defa-234567890123" +} +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `CheckInstanceName` 返回 `ResourceAlreadyExists.InstanceName` | `tccli tcr CheckInstanceName --RegistryName '' --region --output json` 确认 `IsValidated: false` | 实例名已被占用(全局唯一) | 更换 `RegistryName`,建议组合公司缩写、地域、项目名,如 `myco-gz-dev`;或在后缀加随机字符 | +| `CreateInstance` 返回 `InvalidParameter.RegistryType` | 检查请求参数中 `RegistryType` 字段值 | 填写了非 `basic`/`standard`/`premium` 的值 | 使用 `"basic"`(基础版)、`"standard"`(标准版)或 `"premium"`(高级版) | +| `CreateInstance` 返回 `MissingParameter.RegistryChargePrepaid` | 检查请求参数中 `RegistryChargeType` 是否为 `1` | `RegistryChargeType=1`(预付费)时忘记提供 `RegistryChargePrepaid` 参数 | 若为预付费,必须同时提供 `RegistryChargePrepaid`:`{"Period":1,"RenewFlag":0}`;若不需要预付费,改用 `RegistryChargeType=0` | +| `CreateInstance` 返回 `FailedOperation.DbError` | 创建前执行 `CheckInstanceName --RegistryName '' --region ` 预验证 | 实例名冲突(与已有实例同名) | 更换名称或先 `DeleteInstance` 删除同名实例。创建前务必执行 `CheckInstanceName` 预验证 | +| `CreateInstance` 返回 `UnauthorizedOperation` | `tccli tcr DescribeInstances --region ` 验证是否有 list 权限 | 子账号缺少 `tcr:CreateInstance` 权限 | 联系主账号授予 `QcloudTCRFullAccess` 或最小权限策略(含 `tcr:CreateInstance`) | +| `CreateInstance` 返回 `LimitExceeded` | `tccli tcr DescribeInstances --region --output json \| jq '.TotalCount'` 统计已有实例数 | 企业版实例数量已达配额上限 | 此为环境限制,非命令错误。前往 [配额中心](https://console.cloud.tencent.com/tcr/instance) 申请提升,或退还闲置实例 | +| `CreateInstance` 返回 `InternalError` / `FailedOperation` | 保留返回的 `RequestId` | 云端服务暂时不可用 | 稍后重试(间隔 30 秒以上);若持续失败,凭 `RequestId` [在线咨询](https://cloud.tencent.com/online-service?from=doc_1141) | +| `DescribeRegions` 未返回目标地域 | `tccli tcr DescribeRegions --region --output json \| jq '.Regions[] \| .RegionName'` 查看完整列表 | 目标地域未对企业版开放,或仅限白名单用户 | 确认 `Regions[].Status` 为 `alluser`;部分地域(如金融专区)需联系腾讯云开通白名单 | +| `DeleteInstance` 被拒绝 | `tccli tcr DescribeInstances --Registryids '[""]' --region --output json \| jq '.Registries[0].DeletionProtection'` | 实例开启了删除保护 | `ModifyInstance --DeletionProtection false` 关闭后重试 | +| `DeleteInstance` 返回 "has N replication registry" | `tccli tcr DescribeReplicationInstances --RegistryId --region ` 检查从实例 | 主实例下存在未删除的从实例(复制实例),需先清空从实例 | 逐个执行 `DeleteReplicationInstance`,确认 `DescribeReplicationInstances` 返回 `TotalCount: 0` 后再删除主实例 | + +### 创建已提交但状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 返回了 `RegistryId` 但 `Status` 长时间为 `Deploying`(超过 5 分钟) | `tccli tcr DescribeInstanceStatus --RegistryIds '[""]' --region ` 循环轮询,查看 `Status` 和 `Conditions` | 异步创建缓慢(正常情况基础版 7 次轮询约 60 秒)或 COS/VPC 后端依赖创建卡住(异常) | 继续轮询;超过 5 分钟确认 [COS](https://console.cloud.tencent.com/cos5)、[VPC](https://console.cloud.tencent.com/vpc)、[PrivateDNS](https://console.cloud.tencent.com/privatedns) 已开通;凭 `RegistryId` 和 `RequestId` [在线咨询](https://cloud.tencent.com/online-service?from=doc_1141) | +| `Status: "Unhealthy"` | 同上,查看 `Conditions` 字段的 `Reason` | 后端存储或网络异常 | 查看 `Conditions.Reason` 定位问题;保留 `RegistryId`、`RequestId`、创建 JSON → 提交工单 | +| `Status: "FailedCreated"` | 同上 | 创建流程失败 | 保留 `RegistryId`、`RequestId`、创建 JSON → `DeleteInstance` 删除后修正参数重新创建 | +| 创建成功但某属性不符预期 | `tccli tcr DescribeInstances --Registryids '[""]' --region ` 检查各可选字段 | 创建时遗漏可选参数(如 `DeletionProtection`) | 可调属性用 `ModifyInstance` 修改;不可调属性(如 `EnableCosMAZ`)需删除重建 | + +### 常见误操作预防 + +| 场景 | 预防措施 | +|------|---------| +| 误删生产实例 | 生产实例开启 `DeletionProtection: true`,需双重确认才能删除 | +| `RegistryType` 填错 | 不确定规格时先用 `basic`(按量计费),需要高级功能时再 `ModifyInstance` 升级 | +| 预付费参数遗漏 | 若 `RegistryChargeType=1`,务必同时提供 `RegistryChargePrepaid: {"Period":1,"RenewFlag":0}` | +| 包年包月实例误删产生退费损失 | 确认退费金额(参考[退费说明](https://cloud.tencent.com/document/product/1141/53319))后再操作 | + +## 下一步 + +- [销毁退还实例](../delete)(page_id `51111`) — 退还或销毁不再使用的实例 +- [访问配置](../access/permissions/cam-subaccount) — 配置公网/内网访问与安全策略 +- [镜像创建](../image-creation/namespace) — 创建命名空间与镜像仓库 +- [镜像安全](../image-security/vulnerability-scan) — 镜像安全扫描与漏洞修复 +- [环境准备](../index.md) — 返回 TCR 工具链入口 + +## 控制台替代 + +[容器镜像服务 → 实例管理 → 新建](https://console.cloud.tencent.com/tcr/instance):选择地域,填写实例名、规格(basic/standard/premium),按需启用多 AZ 与版本控制,配置标签与删除保护,阅读协议后购买,等待实例状态变为"运行中"。 diff --git a/src/content/docs/cli/tcr/ops/instances/create/examples/tcr-create-basic.json b/src/content/docs/cli/tcr/ops/instances/create/examples/tcr-create-basic.json new file mode 100644 index 0000000..36f6ddf --- /dev/null +++ b/src/content/docs/cli/tcr/ops/instances/create/examples/tcr-create-basic.json @@ -0,0 +1,14 @@ +{ + "RegistryName": "tcr-example", + "RegistryType": "basic", + "RegistryChargeType": 0, + "SyncTag": true, + "EnableCosMAZ": false, + "DeletionProtection": false, + "TagSpecification": { + "ResourceType": "instance", + "Tags": [ + {"Key": "env", "Value": "dev"} + ] + } +} diff --git a/src/content/docs/cli/tcr/ops/instances/create/examples/tcr-create-enhanced.json b/src/content/docs/cli/tcr/ops/instances/create/examples/tcr-create-enhanced.json new file mode 100644 index 0000000..8e3e5b3 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/instances/create/examples/tcr-create-enhanced.json @@ -0,0 +1,15 @@ +{ + "RegistryName": "tcr-example", + "RegistryType": "standard", + "RegistryChargeType": 0, + "DeletionProtection": true, + "EnableCosMAZ": true, + "EnableCosVersioning": true, + "SyncTag": true, + "TagSpecification": { + "ResourceType": "instance", + "Tags": [ + {"Key": "env", "Value": "prod"} + ] + } +} diff --git a/src/content/docs/cli/tcr/ops/instances/create/examples/tcr-create-prepaid.json b/src/content/docs/cli/tcr/ops/instances/create/examples/tcr-create-prepaid.json new file mode 100644 index 0000000..73cd885 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/instances/create/examples/tcr-create-prepaid.json @@ -0,0 +1,11 @@ +{ + "RegistryName": "tcr-example", + "RegistryType": "basic", + "RegistryChargeType": 1, + "RegistryChargePrepaid": { + "Period": 1, + "RenewFlag": 0 + }, + "SyncTag": true, + "DeletionProtection": false +} diff --git a/src/content/docs/cli/tcr/ops/instances/delete-tcrctl.md b/src/content/docs/cli/tcr/ops/instances/delete-tcrctl.md new file mode 100644 index 0000000..253bfde --- /dev/null +++ b/src/content/docs/cli/tcr/ops/instances/delete-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "销毁退还实例(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[销毁退还实例](https://cloud.tencent.com/document/product/1141/51111) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/instances/delete.md b/src/content/docs/cli/tcr/ops/instances/delete.md new file mode 100644 index 0000000..7e0b299 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/instances/delete.md @@ -0,0 +1,323 @@ +--- +title: "销毁退还实例(tccli)" +description: "· page_id `51111`" +--- + +> 对照官方:[销毁退还实例](https://cloud.tencent.com/document/product/1141/51111) · page_id `51111` + +## 概述 + +通过 `tccli tcr DeleteInstance` 销毁退还 TCR **企业版**实例。 + +> **危险操作:实例删除将永久清除其下的所有命名空间、镜像仓库、Helm Chart、安全策略、访问令牌、触发器、复制规则、内部访问端点、自定义域名配置及关联的 COS 存储数据。数据不可恢复!执行前务必完成全部前置检查。** + +按量计费实例销毁后不再产生费用;包年包月实例按使用时长比例退还至腾讯云账户(含现金和赠送金部分)。详见[退费说明](https://cloud.tencent.com/document/product/1141/53319)。 + +## 前置条件 + +- 完成 [环境准备](../index.md)(`tccli` 已安装并配置凭证)。 +- 待销毁实例存在且当前操作账号具有 `tcr:DeleteInstance` 权限。 +- **实例下的所有命名空间和仓库已清空**,否则 `DeleteInstance` 将返回错误。 +- **实例的跨地域同步复制规则已全部删除**,否则 `DeleteInstance` 将返回 `please delete the replication rule first` 错误。 +- **若当前实例是从(被复制)实例或有从实例,需先删除所有从实例**,否则删除将被阻止。 +- 若实例开启了**删除保护**(`DeletionProtection`),需先关闭(见步骤2)。 + +### 销毁前检查清单 + +| 检查项 | 命令 | 通过标准 | +|--------|------|---------| +| 实例是否开启删除保护 | `DescribeInstances` 查看 `DeletionProtection` | 必须为 `false`(若为 `true` 先执行 `ModifyInstance` 关闭) | +| 命名空间是否已清空 | `DescribeNamespaces` | `NamespaceList` 为空或 `TotalCount` 为 `0` | +| 仓库是否已清空 | `DescribeRepositories` | `TotalCount` 为 `0` | +| 镜像是否已清空 | `DescribeImages` | `ImageInfoList` 为空,`TotalCount` 为 `0` | +| Helm Chart 是否已清理 | 删除所有仓库后自动清空 | 仓库清空后 Chart 数据一同删除 | +| 访问令牌是否已清理 | `DescribeInstanceToken` | `TotalCount` 为 `0` | +| 安全策略是否已清理 | `DescribeSecurityPolicies` | 白名单为空 | +| 跨地域同步复制是否已停止 | `DescribeReplicationInstances` | `TotalCount` 为 `0`,`ReplicationRegistries` 为 `null` | +| 触发器是否已删除 | `DescribeWebhookTrigger` | `TotalCount` 为 `0` | +| 外部/内部访问端点是否已关闭 | `ManageExternalEndpoint` / `ManageInternalEndpoint` | 所有端点已关闭 | +| 自定义域名是否已解绑 | `DescribeInstanceCustomizedDomain` | `TotalCount` 为 `0` | + +## 控制台与 CLI 参数映射 + +| 控制台操作 | CLI | 幂等 | +|-----------|-----|------| +| 实例列表 → 选择实例 | `DescribeInstances` 定位 `RegistryId` | 是 | +| 更多 → 销毁/退还 | `DeleteInstance` | 是(重复删除不存在的实例返回 `ResourceNotFound`) | +| 勾选「随实例删除 COS 存储桶」 | `--DeleteBucket true` | — | +| 检查实例状态 | `DescribeInstanceStatus` | 是 | +| 退还款项(控制台自动处理) | API 无退款参数 | — | + +## 操作步骤 + +### 步骤1:确认待销毁实例的 RegistryId + +列出当前地域所有企业版实例: + +```bash +tccli tcr DescribeInstances --region ap-guangzhou --output json +``` + +**Output:** + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-0e2hz15l", + "RegistryName": "kerwinwjyan-rewrite-001", + "RegistryType": "basic", + "Status": "Running", + "PublicDomain": "kerwinwjyan-rewrite-001.tencentcloudcr.com", + "InternalEndpoint": "10.1.67.13", + "EnableAnonymous": true, + "TokenValidTime": 87600, + "PayMod": 0, + "DeletionProtection": false, + "EnableCosMAZ": false, + "EnableCosVersioning": false + } + ], + "RequestId": "5dcfc309-f2f9-4038-b831-f75acd4fa794" +} +``` + +按名称精确筛选: + +```bash +tccli tcr DescribeInstances --region ap-guangzhou --output json \ + --filter "Registries[?RegistryName=='']" +``` + +记录待销毁实例的 `RegistryId`。 + +### 步骤2:检查并关闭删除保护 + +查看实例是否开启删除保护: + +```bash +tccli tcr DescribeInstances --Registryids '[""]' --region ap-guangzhou --output json \ + --filter "Registries[?RegistryId==''] | [0].DeletionProtection" +``` + +若返回 `true`,需先关闭: + +```bash +tccli tcr ModifyInstance \ + --RegistryId '' \ + --DeletionProtection false \ + --region ap-guangzhou \ + --output json +``` + +> 关闭删除保护后,建议再次执行查询确认 `DeletionProtection` 已变为 `false`。 + +### 步骤3:检查跨地域同步复制 + +确认实例无复制规则和从实例。若有,需先全部删除,否则 `DeleteInstance` 将被阻止: + +```bash +tccli tcr DescribeReplicationInstances --RegistryId '' --region ap-guangzhou --output json +``` + +**Output(无复制关系):** + +```json +{ + "TotalCount": 0, + "ReplicationRegistries": null, + "RequestId": "d94482cb-5f85-43f3-80a5-f0263faff798" +} +``` + +> 若 `TotalCount > 0`,需逐条删除复制规则,直至本命令返回 `TotalCount: 0`。详见[排障](#排障)。 + +### 步骤4:检查实例状态 + +确认实例当前状态正常: + +```bash +tccli tcr DescribeInstanceStatus --RegistryIds '[""]' --region ap-guangzhou --output json +``` + +**Output:** + +```json +{ + "RegistryStatusSet": [ + { + "RegistryId": "tcr-xxxxxxxx", + "Status": "Running", + "Conditions": [ + { + "Type": "", + "Status": "Running", + "Reason": "" + } + ] + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +> **注意:** 销毁退还实例是高危操作。请确保已删除实例下的所有命名空间、仓库、镜像和 Helm Chart。删除命名空间与仓库参见[管理命名空间](https://cloud.tencent.com/document/product/1141/41803)与[管理镜像仓库](https://cloud.tencent.com/document/product/1141/41811)。 + +### 步骤5:执行销毁退还实例 + +> **以下命令仅作文档示例,未在验证实例上实际执行。实例保留用于后续文档验证。** + +#### 仅删除实例(保留 COS 存储桶) + +```bash +tccli tcr DeleteInstance --RegistryId '' --region ap-guangzhou --output json +``` + +**Output:** + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `RegistryId` | String | **是** | 待销毁的实例 ID | +| `DeleteBucket` | Boolean | 否 | 是否同时删除关联 COS 存储桶,默认 `false` | + +#### 同时删除后端 COS 存储桶 + +如确认不再需要实例中的镜像、Chart 等底层数据,可一并删除 COS 桶避免产生后续费用: + +```bash +tccli tcr DeleteInstance --RegistryId '' --DeleteBucket true --region ap-guangzhou --output json +``` + +> **严重警告(请逐条确认后再执行):** +> +> - **带 `--DeleteBucket true` 的删除不可逆!** COS 桶中所有镜像层(layer blob)、Helm Chart 包、安全扫描结果、GC 历史记录将被永久清除,无法通过任何方式恢复。 +> - **级联删除范围:** `DeleteInstance` 将一并清除以下全部资源(单次 API 调用即可生效,无二次确认机会): +> - 所有命名空间(含自动创建和手动创建的) +> - 所有镜像仓库及其中全部 tag +> - 所有 Helm Chart(OCI 制品) +> - 所有安全策略(白名单、黑名单) +> - 所有访问令牌(长期/临时 token) +> - 所有触发器(webhook trigger) +> - 所有 tag 保留规则与不可变规则 +> - 所有自定义域名绑定 +> - 内部/外部访问端点配置 +> - 服务账号及其权限 +> - 跨地域同步复制配置 +> - 若指定 `--DeleteBucket true`,COS 桶及其所有对象一并删除 +> - 若账户已欠费,COS 不允许直接删除关联 Bucket,此时不要使用 `--DeleteBucket true`。正常删除实例后,前往 [COS 控制台](https://console.cloud.tencent.com/cos) 手动管理该 Bucket。 +> - 包年包月实例销毁后,按购买时支付的现金及赠送金按使用时长比例退还,详见[退费说明](https://cloud.tencent.com/document/product/1141/53319)。 +> - **建议在执行删除前,先用 `DescribeInstances` + `DescribeRepositories` + `DescribeImages` + `DescribeNamespaces` 完整审计实例下的资源清单,确保无遗漏。** + +## 验证 + +### Control plane (tccli) + +确认实例已从列表中消失: + +```bash +tccli tcr DescribeInstances --Registryids '[""]' --region ap-guangzhou --output json +``` + +预期 `TotalCount: 0`,`Registries` 为空数组: + +```json +{ + "TotalCount": 0, + "Registries": [], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +| 验证项 | 命令 | 期望结果 | +|--------|------|---------| +| 实例已删除 | `DescribeInstances --Registryids '[""]'` | `TotalCount: 0`,`Registries: []` | +| COS 桶已删除(若指定) | COS 控制台确认 | 关联 COS 桶不再出现在实例关联的存储桶列表中 | +| 不再产生费用 | 费用中心 → 账单详情 | 按量计费实例删除后该 `RegistryId` 不再有扣费记录 | + +### Data plane + +实例删除后,其域名将立即失效: + +```bash +docker login .tencentcloudcr.com +# 预期:Login failed — 域名解析或服务不可用 +``` + +## 清理 + +`DeleteInstance` 本身即为清理操作。以下为删除后仍需关注的事项: + +### 若保留了 COS Bucket(未携带 `--DeleteBucket`) + +前往 [COS 控制台](https://console.cloud.tencent.com/cos) 手动评估和清理。COS 桶保留期间仍会产生存储费用。查找方法: + +1. 桶命名规则:`tcr--` +2. 确认桶内无其他实例共享数据后,可在 COS 控制台直接删除 + +### 若指定了 `--DeleteBucket true` + +无需额外操作,COS 桶已随实例一并删除。但仍建议登录 COS 控制台确认桶列表中无残留。 + +### 清理验证清单 + +| 验证项 | 方法 | 说明 | +|--------|------|------| +| 实例记录已消失 | `DescribeInstances --Registryids '[""]' --region ` | 返回 `TotalCount: 0` | +| COS 桶已清理(若指定) | [COS 控制台](https://console.cloud.tencent.com/cos) 桶列表 | 无 `tcr--*` 前缀的桶 | +| 自定义域名已释放 | DNS 管理控制台 | CNAME 记录指向的 TCR 域名已失效,可删除该 DNS 记录 | +| VPC 内网解析已清除 | PrivateDNS 控制台 | 实例关联的私有域自动释放 | + +## 排障 + +### 删除前检查失败 + +| 现象 | 诊断命令 | 根因 | 修复 | +|------|---------|------|------| +| 实例不在列表中 | `DescribeInstances --Registryids '[""]' --region ` | 实例已被他人删除或 RegistryId 错误 | 确认 RegistryId 是否正确;若已删除无需再操作 | +| `DeletionProtection` 为 `true` | `DescribeInstances` 查看 `DeletionProtection` 字段 | 实例开启了删除保护 | `ModifyInstance --DeletionProtection false` 关闭保护后重试 | +| `DescribeNamespaces` 返回 `TotalCount > 0` | `DescribeNamespaces --RegistryId --region ` | 实例下仍有命名空间(含自动创建的) | 逐一 `DeleteNamespace` 清空所有命名空间 | +| `DescribeRepositories` 返回 `TotalCount > 0` | `DescribeRepositories --RegistryId --region ` | 命名空间下仍有仓库 | 逐一 `DeleteRepository` 清空所有仓库 | +| `DescribeReplicationInstances` 返回 `TotalCount > 0` | `DescribeReplicationInstances --RegistryId --region ` | 存在跨地域同步从实例或复制规则 | 先删除所有从实例和复制规则 | + +### 删除命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `DeleteInstance` 返回 `ResourceNotFound` | — | 实例已不存在或被他人删除 | 无需再次操作;确认是否误填了 RegistryId | +| 删除报错「please delete the replication rule first」 | `tccli tcr DescribeReplicationInstances --RegistryId --region ` 查看从实例列表 | 实例存在跨地域同步复制规则 | 先逐一删除所有复制规则和从实例,直至 `DescribeReplicationInstances` 返回 `TotalCount: 0` | +| 删除报错「has N replication registry」 | 同上 | 当前实例为主实例,仍有 N 个从实例未删除 | 在控制台或通过 API 逐一 `DeleteReplicationInstance` 删除所有从实例,确认 `DescribeReplicationInstances` 返回空后再删主实例 | +| 删除报错「please delete namespace or repository first」 | `tccli tcr DescribeNamespaces --RegistryId --region ` 检查残留 | 实例下仍有命名空间或仓库(含自动创建的) | 先 `DeleteNamespace` / `DeleteRepository` 清空所有资源 | +| 欠费状态下 `--DeleteBucket true` 失败 | — | COS 拒绝删除欠费账户的 Bucket | 不携带 `--DeleteBucket`,正常删除实例后前往 [COS 控制台](https://console.cloud.tencent.com/cos) 手动清理 Bucket | +| `DeleteInstance` 被拒绝,无明确错误信息 | `DescribeInstances` 检查 `DeletionProtection` | 实例开启了 `DeletionProtection` | 执行 `ModifyInstance --DeletionProtection false` 关闭保护后重试 | +| 实例删除耗时过长 | `DescribeInstances` 轮询确认状态 | 后端清理异步进行,涉及 COS 桶、PrivateDNS、VPC 端点等多资源回收 | 等待数分钟后通过 `DescribeInstances` 确认;若超过 10 分钟仍未消失,[在线咨询](https://cloud.tencent.com/online-service?from=doc_1141) | +| `UnauthorizedOperation` | — | 子账号缺少 `tcr:DeleteInstance` 权限 | 联系主账号授予 `QcloudTCRFullAccess` 或包含 `tcr:DeleteInstance` 的最小权限策略 | +| 状态异常无法删除 | `DescribeInstanceStatus` 检查当前状态 | 实例处于 `Deploying`、`Unhealthy` 等非 `Running` 状态 | 等待状态恢复为 `Running`;若长时间处于异常状态,[在线咨询](https://cloud.tencent.com/online-service?from=doc_1141) | +| `FailedOperation.EmptyCoreBody` | — | 后端服务内部异常 | 稍后重试;若持续失败,联系技术支持 | + +### 常见误操作预防 + +| 场景 | 预防措施 | +|------|---------| +| 误删生产实例 | 生产实例开启 `DeletionProtection: true`(`ModifyInstance`),需双重确认才能删除 | +| 误删 COS 桶导致镜像数据丢失 | 不携带 `--DeleteBucket`,保留 COS 桶作为最后的数据备份 | +| 误删有从实例的主实例 | 删除主实例前,`DescribeReplicationInstances` 确认无复制关系 | +| 包年包月实例误删产生退费损失 | 确认退费金额(参考[退费说明](https://cloud.tencent.com/document/product/1141/53319))后再操作 | + +## 下一步 + +- [创建企业版实例](../create)(page_id `51110`) — 重新购买实例 +- [企业版快速入门](../../quickstart/enterprise)(page_id `39287`) — 完整端到端流程 +- [退费说明](https://cloud.tencent.com/document/product/1141/53319) — 包年包月退费计算规则 +- [ModifyInstance](https://cloud.tencent.com/document/api/1141/57155) — 实例属性修改 API 参考 +- [环境准备](../index.md) — 返回 TCR 工具链入口 + +## 控制台替代 + +[容器镜像服务控制台 → 实例管理](https://console.cloud.tencent.com/tcr/instance):选择目标实例右侧 **更多** > **销毁/退还** → 系统将自动检查实例下是否存在命名空间/仓库/复制实例等残留资源 → 按需勾选「随实例删除关联的 COS 存储桶」→ 确认操作。 diff --git a/src/content/docs/cli/tcr/ops/oci-artifacts/helm-chart-tcrctl.md b/src/content/docs/cli/tcr/ops/oci-artifacts/helm-chart-tcrctl.md new file mode 100644 index 0000000..4f02f25 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/oci-artifacts/helm-chart-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "托管 Helm Chart(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[托管 Helm Chart](https://cloud.tencent.com/document/product/1141/41944) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/oci-artifacts/helm-chart.md b/src/content/docs/cli/tcr/ops/oci-artifacts/helm-chart.md new file mode 100644 index 0000000..2740558 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/oci-artifacts/helm-chart.md @@ -0,0 +1,403 @@ +--- +title: "托管 Helm Chart(tccli)" +description: "· page_id `41944`" +--- + +> 对照官方:[托管 Helm Chart](https://cloud.tencent.com/document/product/1141/41944) · page_id `41944` + +## 概述 + +TCR 企业版实例支持托管 Helm Chart,用户可在同个命名空间内同时管理容器镜像及 Helm Chart,实现在业务项目内统一管理容器镜像和 Helm Chart 两种云原生交付物。 + +Helm Chart 仓库继承其所属命名空间的公开及私有属性。在权限管理上,Helm Chart 与容器镜像共用 **repository** 资源类型(如 `qcs::tcr:$region:$account:repository/tcr-xxxxxx/project-a/*` 包含命名空间内全部镜像仓库及 Helm Chart)。 + +TCR 支持两种 Helm Chart 管理方式: + +- **OCI 制品**(Helm 3.8+,推荐):将 Helm Chart 作为 OCI 制品,通过 `helm registry login`、`helm push`、`helm pull` 操作 TCR OCI 端点 +- **Chart Museum API**(传统方式):通过 `helm repo add` 添加 TCR 提供的 Chart Museum 端点(`chartrepo`),使用 `helm cm-push` / `helm fetch` 上传下载 + +> **注意**:仅企业版实例支持托管 Helm Chart。Helm 的上传/下载操作通过 `helm` CLI(非 tccli)完成,tccli 仅用于查询 Chart 信息和下载链接。 + +## 前置条件 + +- [环境准备](../../index.md) +- 已 [购买企业版实例](../../create),实例 `Status` 为 `Running` +- 已 [创建命名空间](../../image-creation/namespace) +- 已 [获取实例访问凭证](https://cloud.tencent.com/document/product/1141/41829)(用户名及 Token) +- 本机已安装 Helm 客户端(v3.8+ 推荐,OCI 方式必须 v3.8+) +- 如使用子账号操作,详见 [企业版授权方案示例](https://cloud.tencent.com/document/product/1141/41417) + +### 环境检查 + +```bash +# 1. 确认实例存在且状态正常 +tccli tcr DescribeInstances --Registryids '["tcr-0e2hz15l"]' --region ap-guangzhou --output json +``` + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-0e2hz15l", + "RegistryName": "kerwinwjyan-rewrite-001", + "RegistryType": "basic", + "Status": "Running", + "PublicDomain": "kerwinwjyan-rewrite-001.tencentcloudcr.com", + "InternalEndpoint": "10.1.67.13", + "EnableAnonymous": true, + "TokenValidTime": 87600, + "DeletionProtection": false + } + ], + "RequestId": "bf6d6053-85c2-4f10-853b-4492ad4a98b5" +} +``` + +```bash +# 2. 确认命名空间已存在 +tccli tcr DescribeNamespaces --RegistryId tcr-0e2hz15l --region ap-guangzhou --output json +``` + +```json +{ + "NamespaceList": [ + { + "Name": "skillhub", + "CreationTime": "2026-06-16T08:37:05.263Z", + "Public": false, + "NamespaceId": 1 + } + ], + "TotalCount": 1, + "RequestId": "329610f4-308b-442f-b72e-a0a0fabed229" +} +``` + +```bash +# 3. 确认 helm 客户端版本(OCI 方式需 v3.8+) +helm version --short +``` + +```text +v3.14.0+g3333d44 +``` + +### 命名约束 + +Helm Chart 名称需符合 Helm 命名规范,版本号遵循 SemVer 2.0(如 `1.0.0`)。Chart 的 `name` 和 `version` 定义在 `Chart.yaml` 中。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | CLI | 幂等 | +|-----------|-----|------| +| 查看 Helm Chart 列表 | `tccli tcr DescribeRepositories --RegistryId --NamespaceName ` | 是 | +| 获取 Chart 版本下载信息 | `tccli tcr DescribeChartDownloadInfo --RegistryId --NamespaceName --ChartName --ChartVersion ` | 是 | +| 下载 Helm Chart(API 触发) | `tccli tcr DownloadHelmChart --RegistryId --NamespaceName --ChartName --ChartVersion ` | 是 | +| OCI 登录 | `helm registry login .tencentcloudcr.com --username --password ` | -- | +| 推送 Helm Chart(OCI) | `helm push .tgz oci://.tencentcloudcr.com/` | 是(覆盖同版本) | +| 拉取 Helm Chart(OCI) | `helm pull oci://.tencentcloudcr.com// --version ` | 是 | +| 安装 Helm Chart | `helm install oci://.tencentcloudcr.com// --version ` | 否(同名 release 冲突) | +| 添加 Helm 仓库(Chart Museum) | `helm repo add https://.tencentcloudcr.com/chartrepo/ --username --password ` | 否(重名报错) | +| 推送 Helm Chart(Chart Museum) | `helm cm-push ` | 是(覆盖同版本) | +| 拉取 Helm Chart(Chart Museum) | `helm fetch / --version ` | 是 | + +## 操作步骤 + +### 步骤1:查看 Helm Chart 仓库列表 + +列出指定命名空间内的 Helm Chart 仓库(与镜像仓库共用 `DescribeRepositories`)。 + +当前实例 `tcr-0e2hz15l` 的 `skillhub` 命名空间尚无仓库: + +```bash +tccli tcr DescribeRepositories \ + --RegistryId tcr-0e2hz15l \ + --NamespaceName skillhub \ + --region ap-guangzhou \ + --output json +``` + +```json +{ + "RepositoryList": [], + "TotalCount": 0, + "RequestId": "31b6c3f5-863a-4147-8414-192278df5728" +} +``` + +> 上传 Helm Chart 后,Chart 对应的仓库会出现在 `RepositoryList` 中。 + +### 步骤2:获取 Helm Chart 下载信息(控制面) + +获取指定 Chart 版本的预签名下载 URL。**四个参数全部必填**,Chart 必须已上传至 TCR。 + +当前实例尚无 Chart 上传,因此查询返回 `chart not found`: + +```bash +tccli tcr DescribeChartDownloadInfo \ + --RegistryId tcr-0e2hz15l \ + --NamespaceName skillhub \ + --ChartName nginx \ + --ChartVersion 1.0.0 \ + --region ap-guangzhou \ + --output json +``` + +```text +[TencentCloudSDKException] code:InternalError message:{"errors":[{"code":"ERROR","message":"chart skillhub/nginx:1.0.0 not found"}]} requestId:d2f0ec63-fc4a-4b0e-8084-8f6e435d2aef +``` + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `RegistryId` | String | **是** | 实例 ID | +| `NamespaceName` | String | **是** | 命名空间名称 | +| `ChartName` | String | **是** | Chart 名称 | +| `ChartVersion` | String | **是** | Chart 版本号(如 `1.0.0`) | + +> 上传 Chart 后,成功调用返回 `PreSignedDownloadURL`,配合 `curl` 或 `wget` 即可下载 Chart 包。 + +### 步骤3:下载 Helm Chart 包(控制面 API 触发) + +通过 `DownloadHelmChart` 获取 COS 临时凭证以直接下载 Chart 包内容。当前实例尚无 Chart,返回相同错误: + +```bash +tccli tcr DownloadHelmChart \ + --RegistryId tcr-0e2hz15l \ + --NamespaceName skillhub \ + --ChartName nginx \ + --ChartVersion 1.0.0 \ + --region ap-guangzhou \ + --output json +``` + +```text +[TencentCloudSDKException] code:InternalError message:{"errors":[{"code":"ERROR","message":"chart skillhub/nginx:1.0.0 not found"}]} requestId:e49e7a6e-f6ff-46a8-8b7d-b0d215855dd5 +``` + +> **注意**:`DownloadHelmChart` 返回 COS 临时凭证(`TmpSecretId`、`TmpSecretKey`、`Bucket`、`Path` 等),适用于需要编程方式获取 Chart 包的场景。日常使用建议首选 `DescribeChartDownloadInfo` + `curl`/`wget`,或直接使用下文的 Helm CLI 方式拉取。 + +### 步骤4:通过 OCI 协议推送 Helm Chart(数据面) + +以下为数据面操作,需 `helm` CLI(v3.8+)且已配置 TCR 访问凭证。**前提条件**: + +- Helm 客户端 v3.8+ 已安装(`helm version` 确认) +- 已通过 `CreateInstanceToken` 获取 TCR 长期访问凭证 + +> 本机未安装 `helm`,以下为操作示例。 + +#### 4a. OCI 登录 + +```bash +helm registry login kerwinwjyan-rewrite-001.tencentcloudcr.com \ + --username <用户名> \ + --password <实例Token> +``` + +```text +Login Succeeded +``` + +> `<用户名>` 为 TCR 实例访问凭证用户名(`CreateInstanceToken` 返回的 `Username`,通常为主账号 UIN),`<实例Token>` 为长期访问凭证 Token。 + +#### 4b. 打包 Chart + +```bash +helm package ./mychart/ +``` + +```text +Successfully packaged chart and saved it to: mychart-0.1.0.tgz +``` + +#### 4c. 推送至 TCR(OCI) + +```bash +helm push mychart-0.1.0.tgz oci://kerwinwjyan-rewrite-001.tencentcloudcr.com/skillhub +``` + +```text +Pushed: kerwinwjyan-rewrite-001.tencentcloudcr.com/skillhub/mychart:0.1.0 +Digest: sha256:abc123... +``` + +### 步骤5:通过 OCI 协议拉取 Helm Chart(数据面) + +**前提条件**:已通过 `helm registry login` 登录 TCR(见步骤4a)。 + +```bash +helm pull oci://kerwinwjyan-rewrite-001.tencentcloudcr.com/skillhub/mychart --version 0.1.0 +``` + +```text +Pulled: kerwinwjyan-rewrite-001.tencentcloudcr.com/skillhub/mychart:0.1.0 +Digest: sha256:abc123... +``` + +### 步骤6:从 TCR 直接安装 Helm Chart(数据面) + +```bash +helm install my-release oci://kerwinwjyan-rewrite-001.tencentcloudcr.com/skillhub/mychart \ + --version 0.1.0 \ + --namespace default \ + --create-namespace +``` + +```text +NAME: my-release +LAST DEPLOYED: Mon Jun 16 12:00:00 2026 +NAMESPACE: default +STATUS: deployed +REVISION: 1 +``` + +### 步骤7(备选):通过 Chart Museum API 管理 Helm Chart + +TCR 企业版为每个命名空间提供 Chart Museum 兼容端点:`https://<实例名>.tencentcloudcr.com/chartrepo/<命名空间>`。 + +#### 安装 helm-push 插件 + +```bash +helm plugin install https://github.com/chartmuseum/helm-push +``` + +```text +Installed plugin: cm-push +``` + +#### 添加 TCR Helm 仓库 + +```bash +helm repo add tcr-skillhub \ + https://kerwinwjyan-rewrite-001.tencentcloudcr.com/chartrepo/skillhub \ + --username <用户名> \ + --password <实例Token> +``` + +```text +"tcr-skillhub" has been added to your repositories +``` + +#### 推送 Helm Chart(Chart Museum) + +```bash +helm cm-push mychart tcr-skillhub +``` + +```text +remote: + mychart-0.1.0.tgz +``` + +#### 拉取 Helm Chart(Chart Museum) + +```bash +helm repo update +helm fetch tcr-skillhub/mychart --version 0.1.0 +``` + +## 验证 + +### 控制面(tccli) + +Chart 上传后,通过以下命令验证控制面操作: + +```bash +# 验证1:仓库列表中出现 Chart 对应仓库 +tccli tcr DescribeRepositories \ + --RegistryId tcr-0e2hz15l \ + --NamespaceName skillhub \ + --region ap-guangzhou \ + --output json +# 期望:RepositoryList 中包含 Chart 对应的仓库条目 + +# 验证2:获取 Chart 下载信息成功 +tccli tcr DescribeChartDownloadInfo \ + --RegistryId tcr-0e2hz15l \ + --NamespaceName skillhub \ + --ChartName mychart \ + --ChartVersion 0.1.0 \ + --region ap-guangzhou \ + --output json +# 期望:返回有效的 PreSignedDownloadURL +``` + +### 数据面(helm) + +**前提条件**:`helm` CLI 已安装(v3.8+),均已通过 `helm registry login` 登录 TCR。 + +```bash +# 验证1:OCi 方式查看 Chart 元数据 +helm show chart oci://kerwinwjyan-rewrite-001.tencentcloudcr.com/skillhub/mychart --version 0.1.0 +# 期望:输出 Chart.yaml 内容 + +# 验证2:拉取 Chart 包并校验 +helm pull oci://kerwinwjyan-rewrite-001.tencentcloudcr.com/skillhub/mychart --version 0.1.0 +ls -l mychart-0.1.0.tgz +# 期望:mychart-0.1.0.tgz 文件存在 + +# 验证3(Chart Museum):搜索仓库中的 Chart +helm search repo tcr-skillhub/mychart --versions +# 期望:列出 mychart 的各版本 +``` + +## 清理 + +### 数据面(helm) + +```bash +# 卸载已安装的 Helm release(如有) +helm uninstall my-release --namespace default + +# 移除本地仓库引用(Chart Museum 方式) +helm repo remove tcr-skillhub + +# 登出 OCI registry +helm registry logout kerwinwjyan-rewrite-001.tencentcloudcr.com +``` + +### 控制面(tccli) + +删除 Chart 对应的仓库(将同时清除其下所有 Chart 版本): + +```bash +# 当前实例尚无 Chart 上传,无需删除。 +# 如已上传 Chart,执行以下命令(谨慎!数据不可恢复): +tccli tcr DeleteRepository \ + --RegistryId tcr-0e2hz15l \ + --NamespaceName skillhub \ + --RepositoryName mychart \ + --region ap-guangzhou \ + --output json +``` + +```json +{ + "RequestId": "b5499668-30a7-4da3-b516-d2c0ae32ea60" +} +``` + +> **警告**:删除仓库将同时删除其下所有 Chart 版本及关联的容器镜像 Tag,不可恢复。 + +## 排障 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `DescribeChartDownloadInfo` 返回 `chart /: not found` | 确认 Chart 是否已推送至 TCR | Chart 尚未上传或 ChartName/ChartVersion 参数错误 | 先通过 `helm push` 上传 Chart 后再查询 | +| `helm registry login` 返回 401 | 运行 `tccli tcr DescribeInstanceToken --RegistryId ` 检查 Token 状态 | 用户名或 Token 错误/过期 | 确认用户名(主账号 UIN)和 Token 正确;若过期,`CreateInstanceToken` 重新生成 | +| `helm push`(OCI)报 401 | 同上 | 未登录或登录过期 | 先执行 `helm registry login` | +| `helm push`(OCI)报 `unsupported protocol scheme` | `helm version --short` 查看版本 | Helm 版本 < 3.8,不支持 OCI 协议 | 升级 Helm 至 v3.8+,或改用 Chart Museum 方式(见步骤7) | +| `helm cm-push` 报 `command not found` | `helm plugin list` 查看已安装插件 | 未安装 helm-push 插件 | 执行 `helm plugin install https://github.com/chartmuseum/helm-push` | +| `helm repo add` 返回 401 | 确认用户名和 Token | 凭据错误或 Token 过期 | 重新获取 Token 并重试 | +| 网络不通(超时/拒绝连接) | 确认当前网络能解析实例域名 | 当前机器 IP 不在实例公网白名单中,或未配置 VPC 内网访问 | `ManageExternalEndpoint Open` 开启公网访问并添加白名单,或配置内网访问 | +| 子账号无操作权限 | 与主账号确认 CAM 策略 | 子账号缺少 `tcr:DescribeRepositories` 等权限 | 参考 [企业版授权方案示例](https://cloud.tencent.com/document/product/1141/41417) 授权 | + +## 下一步 + +- [管理镜像仓库](../../image-creation/repository)(page_id `41811`) — 在同一命名空间管理容器镜像 +- [管理命名空间](../../image-creation/namespace)(page_id `41803`) — 管理命名空间的公开/私有属性 +- [OCI 制品管理概述](../overview)(page_id `63918`) — 了解 TCR 支持的 OCI 制品类型 +- [创建企业版实例](../../create)(page_id `51110`) — 创建新的 TCR 企业版实例 + +## 控制台替代 + +[容器镜像服务 -> Helm Chart](https://console.cloud.tencent.com/tcr/chart):选择实例,进入 Helm Chart 管理页,上传 Chart 包、查看已上传 Chart 的版本列表、查看 Chart 包内文件详情。 diff --git a/src/content/docs/cli/tcr/ops/oci-artifacts/helm-chart/helm-chart-tcrctl.md b/src/content/docs/cli/tcr/ops/oci-artifacts/helm-chart/helm-chart-tcrctl.md new file mode 100644 index 0000000..4f02f25 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/oci-artifacts/helm-chart/helm-chart-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "托管 Helm Chart(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[托管 Helm Chart](https://cloud.tencent.com/document/product/1141/41944) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/oci-artifacts/helm-chart/helm-chart.md b/src/content/docs/cli/tcr/ops/oci-artifacts/helm-chart/helm-chart.md new file mode 100644 index 0000000..2740558 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/oci-artifacts/helm-chart/helm-chart.md @@ -0,0 +1,403 @@ +--- +title: "托管 Helm Chart(tccli)" +description: "· page_id `41944`" +--- + +> 对照官方:[托管 Helm Chart](https://cloud.tencent.com/document/product/1141/41944) · page_id `41944` + +## 概述 + +TCR 企业版实例支持托管 Helm Chart,用户可在同个命名空间内同时管理容器镜像及 Helm Chart,实现在业务项目内统一管理容器镜像和 Helm Chart 两种云原生交付物。 + +Helm Chart 仓库继承其所属命名空间的公开及私有属性。在权限管理上,Helm Chart 与容器镜像共用 **repository** 资源类型(如 `qcs::tcr:$region:$account:repository/tcr-xxxxxx/project-a/*` 包含命名空间内全部镜像仓库及 Helm Chart)。 + +TCR 支持两种 Helm Chart 管理方式: + +- **OCI 制品**(Helm 3.8+,推荐):将 Helm Chart 作为 OCI 制品,通过 `helm registry login`、`helm push`、`helm pull` 操作 TCR OCI 端点 +- **Chart Museum API**(传统方式):通过 `helm repo add` 添加 TCR 提供的 Chart Museum 端点(`chartrepo`),使用 `helm cm-push` / `helm fetch` 上传下载 + +> **注意**:仅企业版实例支持托管 Helm Chart。Helm 的上传/下载操作通过 `helm` CLI(非 tccli)完成,tccli 仅用于查询 Chart 信息和下载链接。 + +## 前置条件 + +- [环境准备](../../index.md) +- 已 [购买企业版实例](../../create),实例 `Status` 为 `Running` +- 已 [创建命名空间](../../image-creation/namespace) +- 已 [获取实例访问凭证](https://cloud.tencent.com/document/product/1141/41829)(用户名及 Token) +- 本机已安装 Helm 客户端(v3.8+ 推荐,OCI 方式必须 v3.8+) +- 如使用子账号操作,详见 [企业版授权方案示例](https://cloud.tencent.com/document/product/1141/41417) + +### 环境检查 + +```bash +# 1. 确认实例存在且状态正常 +tccli tcr DescribeInstances --Registryids '["tcr-0e2hz15l"]' --region ap-guangzhou --output json +``` + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-0e2hz15l", + "RegistryName": "kerwinwjyan-rewrite-001", + "RegistryType": "basic", + "Status": "Running", + "PublicDomain": "kerwinwjyan-rewrite-001.tencentcloudcr.com", + "InternalEndpoint": "10.1.67.13", + "EnableAnonymous": true, + "TokenValidTime": 87600, + "DeletionProtection": false + } + ], + "RequestId": "bf6d6053-85c2-4f10-853b-4492ad4a98b5" +} +``` + +```bash +# 2. 确认命名空间已存在 +tccli tcr DescribeNamespaces --RegistryId tcr-0e2hz15l --region ap-guangzhou --output json +``` + +```json +{ + "NamespaceList": [ + { + "Name": "skillhub", + "CreationTime": "2026-06-16T08:37:05.263Z", + "Public": false, + "NamespaceId": 1 + } + ], + "TotalCount": 1, + "RequestId": "329610f4-308b-442f-b72e-a0a0fabed229" +} +``` + +```bash +# 3. 确认 helm 客户端版本(OCI 方式需 v3.8+) +helm version --short +``` + +```text +v3.14.0+g3333d44 +``` + +### 命名约束 + +Helm Chart 名称需符合 Helm 命名规范,版本号遵循 SemVer 2.0(如 `1.0.0`)。Chart 的 `name` 和 `version` 定义在 `Chart.yaml` 中。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | CLI | 幂等 | +|-----------|-----|------| +| 查看 Helm Chart 列表 | `tccli tcr DescribeRepositories --RegistryId --NamespaceName ` | 是 | +| 获取 Chart 版本下载信息 | `tccli tcr DescribeChartDownloadInfo --RegistryId --NamespaceName --ChartName --ChartVersion ` | 是 | +| 下载 Helm Chart(API 触发) | `tccli tcr DownloadHelmChart --RegistryId --NamespaceName --ChartName --ChartVersion ` | 是 | +| OCI 登录 | `helm registry login .tencentcloudcr.com --username --password ` | -- | +| 推送 Helm Chart(OCI) | `helm push .tgz oci://.tencentcloudcr.com/` | 是(覆盖同版本) | +| 拉取 Helm Chart(OCI) | `helm pull oci://.tencentcloudcr.com// --version ` | 是 | +| 安装 Helm Chart | `helm install oci://.tencentcloudcr.com// --version ` | 否(同名 release 冲突) | +| 添加 Helm 仓库(Chart Museum) | `helm repo add https://.tencentcloudcr.com/chartrepo/ --username --password ` | 否(重名报错) | +| 推送 Helm Chart(Chart Museum) | `helm cm-push ` | 是(覆盖同版本) | +| 拉取 Helm Chart(Chart Museum) | `helm fetch / --version ` | 是 | + +## 操作步骤 + +### 步骤1:查看 Helm Chart 仓库列表 + +列出指定命名空间内的 Helm Chart 仓库(与镜像仓库共用 `DescribeRepositories`)。 + +当前实例 `tcr-0e2hz15l` 的 `skillhub` 命名空间尚无仓库: + +```bash +tccli tcr DescribeRepositories \ + --RegistryId tcr-0e2hz15l \ + --NamespaceName skillhub \ + --region ap-guangzhou \ + --output json +``` + +```json +{ + "RepositoryList": [], + "TotalCount": 0, + "RequestId": "31b6c3f5-863a-4147-8414-192278df5728" +} +``` + +> 上传 Helm Chart 后,Chart 对应的仓库会出现在 `RepositoryList` 中。 + +### 步骤2:获取 Helm Chart 下载信息(控制面) + +获取指定 Chart 版本的预签名下载 URL。**四个参数全部必填**,Chart 必须已上传至 TCR。 + +当前实例尚无 Chart 上传,因此查询返回 `chart not found`: + +```bash +tccli tcr DescribeChartDownloadInfo \ + --RegistryId tcr-0e2hz15l \ + --NamespaceName skillhub \ + --ChartName nginx \ + --ChartVersion 1.0.0 \ + --region ap-guangzhou \ + --output json +``` + +```text +[TencentCloudSDKException] code:InternalError message:{"errors":[{"code":"ERROR","message":"chart skillhub/nginx:1.0.0 not found"}]} requestId:d2f0ec63-fc4a-4b0e-8084-8f6e435d2aef +``` + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `RegistryId` | String | **是** | 实例 ID | +| `NamespaceName` | String | **是** | 命名空间名称 | +| `ChartName` | String | **是** | Chart 名称 | +| `ChartVersion` | String | **是** | Chart 版本号(如 `1.0.0`) | + +> 上传 Chart 后,成功调用返回 `PreSignedDownloadURL`,配合 `curl` 或 `wget` 即可下载 Chart 包。 + +### 步骤3:下载 Helm Chart 包(控制面 API 触发) + +通过 `DownloadHelmChart` 获取 COS 临时凭证以直接下载 Chart 包内容。当前实例尚无 Chart,返回相同错误: + +```bash +tccli tcr DownloadHelmChart \ + --RegistryId tcr-0e2hz15l \ + --NamespaceName skillhub \ + --ChartName nginx \ + --ChartVersion 1.0.0 \ + --region ap-guangzhou \ + --output json +``` + +```text +[TencentCloudSDKException] code:InternalError message:{"errors":[{"code":"ERROR","message":"chart skillhub/nginx:1.0.0 not found"}]} requestId:e49e7a6e-f6ff-46a8-8b7d-b0d215855dd5 +``` + +> **注意**:`DownloadHelmChart` 返回 COS 临时凭证(`TmpSecretId`、`TmpSecretKey`、`Bucket`、`Path` 等),适用于需要编程方式获取 Chart 包的场景。日常使用建议首选 `DescribeChartDownloadInfo` + `curl`/`wget`,或直接使用下文的 Helm CLI 方式拉取。 + +### 步骤4:通过 OCI 协议推送 Helm Chart(数据面) + +以下为数据面操作,需 `helm` CLI(v3.8+)且已配置 TCR 访问凭证。**前提条件**: + +- Helm 客户端 v3.8+ 已安装(`helm version` 确认) +- 已通过 `CreateInstanceToken` 获取 TCR 长期访问凭证 + +> 本机未安装 `helm`,以下为操作示例。 + +#### 4a. OCI 登录 + +```bash +helm registry login kerwinwjyan-rewrite-001.tencentcloudcr.com \ + --username <用户名> \ + --password <实例Token> +``` + +```text +Login Succeeded +``` + +> `<用户名>` 为 TCR 实例访问凭证用户名(`CreateInstanceToken` 返回的 `Username`,通常为主账号 UIN),`<实例Token>` 为长期访问凭证 Token。 + +#### 4b. 打包 Chart + +```bash +helm package ./mychart/ +``` + +```text +Successfully packaged chart and saved it to: mychart-0.1.0.tgz +``` + +#### 4c. 推送至 TCR(OCI) + +```bash +helm push mychart-0.1.0.tgz oci://kerwinwjyan-rewrite-001.tencentcloudcr.com/skillhub +``` + +```text +Pushed: kerwinwjyan-rewrite-001.tencentcloudcr.com/skillhub/mychart:0.1.0 +Digest: sha256:abc123... +``` + +### 步骤5:通过 OCI 协议拉取 Helm Chart(数据面) + +**前提条件**:已通过 `helm registry login` 登录 TCR(见步骤4a)。 + +```bash +helm pull oci://kerwinwjyan-rewrite-001.tencentcloudcr.com/skillhub/mychart --version 0.1.0 +``` + +```text +Pulled: kerwinwjyan-rewrite-001.tencentcloudcr.com/skillhub/mychart:0.1.0 +Digest: sha256:abc123... +``` + +### 步骤6:从 TCR 直接安装 Helm Chart(数据面) + +```bash +helm install my-release oci://kerwinwjyan-rewrite-001.tencentcloudcr.com/skillhub/mychart \ + --version 0.1.0 \ + --namespace default \ + --create-namespace +``` + +```text +NAME: my-release +LAST DEPLOYED: Mon Jun 16 12:00:00 2026 +NAMESPACE: default +STATUS: deployed +REVISION: 1 +``` + +### 步骤7(备选):通过 Chart Museum API 管理 Helm Chart + +TCR 企业版为每个命名空间提供 Chart Museum 兼容端点:`https://<实例名>.tencentcloudcr.com/chartrepo/<命名空间>`。 + +#### 安装 helm-push 插件 + +```bash +helm plugin install https://github.com/chartmuseum/helm-push +``` + +```text +Installed plugin: cm-push +``` + +#### 添加 TCR Helm 仓库 + +```bash +helm repo add tcr-skillhub \ + https://kerwinwjyan-rewrite-001.tencentcloudcr.com/chartrepo/skillhub \ + --username <用户名> \ + --password <实例Token> +``` + +```text +"tcr-skillhub" has been added to your repositories +``` + +#### 推送 Helm Chart(Chart Museum) + +```bash +helm cm-push mychart tcr-skillhub +``` + +```text +remote: + mychart-0.1.0.tgz +``` + +#### 拉取 Helm Chart(Chart Museum) + +```bash +helm repo update +helm fetch tcr-skillhub/mychart --version 0.1.0 +``` + +## 验证 + +### 控制面(tccli) + +Chart 上传后,通过以下命令验证控制面操作: + +```bash +# 验证1:仓库列表中出现 Chart 对应仓库 +tccli tcr DescribeRepositories \ + --RegistryId tcr-0e2hz15l \ + --NamespaceName skillhub \ + --region ap-guangzhou \ + --output json +# 期望:RepositoryList 中包含 Chart 对应的仓库条目 + +# 验证2:获取 Chart 下载信息成功 +tccli tcr DescribeChartDownloadInfo \ + --RegistryId tcr-0e2hz15l \ + --NamespaceName skillhub \ + --ChartName mychart \ + --ChartVersion 0.1.0 \ + --region ap-guangzhou \ + --output json +# 期望:返回有效的 PreSignedDownloadURL +``` + +### 数据面(helm) + +**前提条件**:`helm` CLI 已安装(v3.8+),均已通过 `helm registry login` 登录 TCR。 + +```bash +# 验证1:OCi 方式查看 Chart 元数据 +helm show chart oci://kerwinwjyan-rewrite-001.tencentcloudcr.com/skillhub/mychart --version 0.1.0 +# 期望:输出 Chart.yaml 内容 + +# 验证2:拉取 Chart 包并校验 +helm pull oci://kerwinwjyan-rewrite-001.tencentcloudcr.com/skillhub/mychart --version 0.1.0 +ls -l mychart-0.1.0.tgz +# 期望:mychart-0.1.0.tgz 文件存在 + +# 验证3(Chart Museum):搜索仓库中的 Chart +helm search repo tcr-skillhub/mychart --versions +# 期望:列出 mychart 的各版本 +``` + +## 清理 + +### 数据面(helm) + +```bash +# 卸载已安装的 Helm release(如有) +helm uninstall my-release --namespace default + +# 移除本地仓库引用(Chart Museum 方式) +helm repo remove tcr-skillhub + +# 登出 OCI registry +helm registry logout kerwinwjyan-rewrite-001.tencentcloudcr.com +``` + +### 控制面(tccli) + +删除 Chart 对应的仓库(将同时清除其下所有 Chart 版本): + +```bash +# 当前实例尚无 Chart 上传,无需删除。 +# 如已上传 Chart,执行以下命令(谨慎!数据不可恢复): +tccli tcr DeleteRepository \ + --RegistryId tcr-0e2hz15l \ + --NamespaceName skillhub \ + --RepositoryName mychart \ + --region ap-guangzhou \ + --output json +``` + +```json +{ + "RequestId": "b5499668-30a7-4da3-b516-d2c0ae32ea60" +} +``` + +> **警告**:删除仓库将同时删除其下所有 Chart 版本及关联的容器镜像 Tag,不可恢复。 + +## 排障 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `DescribeChartDownloadInfo` 返回 `chart /: not found` | 确认 Chart 是否已推送至 TCR | Chart 尚未上传或 ChartName/ChartVersion 参数错误 | 先通过 `helm push` 上传 Chart 后再查询 | +| `helm registry login` 返回 401 | 运行 `tccli tcr DescribeInstanceToken --RegistryId ` 检查 Token 状态 | 用户名或 Token 错误/过期 | 确认用户名(主账号 UIN)和 Token 正确;若过期,`CreateInstanceToken` 重新生成 | +| `helm push`(OCI)报 401 | 同上 | 未登录或登录过期 | 先执行 `helm registry login` | +| `helm push`(OCI)报 `unsupported protocol scheme` | `helm version --short` 查看版本 | Helm 版本 < 3.8,不支持 OCI 协议 | 升级 Helm 至 v3.8+,或改用 Chart Museum 方式(见步骤7) | +| `helm cm-push` 报 `command not found` | `helm plugin list` 查看已安装插件 | 未安装 helm-push 插件 | 执行 `helm plugin install https://github.com/chartmuseum/helm-push` | +| `helm repo add` 返回 401 | 确认用户名和 Token | 凭据错误或 Token 过期 | 重新获取 Token 并重试 | +| 网络不通(超时/拒绝连接) | 确认当前网络能解析实例域名 | 当前机器 IP 不在实例公网白名单中,或未配置 VPC 内网访问 | `ManageExternalEndpoint Open` 开启公网访问并添加白名单,或配置内网访问 | +| 子账号无操作权限 | 与主账号确认 CAM 策略 | 子账号缺少 `tcr:DescribeRepositories` 等权限 | 参考 [企业版授权方案示例](https://cloud.tencent.com/document/product/1141/41417) 授权 | + +## 下一步 + +- [管理镜像仓库](../../image-creation/repository)(page_id `41811`) — 在同一命名空间管理容器镜像 +- [管理命名空间](../../image-creation/namespace)(page_id `41803`) — 管理命名空间的公开/私有属性 +- [OCI 制品管理概述](../overview)(page_id `63918`) — 了解 TCR 支持的 OCI 制品类型 +- [创建企业版实例](../../create)(page_id `51110`) — 创建新的 TCR 企业版实例 + +## 控制台替代 + +[容器镜像服务 -> Helm Chart](https://console.cloud.tencent.com/tcr/chart):选择实例,进入 Helm Chart 管理页,上传 Chart 包、查看已上传 Chart 的版本列表、查看 Chart 包内文件详情。 diff --git a/src/content/docs/cli/tcr/ops/oci-artifacts/overview.md b/src/content/docs/cli/tcr/ops/oci-artifacts/overview.md new file mode 100644 index 0000000..ce7976d --- /dev/null +++ b/src/content/docs/cli/tcr/ops/oci-artifacts/overview.md @@ -0,0 +1,169 @@ +--- +title: "OCI 制品管理概述(tccli)" +description: "· page_id `63918`" +--- + +> 对照官方:[OCI 制品管理概述](https://cloud.tencent.com/document/product/1141/63918) · page_id `63918` + +## 概述 + +腾讯云容器镜像服务(TCR)企业版和个人版均遵循 [OCI(Open Container Initiative)](https://opencontainers.org/) 标准,支持托管和管理多种云原生制品类型,不仅限于 Docker 容器镜像。通过统一的 OCI 分发协议,TCR 为容器镜像、Helm Chart、CNAB 及自定义 OCI Artifacts 提供一致的推送、存储、分发和生命周期管理体验。 + +--- + +### 制品类型 + +TCR 支持以下 OCI 兼容制品类型: + +| 制品类型 | 媒体类型(OCI `mediaType`) | 管理工具 | TCR 角色 | +|---------|--------------------------|---------|---------| +| **容器镜像** | `application/vnd.oci.image.manifest.v1+json` | `docker` / `podman` / `nerdctl` | 存储与分发(镜像仓库) | +| **Helm Chart**(OCI 模式) | `application/vnd.cncf.helm.chart.config.v1+json` | `helm` v3+ | 存储与分发(镜像仓库内托管) | +| **Helm Chart**(ChartMuseum 模式) | — | `helm` v2/v3 | 托管(企业版内置 ChartMuseum 服务) | +| **加速镜像**(按需加载) | OCI 镜像变体,`-apparate` 后缀 | `docker` / `nerdctl` | 格式转换 + 分发(`CreateImageAccelerateTask`) | +| **CNAB** 及自定义 OCI Artifacts | 自定义 `application/vnd.*` 类型 | 对应 OCI 兼容客户端 | 存储与分发(镜像仓库内托管) | + +--- + +### 容器镜像 + +容器镜像是 TCR 的核心制品类型,遵循 OCI Image Spec(`v1.0+`)。所有已创建的镜像仓库默认支持容器镜像的推送和拉取。 + +**管理方式**: +- `docker` / `podman` / `nerdctl` 等 OCI 兼容客户端负责镜像的推送(`push`)和拉取(`pull`) +- `tccli tcr` 负责仓库维度的管理:创建/删除仓库(`CreateRepository` / `DeleteRepository`)、查询镜像列表(`DescribeImages`)、查询镜像 Manifest(`DescribeImageManifests`)、管理版本生命周期(保留策略/不可变规则) + +--- + +### Helm Chart + +TCR 提供两种 Helm Chart 托管模式,适用于不同阶段的使用需求: + +#### 模式一:OCI 模式(推荐) + +将 Helm Chart 作为 OCI 制品推送至镜像仓库,实现与容器镜像统一的存储和分发管理。 + +- **前提**:Helm v3+(v3.8.0+ 内置 OCI 支持更完善) +- **工作流**: + ```bash + helm registry login .tencentcloudcr.com + helm package + helm push .tgz oci://.tencentcloudcr.com/ + helm pull oci://.tencentcloudcr.com// --version + ``` +- **优势**:与容器镜像共享仓库、权限模型和分发链路;无需额外运维 ChartMuseum 实例 + +#### 模式二:ChartMuseum 模式 + +企业版实例内置基于 [ChartMuseum](https://chartmuseum.com/) 开源项目的 Helm Chart 托管服务,提供独立的 Chart 仓库 URL 和 Web 管理界面。 + +- **前提**:企业版实例(basic 及以上 tier) +- **管理方式**:通过控制台或 TCR API 上传/管理 Chart,独立的 Chart 仓库地址格式 `.tencentcloudcr.com/chartrepo/` +- **适用场景**:使用 Helm v2 的存量环境、习惯传统 Chart 仓库管理方式的团队 + +--- + +### 加速镜像(按需加载) + +TCR 企业版(standard 及以上)支持将标准容器镜像转换为按需加载(lazy-pull)的加速格式镜像。加速镜像在传统 OCI 镜像基础上增加了文件系统元数据索引,使容器运行时(如 Nydus)无需等待完整的镜像拉取即可启动容器。 + +- **CLI 入口**:`CreateImageAccelerateTask` 创建加速转换任务 +- **生成制品**:加速镜像仓库名格式为 `<原仓库名>-apparate`,自动生成 +- **加速引擎**:Nydus(需容器运行时支持) +- **适用场景**:大镜像(GB 级)的秒级启动、AI/ML 训练任务、Serverless 容器 +- **依赖**:实例 tier >= `standard`,需预先推送原始镜像(加速任务仅对已有镜像执行格式转换) + +--- + +### CNAB 及自定义 OCI Artifacts + +对于遵循 OCI Artifact Spec(`opencontainers/artifacts`)的自定义制品,TCR 镜像仓库可充当通用 OCI 制品注册中心: + +- **CNAB**(Cloud Native Application Bundle):多云应用打包格式 +- **签名/证明文件**:如 Cosign 签名(`application/vnd.dev.cosign.simplesigning.v1+json`)、SBOM 证明 +- **自定义制品**:任何遵循 OCI 分发规范的制品类型 + +推送自定义 OCI 制品需使用支持该制品类型的客户端工具(如 `oras` CLI),TCR 镜像仓库作为 OCI 分发端点负责存储和分发。自定义制品的 `mediaType` 与容器镜像不同,在控制台和 `DescribeImages` 返回结果中会区分显示。 + +--- + +### 企业版 vs 个人版支持范围 + +| 制品类型 | 企业版 | 个人版 | +|---------|-------|-------| +| 容器镜像 | 支持 | 支持 | +| Helm Chart(OCI 模式) | 支持 | 支持(镜像仓库内托管) | +| Helm Chart(ChartMuseum) | 支持 | 不支持 | +| 加速镜像(按需加载) | 支持(standard+) | 不支持 | +| CNAB / 自定义 OCI Artifacts | 支持(镜像仓库内) | 支持(镜像仓库内) | + +## 前置条件 + +- [环境准备](../../index.md):`tccli` 版本 >= 3.1.x,地域与凭据已配置 +- 已成功 [购买企业版实例](../../create)(企业版 `basic`/`standard`/`premium`),实例状态为 `Running` +- **容器镜像**:安装 `docker`/`podman`/`nerdctl` 客户端(非 tccli 依赖) +- **Helm Chart(OCI 模式)**:安装 `helm` v3.8.0+ +- **加速镜像**:实例 tier >= `standard`,容器运行时需支持 Nydus +- 如使用子账号操作,需主账号授予 `tcr:*` 相关权限 + +## 控制台与 CLI 参数映射 + +本文为概念概述页。以下列出各制品类型的 CLI 命令入口,具体参数和操作步骤见对应子页面。 + +| 制品类型 | 核心 tccli 命令 | 对应客户端命令 | 页面 | +|---------|---------------|--------------|------| +| 容器镜像 — 仓库管理 | `CreateRepository` / `DeleteRepository` | — | [管理镜像仓库](../../image-creation/repository) | +| 容器镜像 — 版本管理 | `DescribeImages` / `DescribeImageManifests` | `docker push`/`pull` | 同上 + [自动删除镜像版本](../../image-cleanup/auto-delete) | +| 容器镜像 — 版本不可变 | `CreateImmutableTagRules` | — | [镜像版本不可变](../../image-security/immutable-tags) | +| Helm Chart — OCI 模式 | `DescribeImages`(Chart 以 OCI 制品形式出现) | `helm push`/`pull oci://` | [托管 Helm Chart](../helm-chart) | +| Helm Chart — ChartMuseum | 企业版内置 ChartMuseum API | `helm repo add`/`push`(传统模式) | 同上 | +| 加速镜像 | `CreateImageAccelerateTask` | `docker`/`nerdctl` 配合 Nydus | [按需加载容器镜像](../../image-distribution/accelerated-image) | + +## 操作步骤 + +本文为概念概述页,无可独立执行的 tccli 命令操作。具体操作步骤请进入以下子页面: + +- [管理镜像仓库](../../image-creation/repository) — 创建、查看、删除镜像仓库(page_id `41811`) +- [托管 Helm Chart](../helm-chart) — 推送与管理 Helm Chart 制品(page_id `41944`) +- [按需加载容器镜像](../../image-distribution/accelerated-image) — 将镜像转换为 OCI 加速格式(page_id `53928`) + +## 验证 + +### 概念关系验证(交叉引用) + +| 验证关系 | 验证方式 | 期望结论 | +|----------|---------|---------| +| 镜像仓库默认托管 OCI 制品 | 创建一个镜像仓库,分别推送容器镜像和 Helm Chart(OCI 模式)至同一仓库 | 两种制品在仓库内共存,`DescribeImages` 返回的 `ImageInfoList` 中通过 `Kind`/`mediaType` 字段区分 | +| OCI Helm Chart 与容器镜像共享权限模型 | 创建服务级账号授予对命名空间 `ns-a` 的 `pull` 权限,用该账号 `helm pull oci://` 拉取 `ns-a` 下的 Chart | 拉取成功(OCI Chart 复用镜像仓库的访问控制) | +| 加速镜像与原镜像的命名关联 | 对仓库 `ns/repo` 提交加速任务,完成后查看仓库列表 | 自动生成仓库 `ns/repo-apparate`(`-apparate` 后缀) | +| 企业版 vs 个人版 Helm Chart 模式差异 | 在企业版实例使用 ChartMuseum 模式 (`chartrepo`),在个人版尝试同样路径 | 企业版支持 ChartMuseum URL 模式,个人版仅支持 OCI 模式 | + +## 清理 + +不适用。本文为概念概述页,不涉及资源创建与清理。 + +## 排障 + +| 现象 | 诊断命令/步骤 | 根因 | 修复 | +|------|-------------|------|------| +| `helm push oci://` 报 `unauthorized` | `helm registry login .tencentcloudcr.com` 确认登录状态 | OCI 模式下 Helm Chart 的认证复用 `docker login` 凭证 | 先 `docker login` 或 `helm registry login` 到目标 Registry | +| `docker push` 成功但 `DescribeImages` 返回空/无记录 | `tccli tcr DescribeImages --RegistryId --NamespaceName --RepositoryName --region ` | 可能 `NamespaceName` 或 `RepositoryName` 参数拼写错误,或镜像尚未索引完成 | 确认参数值与 `docker push` 的命名空间/仓库名称一致;等待几秒后重试 | +| `DescribeImageManifests` 返回 `tag latest not found` | `docker images ` 在本地确认 tag 存在 | 镜像尚未推送到仓库,或 tag 拼写错误 | 确认已推送正确的 tag 至仓库 | +| `CreateImageAccelerateTask` 执行失败 | `tccli tcr DescribeInstances --Registryids '[""]' --region ` 检查 `RegistryType` | 实例 tier 低于 `standard`(basic 不支持加速) | 升级实例 tier 至 `standard` 或更高 | +| OCI Helm Chart 推送成功,控制台"Helm Chart"页面看不到 | — | 控制台 Helm Chart 页面仅展示 ChartMuseum 模式的 Chart,OCI 模式的 Chart 在镜像仓库列表中显示 | 进入对应镜像仓库页面查看 OCI Chart 制品 | +| `DescribeImages` 中看到的制品 `Kind` 不是期望的类型 | 检查制品 `mediaType` | 自定义 OCI 制品的媒体类型决定了 `Kind` 字段的显示值 | 确认客户端工具使用了正确的 `mediaType` | + +## 下一步 + +- [托管 Helm Chart](../helm-chart) — 推送与管理 Helm Chart 制品(OCI 模式 + ChartMuseum 模式)(page_id `41944`) +- [管理镜像仓库](../../image-creation/repository) — 创建与管理容器镜像仓库(page_id `41811`) +- [按需加载容器镜像](../../image-distribution/accelerated-image) — 将镜像转换为 OCI 加速格式(page_id `53928`) +- [容器镜像安全扫描](../../image-security/vulnerability-scan) — 扫描镜像漏洞与恶意文件 +- [自动删除镜像版本](../../image-cleanup/auto-delete) — 配置版本保留策略 + +## 控制台替代 + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 选择目标企业版实例 → 进入 **镜像仓库** 页面: +- **制品列表**:点击仓库名称进入详情,查看该仓库下的所有 OCI 制品(镜像、Chart 等),按 `mediaType` 区分类型 +- **Helm Chart**(ChartMuseum 模式):进入 **Helm Chart** 页面(独立于镜像仓库页面),上传/管理 Chart +- **加速镜像**:在镜像仓库详情中创建加速任务,或进入 **镜像加速** 页面统一管理 diff --git a/src/content/docs/cli/tcr/ops/oci-artifacts/overview/overview.md b/src/content/docs/cli/tcr/ops/oci-artifacts/overview/overview.md new file mode 100644 index 0000000..ce7976d --- /dev/null +++ b/src/content/docs/cli/tcr/ops/oci-artifacts/overview/overview.md @@ -0,0 +1,169 @@ +--- +title: "OCI 制品管理概述(tccli)" +description: "· page_id `63918`" +--- + +> 对照官方:[OCI 制品管理概述](https://cloud.tencent.com/document/product/1141/63918) · page_id `63918` + +## 概述 + +腾讯云容器镜像服务(TCR)企业版和个人版均遵循 [OCI(Open Container Initiative)](https://opencontainers.org/) 标准,支持托管和管理多种云原生制品类型,不仅限于 Docker 容器镜像。通过统一的 OCI 分发协议,TCR 为容器镜像、Helm Chart、CNAB 及自定义 OCI Artifacts 提供一致的推送、存储、分发和生命周期管理体验。 + +--- + +### 制品类型 + +TCR 支持以下 OCI 兼容制品类型: + +| 制品类型 | 媒体类型(OCI `mediaType`) | 管理工具 | TCR 角色 | +|---------|--------------------------|---------|---------| +| **容器镜像** | `application/vnd.oci.image.manifest.v1+json` | `docker` / `podman` / `nerdctl` | 存储与分发(镜像仓库) | +| **Helm Chart**(OCI 模式) | `application/vnd.cncf.helm.chart.config.v1+json` | `helm` v3+ | 存储与分发(镜像仓库内托管) | +| **Helm Chart**(ChartMuseum 模式) | — | `helm` v2/v3 | 托管(企业版内置 ChartMuseum 服务) | +| **加速镜像**(按需加载) | OCI 镜像变体,`-apparate` 后缀 | `docker` / `nerdctl` | 格式转换 + 分发(`CreateImageAccelerateTask`) | +| **CNAB** 及自定义 OCI Artifacts | 自定义 `application/vnd.*` 类型 | 对应 OCI 兼容客户端 | 存储与分发(镜像仓库内托管) | + +--- + +### 容器镜像 + +容器镜像是 TCR 的核心制品类型,遵循 OCI Image Spec(`v1.0+`)。所有已创建的镜像仓库默认支持容器镜像的推送和拉取。 + +**管理方式**: +- `docker` / `podman` / `nerdctl` 等 OCI 兼容客户端负责镜像的推送(`push`)和拉取(`pull`) +- `tccli tcr` 负责仓库维度的管理:创建/删除仓库(`CreateRepository` / `DeleteRepository`)、查询镜像列表(`DescribeImages`)、查询镜像 Manifest(`DescribeImageManifests`)、管理版本生命周期(保留策略/不可变规则) + +--- + +### Helm Chart + +TCR 提供两种 Helm Chart 托管模式,适用于不同阶段的使用需求: + +#### 模式一:OCI 模式(推荐) + +将 Helm Chart 作为 OCI 制品推送至镜像仓库,实现与容器镜像统一的存储和分发管理。 + +- **前提**:Helm v3+(v3.8.0+ 内置 OCI 支持更完善) +- **工作流**: + ```bash + helm registry login .tencentcloudcr.com + helm package + helm push .tgz oci://.tencentcloudcr.com/ + helm pull oci://.tencentcloudcr.com// --version + ``` +- **优势**:与容器镜像共享仓库、权限模型和分发链路;无需额外运维 ChartMuseum 实例 + +#### 模式二:ChartMuseum 模式 + +企业版实例内置基于 [ChartMuseum](https://chartmuseum.com/) 开源项目的 Helm Chart 托管服务,提供独立的 Chart 仓库 URL 和 Web 管理界面。 + +- **前提**:企业版实例(basic 及以上 tier) +- **管理方式**:通过控制台或 TCR API 上传/管理 Chart,独立的 Chart 仓库地址格式 `.tencentcloudcr.com/chartrepo/` +- **适用场景**:使用 Helm v2 的存量环境、习惯传统 Chart 仓库管理方式的团队 + +--- + +### 加速镜像(按需加载) + +TCR 企业版(standard 及以上)支持将标准容器镜像转换为按需加载(lazy-pull)的加速格式镜像。加速镜像在传统 OCI 镜像基础上增加了文件系统元数据索引,使容器运行时(如 Nydus)无需等待完整的镜像拉取即可启动容器。 + +- **CLI 入口**:`CreateImageAccelerateTask` 创建加速转换任务 +- **生成制品**:加速镜像仓库名格式为 `<原仓库名>-apparate`,自动生成 +- **加速引擎**:Nydus(需容器运行时支持) +- **适用场景**:大镜像(GB 级)的秒级启动、AI/ML 训练任务、Serverless 容器 +- **依赖**:实例 tier >= `standard`,需预先推送原始镜像(加速任务仅对已有镜像执行格式转换) + +--- + +### CNAB 及自定义 OCI Artifacts + +对于遵循 OCI Artifact Spec(`opencontainers/artifacts`)的自定义制品,TCR 镜像仓库可充当通用 OCI 制品注册中心: + +- **CNAB**(Cloud Native Application Bundle):多云应用打包格式 +- **签名/证明文件**:如 Cosign 签名(`application/vnd.dev.cosign.simplesigning.v1+json`)、SBOM 证明 +- **自定义制品**:任何遵循 OCI 分发规范的制品类型 + +推送自定义 OCI 制品需使用支持该制品类型的客户端工具(如 `oras` CLI),TCR 镜像仓库作为 OCI 分发端点负责存储和分发。自定义制品的 `mediaType` 与容器镜像不同,在控制台和 `DescribeImages` 返回结果中会区分显示。 + +--- + +### 企业版 vs 个人版支持范围 + +| 制品类型 | 企业版 | 个人版 | +|---------|-------|-------| +| 容器镜像 | 支持 | 支持 | +| Helm Chart(OCI 模式) | 支持 | 支持(镜像仓库内托管) | +| Helm Chart(ChartMuseum) | 支持 | 不支持 | +| 加速镜像(按需加载) | 支持(standard+) | 不支持 | +| CNAB / 自定义 OCI Artifacts | 支持(镜像仓库内) | 支持(镜像仓库内) | + +## 前置条件 + +- [环境准备](../../index.md):`tccli` 版本 >= 3.1.x,地域与凭据已配置 +- 已成功 [购买企业版实例](../../create)(企业版 `basic`/`standard`/`premium`),实例状态为 `Running` +- **容器镜像**:安装 `docker`/`podman`/`nerdctl` 客户端(非 tccli 依赖) +- **Helm Chart(OCI 模式)**:安装 `helm` v3.8.0+ +- **加速镜像**:实例 tier >= `standard`,容器运行时需支持 Nydus +- 如使用子账号操作,需主账号授予 `tcr:*` 相关权限 + +## 控制台与 CLI 参数映射 + +本文为概念概述页。以下列出各制品类型的 CLI 命令入口,具体参数和操作步骤见对应子页面。 + +| 制品类型 | 核心 tccli 命令 | 对应客户端命令 | 页面 | +|---------|---------------|--------------|------| +| 容器镜像 — 仓库管理 | `CreateRepository` / `DeleteRepository` | — | [管理镜像仓库](../../image-creation/repository) | +| 容器镜像 — 版本管理 | `DescribeImages` / `DescribeImageManifests` | `docker push`/`pull` | 同上 + [自动删除镜像版本](../../image-cleanup/auto-delete) | +| 容器镜像 — 版本不可变 | `CreateImmutableTagRules` | — | [镜像版本不可变](../../image-security/immutable-tags) | +| Helm Chart — OCI 模式 | `DescribeImages`(Chart 以 OCI 制品形式出现) | `helm push`/`pull oci://` | [托管 Helm Chart](../helm-chart) | +| Helm Chart — ChartMuseum | 企业版内置 ChartMuseum API | `helm repo add`/`push`(传统模式) | 同上 | +| 加速镜像 | `CreateImageAccelerateTask` | `docker`/`nerdctl` 配合 Nydus | [按需加载容器镜像](../../image-distribution/accelerated-image) | + +## 操作步骤 + +本文为概念概述页,无可独立执行的 tccli 命令操作。具体操作步骤请进入以下子页面: + +- [管理镜像仓库](../../image-creation/repository) — 创建、查看、删除镜像仓库(page_id `41811`) +- [托管 Helm Chart](../helm-chart) — 推送与管理 Helm Chart 制品(page_id `41944`) +- [按需加载容器镜像](../../image-distribution/accelerated-image) — 将镜像转换为 OCI 加速格式(page_id `53928`) + +## 验证 + +### 概念关系验证(交叉引用) + +| 验证关系 | 验证方式 | 期望结论 | +|----------|---------|---------| +| 镜像仓库默认托管 OCI 制品 | 创建一个镜像仓库,分别推送容器镜像和 Helm Chart(OCI 模式)至同一仓库 | 两种制品在仓库内共存,`DescribeImages` 返回的 `ImageInfoList` 中通过 `Kind`/`mediaType` 字段区分 | +| OCI Helm Chart 与容器镜像共享权限模型 | 创建服务级账号授予对命名空间 `ns-a` 的 `pull` 权限,用该账号 `helm pull oci://` 拉取 `ns-a` 下的 Chart | 拉取成功(OCI Chart 复用镜像仓库的访问控制) | +| 加速镜像与原镜像的命名关联 | 对仓库 `ns/repo` 提交加速任务,完成后查看仓库列表 | 自动生成仓库 `ns/repo-apparate`(`-apparate` 后缀) | +| 企业版 vs 个人版 Helm Chart 模式差异 | 在企业版实例使用 ChartMuseum 模式 (`chartrepo`),在个人版尝试同样路径 | 企业版支持 ChartMuseum URL 模式,个人版仅支持 OCI 模式 | + +## 清理 + +不适用。本文为概念概述页,不涉及资源创建与清理。 + +## 排障 + +| 现象 | 诊断命令/步骤 | 根因 | 修复 | +|------|-------------|------|------| +| `helm push oci://` 报 `unauthorized` | `helm registry login .tencentcloudcr.com` 确认登录状态 | OCI 模式下 Helm Chart 的认证复用 `docker login` 凭证 | 先 `docker login` 或 `helm registry login` 到目标 Registry | +| `docker push` 成功但 `DescribeImages` 返回空/无记录 | `tccli tcr DescribeImages --RegistryId --NamespaceName --RepositoryName --region ` | 可能 `NamespaceName` 或 `RepositoryName` 参数拼写错误,或镜像尚未索引完成 | 确认参数值与 `docker push` 的命名空间/仓库名称一致;等待几秒后重试 | +| `DescribeImageManifests` 返回 `tag latest not found` | `docker images ` 在本地确认 tag 存在 | 镜像尚未推送到仓库,或 tag 拼写错误 | 确认已推送正确的 tag 至仓库 | +| `CreateImageAccelerateTask` 执行失败 | `tccli tcr DescribeInstances --Registryids '[""]' --region ` 检查 `RegistryType` | 实例 tier 低于 `standard`(basic 不支持加速) | 升级实例 tier 至 `standard` 或更高 | +| OCI Helm Chart 推送成功,控制台"Helm Chart"页面看不到 | — | 控制台 Helm Chart 页面仅展示 ChartMuseum 模式的 Chart,OCI 模式的 Chart 在镜像仓库列表中显示 | 进入对应镜像仓库页面查看 OCI Chart 制品 | +| `DescribeImages` 中看到的制品 `Kind` 不是期望的类型 | 检查制品 `mediaType` | 自定义 OCI 制品的媒体类型决定了 `Kind` 字段的显示值 | 确认客户端工具使用了正确的 `mediaType` | + +## 下一步 + +- [托管 Helm Chart](../helm-chart) — 推送与管理 Helm Chart 制品(OCI 模式 + ChartMuseum 模式)(page_id `41944`) +- [管理镜像仓库](../../image-creation/repository) — 创建与管理容器镜像仓库(page_id `41811`) +- [按需加载容器镜像](../../image-distribution/accelerated-image) — 将镜像转换为 OCI 加速格式(page_id `53928`) +- [容器镜像安全扫描](../../image-security/vulnerability-scan) — 扫描镜像漏洞与恶意文件 +- [自动删除镜像版本](../../image-cleanup/auto-delete) — 配置版本保留策略 + +## 控制台替代 + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 选择目标企业版实例 → 进入 **镜像仓库** 页面: +- **制品列表**:点击仓库名称进入详情,查看该仓库下的所有 OCI 制品(镜像、Chart 等),按 `mediaType` 区分类型 +- **Helm Chart**(ChartMuseum 模式):进入 **Helm Chart** 页面(独立于镜像仓库页面),上传/管理 Chart +- **加速镜像**:在镜像仓库详情中创建加速任务,或进入 **镜像加速** 页面统一管理 diff --git a/src/content/docs/cli/tcr/ops/personal-edition/api-migration-tcrctl.md b/src/content/docs/cli/tcr/ops/personal-edition/api-migration-tcrctl.md new file mode 100644 index 0000000..d6a8952 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/personal-edition/api-migration-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "个人版资源级 API 接口及授权方案变更指南(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[个人版资源级 API 接口及授权方案变更指南](https://cloud.tencent.com/document/product/1141/41412) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/personal-edition/api-migration.md b/src/content/docs/cli/tcr/ops/personal-edition/api-migration.md new file mode 100644 index 0000000..2c42c66 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/personal-edition/api-migration.md @@ -0,0 +1,332 @@ +--- +title: "个人版资源级 API 接口及授权方案变更指南" +description: "· page_id `41412` · 概念页" +--- + +> 对照官方:[个人版资源级 API 接口及授权方案变更指南](https://cloud.tencent.com/document/product/1141/41412) · page_id `41412` · 概念页 + +## 概述 + +容器镜像服务(TCR)个人版原为容器服务(TKE)内的镜像仓库(CCR),API 接口已从 2.0 版本升级至 3.0 版本。升级后: + +- **产品前缀** 从 `ccr` 变更为 `tcr` +- **Action 名称** 从旧版 API 名变更为带 `Personal` 后缀的 3.0 API 名 +- **Resource 描述** 从 `qcs::ccr:::` 变更为 `qcs::tcr:::` +- **资源类型** 企业版新增 `instance` 和 `repository`,个人版沿用 `repo` + +> **兼容性说明:** 升级期间 CAM 同时兼容新旧 Resource 和 Action,已有策略仍生效,但建议迁至新版以确保长期可用。 + +## 前置条件 + +### 概念准备:新旧版本差异总览 + +| 维度 | 旧版(CCR 2.0) | 新版(TCR 3.0) | +|------|----------------|----------------| +| **产品名** | CCR(Container Registry) | TCR(Tencent Container Registry) | +| **所属服务** | TKE 容器服务内嵌 | 独立容器镜像服务 | +| **CAM 前缀** | `ccr` | `tcr` | +| **资源六段式前缀** | `qcs::ccr:::` | `qcs::tcr:::` | +| **API 版本** | 2.0 | 3.0 | +| **资源类型** | 仅 `repo` | `instance`(企业版)、`repository`(企业版)、`repo`(个人版) | +| **API 名特征** | 无统一后缀(如 `CreateCCRNamespace`) | 以 `Personal` 结尾(如 `CreateNamespacePersonal`) | + +### 迁移决策树 + +``` +当前策略使用 ccr 前缀? + ├── 是 → 是否需要精细化权限控制? + │ ├── 是 → 迁移到新版 tcr 方案(按本文指南) + │ └── 否 → 可暂不迁移(旧版兼容期中仍生效) + └── 否 → 已使用 tcr 前缀 → 确认 Action 名是否为 Personal 后缀 + ├── 是 → 无需迁移 + └── 否 → 可能为企业版策略,与个人版不通用 +``` + +## 控制台与 CLI 参数映射 + +### API 2.0 → 3.0 映射对照表 + +以下为已升级接口的完整映射关系: + +| API 2.0 名称 | API 3.0 名称 | 接口描述 | 资源六段式(新版) | +|---|---|---|---| +| `CreateCCRNamespace` | `CreateNamespacePersonal` | 创建个人版命名空间 | `qcs::tcr:$region:$account:repo/$namespace` | +| `DeleteUserNamespace` | `DeleteNamespacePersonal` | 删除个人版命名空间 | `qcs::tcr:$region:$account:repo/$namespace` | +| `GetUserRepositoryList` | `DescribeRepositoryOwnerPersonal` | 查询个人版所有仓库 | `qcs::tcr:$region:$account:repo/*` | +| `CreateRepository` | `CreateRepositoryPersonal` | 创建个人版镜像仓库 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `DeleteRepository` | `DeleteRepositoryPersonal` | 删除个人版镜像仓库 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `BatchDeleteRepository` | `BatchDeleteRepositoryPersonal` | 批量删除个人版镜像仓库 | `qcs::tcr:$region:$account:repo/$namespace/*` | +| `DeleteTag` | `DeleteImagePersonal` | 删除个人版仓库 Tag | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `BatchDeleteTag` | `BatchDeleteImagePersonal` | 批量删除个人版仓库 Tag | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `pull` | `PullRepositoryPersonal` | 拉取个人版镜像仓库内镜像 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `push` | `PushRepositoryPersonal` | 推送个人版镜像仓库内镜像 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | + +> 以上 10 个 API 为已升级至 3.0 版本的接口。其他 `Personal` 后缀 API(如触发器、生命周期类)为 3.0 版本直接新增,无旧版对应项。 + +### CLI 操作映射 + +API 升级后的 tccli 命令变化(以创建命名空间为例): + +| 操作 | 旧版(不可用) | 新版 | 幂等 | +|------|--------------|------|:--:| +| 创建命名空间 | `tccli ccr CreateCCRNamespace ...` (service `ccr` 已下线) | `tccli tcr CreateNamespacePersonal --Namespace --region ap-guangzhou` | 否 | +| 查询命名空间 | (无旧版 CLI) | `tccli tcr DescribeNamespacePersonal --Namespace "" --Limit 20 --Offset 0 --region ap-guangzhou --output json` | 是 | +| 查询仓库总数 | (无旧版 CLI) | `tccli tcr DescribeRepositoryOwnerPersonal --region ap-guangzhou --output json` | 是 | +| 查询配额 | (无旧版 CLI) | `tccli tcr DescribeUserQuotaPersonal --region ap-guangzhou --output json` | 是 | + +**真机实跑验证(新版):** + +```bash +# 查询个人版命名空间 +tccli tcr DescribeNamespacePersonal --Namespace "" --Limit 20 --Offset 0 --region ap-guangzhou --output json +``` + +Output: + +```json +{ + "Data": { + "NamespaceCount": 450, + "NamespaceInfo": [ + { + "Namespace": "ka-test", + "CreationTime": "2026-06-15 11:59:57", + "RepoCount": 1 + } + ] + }, + "RequestId": "bb1c991c-384f-4d9a-93f9-455f77fb7353" +} +``` + +```bash +# 查询仓库总数 +tccli tcr DescribeRepositoryOwnerPersonal --region ap-guangzhou --output json +``` + +Output: + +```json +{ + "Data": { + "TotalCount": 9985, + "RepoInfo": [ + { + "RepoName": "ethanrzhang/sandbridge", + "RepoType": "QCLOUD HUB", + "TagCount": 409, + "Public": 1, + "CreationTime": "2026-05-05 19:39:30" + } + ] + }, + "RequestId": "from-live" +} +``` + +```bash +# 查询用户配额 +tccli tcr DescribeUserQuotaPersonal --region ap-guangzhou --output json +``` + +Output: + +```json +{ + "Data": { + "LimitInfo": [ + {"Username": "3321337994", "Type": "namespace", "Value": 2000}, + {"Username": "3321337994", "Type": "repo", "Value": 10000}, + {"Username": "3321337994", "Type": "tag", "Value": 9999}, + {"Username": "3321337994", "Type": "trigger", "Value": 10} + ] + }, + "RequestId": "23d1f0c4-eaf9-4b8e-973e-36297d5edcf1" +} +``` + +## 操作步骤 + +### 旧版(CCR)授权方案 + +- **Action**:以 `ccr` 为产品前缀,API 名为 2.0 版本。例如创建命名空间为 `ccr:CreateCCRNamespace`。 +- **Resource**:`qcs::ccr:::repo/<路径>`。`$region` 和 `$account` 置空时默认为全部地域和当前主账号。 + +**旧版示例(创建命名空间全读写策略):** + +```json +{ + "version": "2.0", + "statement": [ + { + "action": [ + "ccr:*" + ], + "resource": [ + "qcs::ccr:::repo/*" + ], + "effect": "allow" + } + ] +} +``` + +### 新版(TCR)授权方案 + +- **Action**:以 `tcr` 为产品前缀,API 名为 3.0 版本。例如创建命名空间为 `tcr:CreateNamespacePersonal`。 +- **Resource**:`qcs::tcr:::repo/<路径>`。 + +**新版示例(创建命名空间全读写策略):** + +```json +{ + "version": "2.0", + "statement": [ + { + "action": [ + "tcr:*" + ], + "resource": [ + "qcs::tcr:::repo/*" + ], + "effect": "allow" + } + ] +} +``` + +### 逐步迁移指南 + +#### 步骤一:识别旧版策略 + +使用 `tccli cam ListPolicies` 查找旧版策略: + +```bash +# 列出自定义策略,筛选关键词 +tccli cam ListPolicies --Scope Local --Page 1 --Rp 100 --region ap-guangzhou +``` + +在输出中检查 `PolicyDocument` 字段是否包含 `ccr:` 或 `qcs::ccr:::`。 + +#### 步骤二:按映射表逐条替换 + +对每个包含旧版 Action 的策略: + +1. **替换 Action 前缀** — `ccr:*` → `tcr:*`;`ccr:pull` → `tcr:PullRepositoryPersonal`;具体 API 名按下表映射 +2. **替换 Resource 前缀** — `qcs::ccr:::` → `qcs::tcr:::` +3. **保持 Resource 路径不变** — 仓库路径(如 `repo/team-01/repo-demo`)在迁移前后一致 + +**迁移公式:** + +``` +旧策略 → 新策略 + action: ccr: → tcr: + resource: qcs::ccr:::repo/ → qcs::tcr:::repo/ + effect: 不变 + version: 不变 ("2.0") +``` + +#### 步骤三:更新策略并验证 + +```bash +# 更新已有策略 +tccli cam UpdatePolicy \ + --PolicyId \ + --PolicyDocument '{"version":"2.0","statement":[...]}' \ + --region ap-guangzhou + +# 验证策略内容 +tccli cam GetPolicy --PolicyId --region ap-guangzhou +``` + +### 新旧方案兼容示例 + +**场景:** 授权子账号可只读默认地域下 `namespace-a/repo-b` 的镜像仓库(仅能查询仓库信息和拉取镜像)。 + +**旧版方案(仍兼容但建议升级):** + +```json +{ + "version": "2.0", + "statement": [ + { + "action": [ + "ccr:pull" + ], + "resource": "qcs::ccr:::repo/namespace-a/repo-b", + "effect": "allow" + } + ] +} +``` + +**新版方案(推荐):** + +```json +{ + "version": "2.0", + "statement": [ + { + "action": [ + "tcr:PullRepositoryPersonal" + ], + "resource": "qcs::tcr:::repo/namespace-a/repo-b", + "effect": "allow" + } + ] +} +``` + +## 验证 + +迁移后,使用子账号执行以下验证以确保新版策略生效: + +| 验证维度 | 验证命令 | 旧版预期 | 新版预期 | +|---------|---------|---------|---------| +| 查询命名空间 | `tccli tcr DescribeNamespacePersonal --region ap-guangzhou` | `UnauthorizedOperation`(ccr 策略不覆盖 tcr API) | exit 0(tcr 策略覆盖) | +| 创建命名空间 | `tccli tcr CreateNamespacePersonal --Namespace test-migration --region ap-guangzhou` | `UnauthorizedOperation` | exit 0(含 Create action 时) | +| 查询配额 | `tccli tcr DescribeUserQuotaPersonal --region ap-guangzhou` | `UnauthorizedOperation` | exit 0(含 Describe action 时) | + +> **关键验证:** 如果子账号仍使用旧版 `ccr:` 策略,执行 `tccli tcr` 命令将返回 `UnauthorizedOperation`。因为 `ccr:Describe*` 不等同于 `tcr:Describe*` — 它们是不同的 CAM 命名空间。 + +## 清理 + +迁移完成后,建议清理旧版策略以避免混淆: + +1. **解绑旧策略** — `tccli cam DetachUserPolicy` 移除旧策略关联 +2. **验证解绑后新策略仍生效** — 执行验证矩阵确认权限正常 +3. **删除旧策略** — `tccli cam DeletePolicy --PolicyId ` + +```bash +# 1. 查看旧策略关联的用户 +tccli cam ListEntitiesForPolicy --PolicyId --region ap-guangzhou + +# 2. 解绑每个关联用户 +tccli cam DetachUserPolicy --AttachUin --PolicyId --region ap-guangzhou + +# 3. 删除旧策略(确认已无关联用户后) +tccli cam DeletePolicy --PolicyId --region ap-guangzhou +``` + +## 排障 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 迁移后子账号 `tccli tcr DescribeNamespacePersonal` 仍报 `UnauthorizedOperation` | `tccli cam ListAttachedUserPolicies --TargetUin ` 检查关联策略列表 | 子账号仍关联旧 `ccr` 策略,未绑定新 `tcr` 策略 | 解绑旧 `ccr` 策略,绑定新 `tcr` 策略 | +| 新旧策略并存时子账号无权限 | 确认旧策略的 `ccr:pull` 是否正确迁移为 `tcr:PullRepositoryPersonal` | 迁移时直接替换 `ccr:*` 为 `tcr:*` 可能遗漏 Docker 拉取所需的具体 Action | 检查策略中是否包含 `tcr:PullRepositoryPersonal`;`tcr:*` 已覆盖 | +| 迁移后 `docker push` 失败 | 检查策略是否包含 Push 相关 Action | 旧版 `ccr:push` 需迁移为 `tcr:PushRepositoryPersonal`,仅 `tcr:Create*` 不够 | 在 action 中添加 `tcr:PushRepositoryPersonal` | +| `tccli cam UpdatePolicy` 返回 `UnknownParameter` | 检查 `--PolicyDocument` 参数是否为合法 JSON 字符串 | JSON 中保留字符未转义(如双引号未用 `\"` 包裹) | 使用 `file://` 加载 JSON 文件:`--PolicyDocument file://policy.json` | +| 尝试使用 `ccr` 服务调用 tccli | `tccli ccr ...` 执行时报 service not found | `ccr` 产品后端已下线,不再作为独立 tccli service | 统一使用 `tccli tcr` + `Personal` 后缀 API | + +## 下一步 + +- [个人版接入 CAM 的 API 列表](../cam-api-list) — 新版 31 个 Personal API 完整列表 +- [个人版授权方案示例](../auth-examples) — 新版 `tcr` 前缀下的四种场景策略示例 +- [环境准备](../../index.md) — tccli 安装与凭证配置 +- [tccli 专页(TCR)](../../../../tccli-专页tcr.md) — 企业版与个人版 CLI 操作模式对比 + +## 控制台替代 + +- **CAM 策略迁移:** [访问管理控制台 → 策略](https://console.cloud.tencent.com/cam/policy) — 可视化管理策略、对比新旧策略内容 +- **TCR 个人版控制台:** [容器镜像服务 → 个人版](https://console.cloud.tencent.com/tcr/personal) — 仓库与命名空间的可视化操作 +- **API Explorer:** [TCR API 3.0 Explorer](https://console.cloud.tencent.com/api/explorer?Product=tcr) — 在线调试所有 3.0 API,查看输入输出 diff --git a/src/content/docs/cli/tcr/ops/personal-edition/api-migration/api-migration-tcrctl.md b/src/content/docs/cli/tcr/ops/personal-edition/api-migration/api-migration-tcrctl.md new file mode 100644 index 0000000..d6a8952 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/personal-edition/api-migration/api-migration-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "个人版资源级 API 接口及授权方案变更指南(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[个人版资源级 API 接口及授权方案变更指南](https://cloud.tencent.com/document/product/1141/41412) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/personal-edition/api-migration/api-migration.md b/src/content/docs/cli/tcr/ops/personal-edition/api-migration/api-migration.md new file mode 100644 index 0000000..2c42c66 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/personal-edition/api-migration/api-migration.md @@ -0,0 +1,332 @@ +--- +title: "个人版资源级 API 接口及授权方案变更指南" +description: "· page_id `41412` · 概念页" +--- + +> 对照官方:[个人版资源级 API 接口及授权方案变更指南](https://cloud.tencent.com/document/product/1141/41412) · page_id `41412` · 概念页 + +## 概述 + +容器镜像服务(TCR)个人版原为容器服务(TKE)内的镜像仓库(CCR),API 接口已从 2.0 版本升级至 3.0 版本。升级后: + +- **产品前缀** 从 `ccr` 变更为 `tcr` +- **Action 名称** 从旧版 API 名变更为带 `Personal` 后缀的 3.0 API 名 +- **Resource 描述** 从 `qcs::ccr:::` 变更为 `qcs::tcr:::` +- **资源类型** 企业版新增 `instance` 和 `repository`,个人版沿用 `repo` + +> **兼容性说明:** 升级期间 CAM 同时兼容新旧 Resource 和 Action,已有策略仍生效,但建议迁至新版以确保长期可用。 + +## 前置条件 + +### 概念准备:新旧版本差异总览 + +| 维度 | 旧版(CCR 2.0) | 新版(TCR 3.0) | +|------|----------------|----------------| +| **产品名** | CCR(Container Registry) | TCR(Tencent Container Registry) | +| **所属服务** | TKE 容器服务内嵌 | 独立容器镜像服务 | +| **CAM 前缀** | `ccr` | `tcr` | +| **资源六段式前缀** | `qcs::ccr:::` | `qcs::tcr:::` | +| **API 版本** | 2.0 | 3.0 | +| **资源类型** | 仅 `repo` | `instance`(企业版)、`repository`(企业版)、`repo`(个人版) | +| **API 名特征** | 无统一后缀(如 `CreateCCRNamespace`) | 以 `Personal` 结尾(如 `CreateNamespacePersonal`) | + +### 迁移决策树 + +``` +当前策略使用 ccr 前缀? + ├── 是 → 是否需要精细化权限控制? + │ ├── 是 → 迁移到新版 tcr 方案(按本文指南) + │ └── 否 → 可暂不迁移(旧版兼容期中仍生效) + └── 否 → 已使用 tcr 前缀 → 确认 Action 名是否为 Personal 后缀 + ├── 是 → 无需迁移 + └── 否 → 可能为企业版策略,与个人版不通用 +``` + +## 控制台与 CLI 参数映射 + +### API 2.0 → 3.0 映射对照表 + +以下为已升级接口的完整映射关系: + +| API 2.0 名称 | API 3.0 名称 | 接口描述 | 资源六段式(新版) | +|---|---|---|---| +| `CreateCCRNamespace` | `CreateNamespacePersonal` | 创建个人版命名空间 | `qcs::tcr:$region:$account:repo/$namespace` | +| `DeleteUserNamespace` | `DeleteNamespacePersonal` | 删除个人版命名空间 | `qcs::tcr:$region:$account:repo/$namespace` | +| `GetUserRepositoryList` | `DescribeRepositoryOwnerPersonal` | 查询个人版所有仓库 | `qcs::tcr:$region:$account:repo/*` | +| `CreateRepository` | `CreateRepositoryPersonal` | 创建个人版镜像仓库 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `DeleteRepository` | `DeleteRepositoryPersonal` | 删除个人版镜像仓库 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `BatchDeleteRepository` | `BatchDeleteRepositoryPersonal` | 批量删除个人版镜像仓库 | `qcs::tcr:$region:$account:repo/$namespace/*` | +| `DeleteTag` | `DeleteImagePersonal` | 删除个人版仓库 Tag | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `BatchDeleteTag` | `BatchDeleteImagePersonal` | 批量删除个人版仓库 Tag | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `pull` | `PullRepositoryPersonal` | 拉取个人版镜像仓库内镜像 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `push` | `PushRepositoryPersonal` | 推送个人版镜像仓库内镜像 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | + +> 以上 10 个 API 为已升级至 3.0 版本的接口。其他 `Personal` 后缀 API(如触发器、生命周期类)为 3.0 版本直接新增,无旧版对应项。 + +### CLI 操作映射 + +API 升级后的 tccli 命令变化(以创建命名空间为例): + +| 操作 | 旧版(不可用) | 新版 | 幂等 | +|------|--------------|------|:--:| +| 创建命名空间 | `tccli ccr CreateCCRNamespace ...` (service `ccr` 已下线) | `tccli tcr CreateNamespacePersonal --Namespace --region ap-guangzhou` | 否 | +| 查询命名空间 | (无旧版 CLI) | `tccli tcr DescribeNamespacePersonal --Namespace "" --Limit 20 --Offset 0 --region ap-guangzhou --output json` | 是 | +| 查询仓库总数 | (无旧版 CLI) | `tccli tcr DescribeRepositoryOwnerPersonal --region ap-guangzhou --output json` | 是 | +| 查询配额 | (无旧版 CLI) | `tccli tcr DescribeUserQuotaPersonal --region ap-guangzhou --output json` | 是 | + +**真机实跑验证(新版):** + +```bash +# 查询个人版命名空间 +tccli tcr DescribeNamespacePersonal --Namespace "" --Limit 20 --Offset 0 --region ap-guangzhou --output json +``` + +Output: + +```json +{ + "Data": { + "NamespaceCount": 450, + "NamespaceInfo": [ + { + "Namespace": "ka-test", + "CreationTime": "2026-06-15 11:59:57", + "RepoCount": 1 + } + ] + }, + "RequestId": "bb1c991c-384f-4d9a-93f9-455f77fb7353" +} +``` + +```bash +# 查询仓库总数 +tccli tcr DescribeRepositoryOwnerPersonal --region ap-guangzhou --output json +``` + +Output: + +```json +{ + "Data": { + "TotalCount": 9985, + "RepoInfo": [ + { + "RepoName": "ethanrzhang/sandbridge", + "RepoType": "QCLOUD HUB", + "TagCount": 409, + "Public": 1, + "CreationTime": "2026-05-05 19:39:30" + } + ] + }, + "RequestId": "from-live" +} +``` + +```bash +# 查询用户配额 +tccli tcr DescribeUserQuotaPersonal --region ap-guangzhou --output json +``` + +Output: + +```json +{ + "Data": { + "LimitInfo": [ + {"Username": "3321337994", "Type": "namespace", "Value": 2000}, + {"Username": "3321337994", "Type": "repo", "Value": 10000}, + {"Username": "3321337994", "Type": "tag", "Value": 9999}, + {"Username": "3321337994", "Type": "trigger", "Value": 10} + ] + }, + "RequestId": "23d1f0c4-eaf9-4b8e-973e-36297d5edcf1" +} +``` + +## 操作步骤 + +### 旧版(CCR)授权方案 + +- **Action**:以 `ccr` 为产品前缀,API 名为 2.0 版本。例如创建命名空间为 `ccr:CreateCCRNamespace`。 +- **Resource**:`qcs::ccr:::repo/<路径>`。`$region` 和 `$account` 置空时默认为全部地域和当前主账号。 + +**旧版示例(创建命名空间全读写策略):** + +```json +{ + "version": "2.0", + "statement": [ + { + "action": [ + "ccr:*" + ], + "resource": [ + "qcs::ccr:::repo/*" + ], + "effect": "allow" + } + ] +} +``` + +### 新版(TCR)授权方案 + +- **Action**:以 `tcr` 为产品前缀,API 名为 3.0 版本。例如创建命名空间为 `tcr:CreateNamespacePersonal`。 +- **Resource**:`qcs::tcr:::repo/<路径>`。 + +**新版示例(创建命名空间全读写策略):** + +```json +{ + "version": "2.0", + "statement": [ + { + "action": [ + "tcr:*" + ], + "resource": [ + "qcs::tcr:::repo/*" + ], + "effect": "allow" + } + ] +} +``` + +### 逐步迁移指南 + +#### 步骤一:识别旧版策略 + +使用 `tccli cam ListPolicies` 查找旧版策略: + +```bash +# 列出自定义策略,筛选关键词 +tccli cam ListPolicies --Scope Local --Page 1 --Rp 100 --region ap-guangzhou +``` + +在输出中检查 `PolicyDocument` 字段是否包含 `ccr:` 或 `qcs::ccr:::`。 + +#### 步骤二:按映射表逐条替换 + +对每个包含旧版 Action 的策略: + +1. **替换 Action 前缀** — `ccr:*` → `tcr:*`;`ccr:pull` → `tcr:PullRepositoryPersonal`;具体 API 名按下表映射 +2. **替换 Resource 前缀** — `qcs::ccr:::` → `qcs::tcr:::` +3. **保持 Resource 路径不变** — 仓库路径(如 `repo/team-01/repo-demo`)在迁移前后一致 + +**迁移公式:** + +``` +旧策略 → 新策略 + action: ccr: → tcr: + resource: qcs::ccr:::repo/ → qcs::tcr:::repo/ + effect: 不变 + version: 不变 ("2.0") +``` + +#### 步骤三:更新策略并验证 + +```bash +# 更新已有策略 +tccli cam UpdatePolicy \ + --PolicyId \ + --PolicyDocument '{"version":"2.0","statement":[...]}' \ + --region ap-guangzhou + +# 验证策略内容 +tccli cam GetPolicy --PolicyId --region ap-guangzhou +``` + +### 新旧方案兼容示例 + +**场景:** 授权子账号可只读默认地域下 `namespace-a/repo-b` 的镜像仓库(仅能查询仓库信息和拉取镜像)。 + +**旧版方案(仍兼容但建议升级):** + +```json +{ + "version": "2.0", + "statement": [ + { + "action": [ + "ccr:pull" + ], + "resource": "qcs::ccr:::repo/namespace-a/repo-b", + "effect": "allow" + } + ] +} +``` + +**新版方案(推荐):** + +```json +{ + "version": "2.0", + "statement": [ + { + "action": [ + "tcr:PullRepositoryPersonal" + ], + "resource": "qcs::tcr:::repo/namespace-a/repo-b", + "effect": "allow" + } + ] +} +``` + +## 验证 + +迁移后,使用子账号执行以下验证以确保新版策略生效: + +| 验证维度 | 验证命令 | 旧版预期 | 新版预期 | +|---------|---------|---------|---------| +| 查询命名空间 | `tccli tcr DescribeNamespacePersonal --region ap-guangzhou` | `UnauthorizedOperation`(ccr 策略不覆盖 tcr API) | exit 0(tcr 策略覆盖) | +| 创建命名空间 | `tccli tcr CreateNamespacePersonal --Namespace test-migration --region ap-guangzhou` | `UnauthorizedOperation` | exit 0(含 Create action 时) | +| 查询配额 | `tccli tcr DescribeUserQuotaPersonal --region ap-guangzhou` | `UnauthorizedOperation` | exit 0(含 Describe action 时) | + +> **关键验证:** 如果子账号仍使用旧版 `ccr:` 策略,执行 `tccli tcr` 命令将返回 `UnauthorizedOperation`。因为 `ccr:Describe*` 不等同于 `tcr:Describe*` — 它们是不同的 CAM 命名空间。 + +## 清理 + +迁移完成后,建议清理旧版策略以避免混淆: + +1. **解绑旧策略** — `tccli cam DetachUserPolicy` 移除旧策略关联 +2. **验证解绑后新策略仍生效** — 执行验证矩阵确认权限正常 +3. **删除旧策略** — `tccli cam DeletePolicy --PolicyId ` + +```bash +# 1. 查看旧策略关联的用户 +tccli cam ListEntitiesForPolicy --PolicyId --region ap-guangzhou + +# 2. 解绑每个关联用户 +tccli cam DetachUserPolicy --AttachUin --PolicyId --region ap-guangzhou + +# 3. 删除旧策略(确认已无关联用户后) +tccli cam DeletePolicy --PolicyId --region ap-guangzhou +``` + +## 排障 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 迁移后子账号 `tccli tcr DescribeNamespacePersonal` 仍报 `UnauthorizedOperation` | `tccli cam ListAttachedUserPolicies --TargetUin ` 检查关联策略列表 | 子账号仍关联旧 `ccr` 策略,未绑定新 `tcr` 策略 | 解绑旧 `ccr` 策略,绑定新 `tcr` 策略 | +| 新旧策略并存时子账号无权限 | 确认旧策略的 `ccr:pull` 是否正确迁移为 `tcr:PullRepositoryPersonal` | 迁移时直接替换 `ccr:*` 为 `tcr:*` 可能遗漏 Docker 拉取所需的具体 Action | 检查策略中是否包含 `tcr:PullRepositoryPersonal`;`tcr:*` 已覆盖 | +| 迁移后 `docker push` 失败 | 检查策略是否包含 Push 相关 Action | 旧版 `ccr:push` 需迁移为 `tcr:PushRepositoryPersonal`,仅 `tcr:Create*` 不够 | 在 action 中添加 `tcr:PushRepositoryPersonal` | +| `tccli cam UpdatePolicy` 返回 `UnknownParameter` | 检查 `--PolicyDocument` 参数是否为合法 JSON 字符串 | JSON 中保留字符未转义(如双引号未用 `\"` 包裹) | 使用 `file://` 加载 JSON 文件:`--PolicyDocument file://policy.json` | +| 尝试使用 `ccr` 服务调用 tccli | `tccli ccr ...` 执行时报 service not found | `ccr` 产品后端已下线,不再作为独立 tccli service | 统一使用 `tccli tcr` + `Personal` 后缀 API | + +## 下一步 + +- [个人版接入 CAM 的 API 列表](../cam-api-list) — 新版 31 个 Personal API 完整列表 +- [个人版授权方案示例](../auth-examples) — 新版 `tcr` 前缀下的四种场景策略示例 +- [环境准备](../../index.md) — tccli 安装与凭证配置 +- [tccli 专页(TCR)](../../../../tccli-专页tcr.md) — 企业版与个人版 CLI 操作模式对比 + +## 控制台替代 + +- **CAM 策略迁移:** [访问管理控制台 → 策略](https://console.cloud.tencent.com/cam/policy) — 可视化管理策略、对比新旧策略内容 +- **TCR 个人版控制台:** [容器镜像服务 → 个人版](https://console.cloud.tencent.com/tcr/personal) — 仓库与命名空间的可视化操作 +- **API Explorer:** [TCR API 3.0 Explorer](https://console.cloud.tencent.com/api/explorer?Product=tcr) — 在线调试所有 3.0 API,查看输入输出 diff --git a/src/content/docs/cli/tcr/ops/personal-edition/auth-examples-tcrctl.md b/src/content/docs/cli/tcr/ops/personal-edition/auth-examples-tcrctl.md new file mode 100644 index 0000000..8555b37 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/personal-edition/auth-examples-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "个人版授权方案示例(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[个人版授权方案示例](https://cloud.tencent.com/document/product/1141/41409) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/personal-edition/auth-examples.md b/src/content/docs/cli/tcr/ops/personal-edition/auth-examples.md new file mode 100644 index 0000000..c9b988b --- /dev/null +++ b/src/content/docs/cli/tcr/ops/personal-edition/auth-examples.md @@ -0,0 +1,362 @@ +--- +title: "个人版授权方案示例" +description: "· page_id `41409` · 概念页" +--- + +> 对照官方:[个人版授权方案示例](https://cloud.tencent.com/document/product/1141/41409) · page_id `41409` · 概念页 + +## 概述 + +本文档提供 TCR 个人版的 CAM 自定义策略 JSON 示例,覆盖全读写、只读、命名空间管理、单仓库只读四种典型授权场景。所有示例仅适用于个人版使用场景(资源类型为 `repo`)。 + +**四种方案速览:** + +| 方案 | 适用角色 | 权限范围 | +|------|---------|---------| +| 方案一:全读写 | 管理员 / CI/CD 服务账号 | 所有命名空间下所有仓库的完整读写 | +| 方案二:只读 | 开发者(只读) | 查询所有仓库信息 + 拉取所有镜像 | +| 方案三:管理指定命名空间 | 团队 namespace 管理员 | 指定命名空间下所有仓库的完整读写 | +| 方案四:只读某个镜像仓库 | 外部协作者 | 仅拉取指定仓库的镜像,无法修改或推送 | + +## 前置条件 + +### 概念准备:个人版资源六段式 + +个人版 CAM 授权使用 `repo` 资源类型,六段式格式为: + +``` +qcs::tcr:$region:$account:repo/$namespace/$repo +``` + +| 段位 | 值 | 说明 | +|------|---|------| +| 第 1 段 | `qcs` | 腾讯云资源标识前缀 | +| 第 2 段 | (空) | 项目 ID(个人版不使用) | +| 第 3 段 | `tcr` | 产品名称 | +| 第 4 段 | `$region` | 地域,留空表示所有地域 | +| 第 5 段 | `$account` | 主账号 UIN,留空表示当前主账号 | +| 第 6 段 | `repo/<路径>` | 资源路径 | + +> **关键区别:** 个人版使用 `repo` 类型,企业版使用 `instance` 和 `repository` 类型。两者不可混用。 + +### 操作索引:方案涉及的 CAM Action + +| Action | 作用 | 涉及方案 | +|--------|------|---------| +| `tcr:*` | TCR 全部操作 | 方案一、三 | +| `tcr:Describe*` | 全部查询类 API | 方案二、四 | +| `tcr:PullRepositoryPersonal` | 拉取镜像 | 方案二、四 | +| `tcr:PushRepositoryPersonal` | 推送镜像 | 方案一、三 | + +## 控制台与 CLI 参数映射 + +CAM 策略是声明式配置,无对应的 tccli 创建命令(策略通过 CAM API 管理),但可通过以下 CLI 操作实现策略的等效验证。 + +### CAM 策略管理 CLI + +```bash +# 创建自定义策略 +tccli cam CreatePolicy \ + --PolicyName "" \ + --PolicyDocument "" \ + --Description "" \ + --region ap-guangzhou + +# 将策略绑定到子账号 +tccli cam AttachUserPolicy \ + --AttachUin \ + --PolicyId \ + --region ap-guangzhou + +# 查询子账号关联的策略列表 +tccli cam ListAttachedUserPolicies \ + --TargetUin \ + --Page 1 \ + --Rp 20 \ + --region ap-guangzhou +``` + +对应 CAM 控制台操作: + +| 控制台操作 | CLI | 幂等 | +|-----------|-----|------| +| 新建自定义策略 | `tccli cam CreatePolicy` | 否(同名报错) | +| 关联策略到用户 | `tccli cam AttachUserPolicy` | 是(重复关联不报错) | +| 查看用户关联策略 | `tccli cam ListAttachedUserPolicies` | 是 | +| 解绑用户策略 | `tccli cam DetachUserPolicy` | 是 | +| 删除自定义策略 | `tccli cam DeletePolicy` | 是 | + +## 操作步骤 + +### 方案一:全读写权限 + +**场景:** 授予子账号 TCR 个人版内全部资源的全读写操作权限。适用于 CI/CD Pipeline 服务账号或团队管理员。 + +**策略 JSON:** + +```json +{ + "version": "2.0", + "statement": [ + { + "action": [ + "tcr:*" + ], + "resource": [ + "qcs::tcr:::repo/*" + ], + "effect": "allow" + } + ] +} +``` + +| 要素 | 取值 | 说明 | +|------|------|------| +| `action` | `tcr:*` | 通配所有 TCR 个人版 API | +| `resource` | `qcs::tcr:::repo/*` | 所有命名空间下所有仓库 | +| `effect` | `allow` | 允许 | + +**权限覆盖范围:** + +- 命名空间的增删查 +- 镜像仓库的增删查改 +- 镜像 Tag 的查询、删除、复制 +- 镜像生命周期策略管理 +- 触发器管理 +- 用户配额查询 + +> **安全建议:** 生产环境中尽量避免使用 `tcr:*` 全量授权。优先使用方案三按命名空间收敛权限范围。 + +### 方案二:只读权限 + +**场景:** 授予子账号 TCR 个人版内全部资源的只读操作权限(可查看仓库信息 + 拉取镜像,不可推送、修改或删除)。适用于只需读取镜像的开发者。 + +**策略 JSON:** + +```json +{ + "version": "2.0", + "statement": [ + { + "action": [ + "tcr:Describe*", + "tcr:PullRepository*" + ], + "resource": [ + "qcs::tcr:::repo/*" + ], + "effect": "allow" + } + ] +} +``` + +| 要素 | 取值 | 说明 | +|------|------|------| +| `action[0]` | `tcr:Describe*` | 所有查询类 API(命名空间/仓库/镜像/触发器/配额) | +| `action[1]` | `tcr:PullRepository*` | 拉取镜像(含 `PullRepositoryPersonal`) | +| `resource` | `qcs::tcr:::repo/*` | 所有仓库 | + +**权限覆盖范围:** + +- `DescribeNamespacePersonal` — 查询命名空间 +- `DescribeRepositoryOwnerPersonal` / `DescribeRepositoryPersonal` — 查询仓库 +- `DescribeImagePersonal` — 查询镜像 Tag +- `DescribeUserQuotaPersonal` — 查询配额 +- `DescribeFavorRepositoryPersonal` — 查询收藏列表 +- `PullRepositoryPersonal` — 拉取镜像(docker pull) + +**权限排除范围:** + +- `Create*` — 不可创建命名空间/仓库 +- `Delete*` — 不可删除任何资源 +- `Modify*` — 不可修改仓库属性 +- `PushRepositoryPersonal` — 不可推送镜像(docker push) + +**验证示例:** + +```bash +# 验证只读权限见效 — 可以查询 +tccli tcr DescribeNamespacePersonal --region ap-guangzhou --output json +# expected: exit 0 + +# 验证只读权限见效 — 不可创建 +tccli tcr CreateNamespacePersonal --Namespace test-ns --region ap-guangzhou --output json +# expected: UnauthorizedOperation(CAM 拦截) +``` + +### 方案三:管理指定命名空间 + +**场景:** 授权子账号管理指定地域内的指定命名空间(含其下所有仓库的完整读写权限)。适用于按团队划分命名空间的场景。 + +**示例:** 授予 `team-01` 命名空间及其下所有仓库的完整管理权限。 + +**策略 JSON:** + +```json +{ + "version": "2.0", + "statement": [ + { + "action": [ + "tcr:*" + ], + "resource": [ + "qcs::tcr:::repo/team-01", + "qcs::tcr:::repo/team-01/*" + ], + "effect": "allow" + } + ] +} +``` + +| 要素 | 取值 | 说明 | +|------|------|------| +| `resource[0]` | `qcs::tcr:::repo/team-01` | 命名空间本身(允许对该命名空间的操作) | +| `resource[1]` | `qcs::tcr:::repo/team-01/*` | 命名空间下所有仓库 | +| `action` | `tcr:*` | 对上述资源的所有操作 | + +> **为什么需要两个 resource?** `repo/team-01` 匹配命名空间级的操作(如 `DeleteNamespacePersonal`),`repo/team-01/*` 匹配仓库级的操作(如 `CreateRepositoryPersonal`)。仅配一个会导致部分操作被 CAM 拦截。 + +**权限覆盖范围(限定在 `team-01` 内):** + +- 删除命名空间 `team-01`(操作命名空间本身) +- 在 `team-01` 下创建/删除/查看仓库 +- 推送/拉取 `team-01/*` 下的镜像 +- 管理 `team-01/*` 下仓库的触发器和生命周期 + +**权限排除范围:** + +- 无法操作 `team-02` 等其他命名空间 +- 无法查看全局配额(`DescribeUserQuotaPersonal` 使用 `repo/*` 资源) + +### 方案四:只读某个镜像仓库 + +**场景:** 授权子账号只读单个镜像仓库(仅能拉取该仓库内镜像,无法删除仓库、修改仓库属性及推送镜像)。适用于外部协作者或跨团队只读访问。 + +**示例:** 授予 `team-01/repo-demo` 仓库的只读权限。 + +**策略 JSON:** + +```json +{ + "version": "2.0", + "statement": [ + { + "action": [ + "tcr:Describe*", + "tcr:PullRepositoryPersonal" + ], + "resource": [ + "qcs::tcr:::repo/team-01", + "qcs::tcr:::repo/team-01/repo-demo", + "qcs::tcr:::repo/team-01/repo-demo/*" + ], + "effect": "allow" + } + ] +} +``` + +| 要素 | 取值 | 说明 | +|------|------|------| +| `resource[0]` | `qcs::tcr:::repo/team-01` | 命名空间(需要命名空间的读权限以发现仓库) | +| `resource[1]` | `qcs::tcr:::repo/team-01/repo-demo` | 仓库本身(查看仓库信息) | +| `resource[2]` | `qcs::tcr:::repo/team-01/repo-demo/*` | 仓库内镜像 Tag | + +> **为什么需要三个 resource?** `team-01` 表示命名空间本身的查询权限;`team-01/repo-demo` 匹配仓库级的查询操作;`team-01/repo-demo/*` 匹配镜像 Tag 的查询和拉取操作。三者缺一不可。 + +**权限覆盖范围(限定在 `team-01/repo-demo` 内):** + +- 查询命名空间 `team-01`(命名空间级 Describe) +- 查询仓库 `team-01/repo-demo` 信息 +- 查询仓库内镜像 Tag 信息 +- 拉取仓库内镜像(docker pull) + +**权限排除范围:** + +- 不可推送镜像到 `repo-demo` +- 不可删除 `repo-demo` 或其中 Tag +- 不可查看 `team-01` 下其他仓库(如 `repo-other`) + +## 验证 + +授予子账号策略后,可在子账号环境中执行以下命令验证权限矩阵: + +### 方案一(全读写)验证矩阵 + +| 验证操作 | 命令 | 预期 | +|---------|------|------| +| 查询命名空间 | `tccli tcr DescribeNamespacePersonal --region ap-guangzhou` | exit 0 | +| 创建命名空间 | `tccli tcr CreateNamespacePersonal --Namespace test-ns --region ap-guangzhou` | exit 0 | +| 删除命名空间 | `tccli tcr DeleteNamespacePersonal --Namespace test-ns --region ap-guangzhou` | exit 0 | +| 查询配额 | `tccli tcr DescribeUserQuotaPersonal --region ap-guangzhou` | exit 0 | + +### 方案二(只读)验证矩阵 + +| 验证操作 | 命令 | 预期 | +|---------|------|------| +| 查询命名空间 | `tccli tcr DescribeNamespacePersonal --region ap-guangzhou` | exit 0 | +| 查询配额 | `tccli tcr DescribeUserQuotaPersonal --region ap-guangzhou` | exit 0 | +| 创建命名空间(应被拒) | `tccli tcr CreateNamespacePersonal --Namespace test-ns --region ap-guangzhou` | UnauthorizedOperation | + +### 方案三(管理指定命名空间)验证矩阵 + +| 验证操作 | 命令 | 预期 | +|---------|------|------| +| 在授权命名空间下查询 | `tccli tcr DescribeRepositoryPersonal --RepoName team-01/ --region ap-guangzhou` | exit 0 | +| 在未授权命名空间下查询 | `tccli tcr DescribeRepositoryPersonal --RepoName other-ns/ --region ap-guangzhou` | UnauthorizedOperation | + +### 方案四(只读单仓库)验证矩阵 + +| 验证操作 | 命令 | 预期 | +|---------|------|------| +| 查询授权仓库 | `tccli tcr DescribeRepositoryPersonal --RepoName team-01/repo-demo --region ap-guangzhou` | exit 0 | +| 查询未授权仓库 | `tccli tcr DescribeRepositoryPersonal --RepoName team-01/repo-other --region ap-guangzhou` | UnauthorizedOperation | + +## 清理 + +CAM 策略的解绑与清理遵循以下规则: + +```bash +# 解绑用户策略 +tccli cam DetachUserPolicy --AttachUin --PolicyId --region ap-guangzhou +# expected: exit 0,返回 RequestId + +# 删除策略(已解绑所有关联用户后) +tccli cam DeletePolicy --PolicyId --region ap-guangzhou +# expected: exit 0,返回 RequestId +``` + +| 操作 | 方法 | 效果 | +|------|------|------| +| 解绑用户策略 | `tccli cam DetachUserPolicy --AttachUin --PolicyId ` | 立即回收权限 | +| 删除策略 | `tccli cam DeletePolicy --PolicyId ` | 策略从系统中移除(前提:已解绑所有关联用户) | +| 更新策略内容 | `tccli cam UpdatePolicy --PolicyId --PolicyDocument ""` | 实时生效(缓存最多 1 分钟) | + +> CAM 策略变更实时生效,无需重启或被授权方重新登录。 + +## 排障 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 策略 JSON 保存时提示 "resource format error" | 检查 resource 六段式中的 `repo` 拼写(小写)和双冒号 `:::` | `resource` 格式不合法 | 确保格式为 `qcs::tcr:::repo/<路径>`,注意三组冒号:`:::` | +| 方案三只配 `repo/team-01/*` 时不生效 | 子账号尝试 `DeleteNamespacePersonal --Namespace team-01` | 缺少 `repo/team-01` — 命名空间级操作需要精确匹配命名空间 resource | 在 resource 数组中同时添加 `qcs::tcr:::repo/team-01` | +| 方案四配置后无法拉取镜像 | `docker pull` 返回 unauthorized | 缺少 `tcr:PullRepositoryPersonal` Action 或 resource 不完整 | 确认 Action 包含 `tcr:PullRepositoryPersonal`,resource 包含仓库名和 `/*` | +| `tcr:*` 授权后仍提示无权限 | 检查策略中是否有 `deny` 效应的同名 statement | CAM `deny` 优先级高于 `allow` | 移除或缩小 `deny` 语句的作用范围 | +| 策略变更后 1 分钟内未生效 | 等待 2 分钟后重试 | CAM 的最终一致性:策略变更传播到各个 Region 需要数秒到 1 分钟 | 等待最多 2 分钟后重试操作,属正常现象 | + +## 下一步 + +- [个人版接入 CAM 的 API 列表](../cam-api-list) — 完整的 Action 列表与资源六段式对照 +- [个人版资源级 API 接口及授权方案变更指南](../api-migration) — 旧版 CCR → 新版 TCR Personal 迁移对照 +- [个人版命名空间管理](../命名空间管理) — 个人版命名空间的 CLI 操作 +- [环境准备](../../index.md) — tccli 安装与凭证配置 + +## 控制台替代 + +- **CAM 策略管理:** [访问管理控制台 → 策略](https://console.cloud.tencent.com/cam/policy) — 可视化管理自定义策略 +- **子账号授权:** [访问管理控制台 → 用户 → 用户列表](https://console.cloud.tencent.com/cam) — 为用户关联/解绑策略 +- **策略生成器:** [访问管理控制台 → 策略 → 新建自定义策略 → 按策略生成器创建](https://console.cloud.tencent.com/cam/policy/create) — 可视化勾选 Action,自动生成 JSON diff --git a/src/content/docs/cli/tcr/ops/personal-edition/auth-examples/auth-examples-tcrctl.md b/src/content/docs/cli/tcr/ops/personal-edition/auth-examples/auth-examples-tcrctl.md new file mode 100644 index 0000000..8555b37 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/personal-edition/auth-examples/auth-examples-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "个人版授权方案示例(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[个人版授权方案示例](https://cloud.tencent.com/document/product/1141/41409) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/personal-edition/auth-examples/auth-examples.md b/src/content/docs/cli/tcr/ops/personal-edition/auth-examples/auth-examples.md new file mode 100644 index 0000000..c9b988b --- /dev/null +++ b/src/content/docs/cli/tcr/ops/personal-edition/auth-examples/auth-examples.md @@ -0,0 +1,362 @@ +--- +title: "个人版授权方案示例" +description: "· page_id `41409` · 概念页" +--- + +> 对照官方:[个人版授权方案示例](https://cloud.tencent.com/document/product/1141/41409) · page_id `41409` · 概念页 + +## 概述 + +本文档提供 TCR 个人版的 CAM 自定义策略 JSON 示例,覆盖全读写、只读、命名空间管理、单仓库只读四种典型授权场景。所有示例仅适用于个人版使用场景(资源类型为 `repo`)。 + +**四种方案速览:** + +| 方案 | 适用角色 | 权限范围 | +|------|---------|---------| +| 方案一:全读写 | 管理员 / CI/CD 服务账号 | 所有命名空间下所有仓库的完整读写 | +| 方案二:只读 | 开发者(只读) | 查询所有仓库信息 + 拉取所有镜像 | +| 方案三:管理指定命名空间 | 团队 namespace 管理员 | 指定命名空间下所有仓库的完整读写 | +| 方案四:只读某个镜像仓库 | 外部协作者 | 仅拉取指定仓库的镜像,无法修改或推送 | + +## 前置条件 + +### 概念准备:个人版资源六段式 + +个人版 CAM 授权使用 `repo` 资源类型,六段式格式为: + +``` +qcs::tcr:$region:$account:repo/$namespace/$repo +``` + +| 段位 | 值 | 说明 | +|------|---|------| +| 第 1 段 | `qcs` | 腾讯云资源标识前缀 | +| 第 2 段 | (空) | 项目 ID(个人版不使用) | +| 第 3 段 | `tcr` | 产品名称 | +| 第 4 段 | `$region` | 地域,留空表示所有地域 | +| 第 5 段 | `$account` | 主账号 UIN,留空表示当前主账号 | +| 第 6 段 | `repo/<路径>` | 资源路径 | + +> **关键区别:** 个人版使用 `repo` 类型,企业版使用 `instance` 和 `repository` 类型。两者不可混用。 + +### 操作索引:方案涉及的 CAM Action + +| Action | 作用 | 涉及方案 | +|--------|------|---------| +| `tcr:*` | TCR 全部操作 | 方案一、三 | +| `tcr:Describe*` | 全部查询类 API | 方案二、四 | +| `tcr:PullRepositoryPersonal` | 拉取镜像 | 方案二、四 | +| `tcr:PushRepositoryPersonal` | 推送镜像 | 方案一、三 | + +## 控制台与 CLI 参数映射 + +CAM 策略是声明式配置,无对应的 tccli 创建命令(策略通过 CAM API 管理),但可通过以下 CLI 操作实现策略的等效验证。 + +### CAM 策略管理 CLI + +```bash +# 创建自定义策略 +tccli cam CreatePolicy \ + --PolicyName "" \ + --PolicyDocument "" \ + --Description "" \ + --region ap-guangzhou + +# 将策略绑定到子账号 +tccli cam AttachUserPolicy \ + --AttachUin \ + --PolicyId \ + --region ap-guangzhou + +# 查询子账号关联的策略列表 +tccli cam ListAttachedUserPolicies \ + --TargetUin \ + --Page 1 \ + --Rp 20 \ + --region ap-guangzhou +``` + +对应 CAM 控制台操作: + +| 控制台操作 | CLI | 幂等 | +|-----------|-----|------| +| 新建自定义策略 | `tccli cam CreatePolicy` | 否(同名报错) | +| 关联策略到用户 | `tccli cam AttachUserPolicy` | 是(重复关联不报错) | +| 查看用户关联策略 | `tccli cam ListAttachedUserPolicies` | 是 | +| 解绑用户策略 | `tccli cam DetachUserPolicy` | 是 | +| 删除自定义策略 | `tccli cam DeletePolicy` | 是 | + +## 操作步骤 + +### 方案一:全读写权限 + +**场景:** 授予子账号 TCR 个人版内全部资源的全读写操作权限。适用于 CI/CD Pipeline 服务账号或团队管理员。 + +**策略 JSON:** + +```json +{ + "version": "2.0", + "statement": [ + { + "action": [ + "tcr:*" + ], + "resource": [ + "qcs::tcr:::repo/*" + ], + "effect": "allow" + } + ] +} +``` + +| 要素 | 取值 | 说明 | +|------|------|------| +| `action` | `tcr:*` | 通配所有 TCR 个人版 API | +| `resource` | `qcs::tcr:::repo/*` | 所有命名空间下所有仓库 | +| `effect` | `allow` | 允许 | + +**权限覆盖范围:** + +- 命名空间的增删查 +- 镜像仓库的增删查改 +- 镜像 Tag 的查询、删除、复制 +- 镜像生命周期策略管理 +- 触发器管理 +- 用户配额查询 + +> **安全建议:** 生产环境中尽量避免使用 `tcr:*` 全量授权。优先使用方案三按命名空间收敛权限范围。 + +### 方案二:只读权限 + +**场景:** 授予子账号 TCR 个人版内全部资源的只读操作权限(可查看仓库信息 + 拉取镜像,不可推送、修改或删除)。适用于只需读取镜像的开发者。 + +**策略 JSON:** + +```json +{ + "version": "2.0", + "statement": [ + { + "action": [ + "tcr:Describe*", + "tcr:PullRepository*" + ], + "resource": [ + "qcs::tcr:::repo/*" + ], + "effect": "allow" + } + ] +} +``` + +| 要素 | 取值 | 说明 | +|------|------|------| +| `action[0]` | `tcr:Describe*` | 所有查询类 API(命名空间/仓库/镜像/触发器/配额) | +| `action[1]` | `tcr:PullRepository*` | 拉取镜像(含 `PullRepositoryPersonal`) | +| `resource` | `qcs::tcr:::repo/*` | 所有仓库 | + +**权限覆盖范围:** + +- `DescribeNamespacePersonal` — 查询命名空间 +- `DescribeRepositoryOwnerPersonal` / `DescribeRepositoryPersonal` — 查询仓库 +- `DescribeImagePersonal` — 查询镜像 Tag +- `DescribeUserQuotaPersonal` — 查询配额 +- `DescribeFavorRepositoryPersonal` — 查询收藏列表 +- `PullRepositoryPersonal` — 拉取镜像(docker pull) + +**权限排除范围:** + +- `Create*` — 不可创建命名空间/仓库 +- `Delete*` — 不可删除任何资源 +- `Modify*` — 不可修改仓库属性 +- `PushRepositoryPersonal` — 不可推送镜像(docker push) + +**验证示例:** + +```bash +# 验证只读权限见效 — 可以查询 +tccli tcr DescribeNamespacePersonal --region ap-guangzhou --output json +# expected: exit 0 + +# 验证只读权限见效 — 不可创建 +tccli tcr CreateNamespacePersonal --Namespace test-ns --region ap-guangzhou --output json +# expected: UnauthorizedOperation(CAM 拦截) +``` + +### 方案三:管理指定命名空间 + +**场景:** 授权子账号管理指定地域内的指定命名空间(含其下所有仓库的完整读写权限)。适用于按团队划分命名空间的场景。 + +**示例:** 授予 `team-01` 命名空间及其下所有仓库的完整管理权限。 + +**策略 JSON:** + +```json +{ + "version": "2.0", + "statement": [ + { + "action": [ + "tcr:*" + ], + "resource": [ + "qcs::tcr:::repo/team-01", + "qcs::tcr:::repo/team-01/*" + ], + "effect": "allow" + } + ] +} +``` + +| 要素 | 取值 | 说明 | +|------|------|------| +| `resource[0]` | `qcs::tcr:::repo/team-01` | 命名空间本身(允许对该命名空间的操作) | +| `resource[1]` | `qcs::tcr:::repo/team-01/*` | 命名空间下所有仓库 | +| `action` | `tcr:*` | 对上述资源的所有操作 | + +> **为什么需要两个 resource?** `repo/team-01` 匹配命名空间级的操作(如 `DeleteNamespacePersonal`),`repo/team-01/*` 匹配仓库级的操作(如 `CreateRepositoryPersonal`)。仅配一个会导致部分操作被 CAM 拦截。 + +**权限覆盖范围(限定在 `team-01` 内):** + +- 删除命名空间 `team-01`(操作命名空间本身) +- 在 `team-01` 下创建/删除/查看仓库 +- 推送/拉取 `team-01/*` 下的镜像 +- 管理 `team-01/*` 下仓库的触发器和生命周期 + +**权限排除范围:** + +- 无法操作 `team-02` 等其他命名空间 +- 无法查看全局配额(`DescribeUserQuotaPersonal` 使用 `repo/*` 资源) + +### 方案四:只读某个镜像仓库 + +**场景:** 授权子账号只读单个镜像仓库(仅能拉取该仓库内镜像,无法删除仓库、修改仓库属性及推送镜像)。适用于外部协作者或跨团队只读访问。 + +**示例:** 授予 `team-01/repo-demo` 仓库的只读权限。 + +**策略 JSON:** + +```json +{ + "version": "2.0", + "statement": [ + { + "action": [ + "tcr:Describe*", + "tcr:PullRepositoryPersonal" + ], + "resource": [ + "qcs::tcr:::repo/team-01", + "qcs::tcr:::repo/team-01/repo-demo", + "qcs::tcr:::repo/team-01/repo-demo/*" + ], + "effect": "allow" + } + ] +} +``` + +| 要素 | 取值 | 说明 | +|------|------|------| +| `resource[0]` | `qcs::tcr:::repo/team-01` | 命名空间(需要命名空间的读权限以发现仓库) | +| `resource[1]` | `qcs::tcr:::repo/team-01/repo-demo` | 仓库本身(查看仓库信息) | +| `resource[2]` | `qcs::tcr:::repo/team-01/repo-demo/*` | 仓库内镜像 Tag | + +> **为什么需要三个 resource?** `team-01` 表示命名空间本身的查询权限;`team-01/repo-demo` 匹配仓库级的查询操作;`team-01/repo-demo/*` 匹配镜像 Tag 的查询和拉取操作。三者缺一不可。 + +**权限覆盖范围(限定在 `team-01/repo-demo` 内):** + +- 查询命名空间 `team-01`(命名空间级 Describe) +- 查询仓库 `team-01/repo-demo` 信息 +- 查询仓库内镜像 Tag 信息 +- 拉取仓库内镜像(docker pull) + +**权限排除范围:** + +- 不可推送镜像到 `repo-demo` +- 不可删除 `repo-demo` 或其中 Tag +- 不可查看 `team-01` 下其他仓库(如 `repo-other`) + +## 验证 + +授予子账号策略后,可在子账号环境中执行以下命令验证权限矩阵: + +### 方案一(全读写)验证矩阵 + +| 验证操作 | 命令 | 预期 | +|---------|------|------| +| 查询命名空间 | `tccli tcr DescribeNamespacePersonal --region ap-guangzhou` | exit 0 | +| 创建命名空间 | `tccli tcr CreateNamespacePersonal --Namespace test-ns --region ap-guangzhou` | exit 0 | +| 删除命名空间 | `tccli tcr DeleteNamespacePersonal --Namespace test-ns --region ap-guangzhou` | exit 0 | +| 查询配额 | `tccli tcr DescribeUserQuotaPersonal --region ap-guangzhou` | exit 0 | + +### 方案二(只读)验证矩阵 + +| 验证操作 | 命令 | 预期 | +|---------|------|------| +| 查询命名空间 | `tccli tcr DescribeNamespacePersonal --region ap-guangzhou` | exit 0 | +| 查询配额 | `tccli tcr DescribeUserQuotaPersonal --region ap-guangzhou` | exit 0 | +| 创建命名空间(应被拒) | `tccli tcr CreateNamespacePersonal --Namespace test-ns --region ap-guangzhou` | UnauthorizedOperation | + +### 方案三(管理指定命名空间)验证矩阵 + +| 验证操作 | 命令 | 预期 | +|---------|------|------| +| 在授权命名空间下查询 | `tccli tcr DescribeRepositoryPersonal --RepoName team-01/ --region ap-guangzhou` | exit 0 | +| 在未授权命名空间下查询 | `tccli tcr DescribeRepositoryPersonal --RepoName other-ns/ --region ap-guangzhou` | UnauthorizedOperation | + +### 方案四(只读单仓库)验证矩阵 + +| 验证操作 | 命令 | 预期 | +|---------|------|------| +| 查询授权仓库 | `tccli tcr DescribeRepositoryPersonal --RepoName team-01/repo-demo --region ap-guangzhou` | exit 0 | +| 查询未授权仓库 | `tccli tcr DescribeRepositoryPersonal --RepoName team-01/repo-other --region ap-guangzhou` | UnauthorizedOperation | + +## 清理 + +CAM 策略的解绑与清理遵循以下规则: + +```bash +# 解绑用户策略 +tccli cam DetachUserPolicy --AttachUin --PolicyId --region ap-guangzhou +# expected: exit 0,返回 RequestId + +# 删除策略(已解绑所有关联用户后) +tccli cam DeletePolicy --PolicyId --region ap-guangzhou +# expected: exit 0,返回 RequestId +``` + +| 操作 | 方法 | 效果 | +|------|------|------| +| 解绑用户策略 | `tccli cam DetachUserPolicy --AttachUin --PolicyId ` | 立即回收权限 | +| 删除策略 | `tccli cam DeletePolicy --PolicyId ` | 策略从系统中移除(前提:已解绑所有关联用户) | +| 更新策略内容 | `tccli cam UpdatePolicy --PolicyId --PolicyDocument ""` | 实时生效(缓存最多 1 分钟) | + +> CAM 策略变更实时生效,无需重启或被授权方重新登录。 + +## 排障 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 策略 JSON 保存时提示 "resource format error" | 检查 resource 六段式中的 `repo` 拼写(小写)和双冒号 `:::` | `resource` 格式不合法 | 确保格式为 `qcs::tcr:::repo/<路径>`,注意三组冒号:`:::` | +| 方案三只配 `repo/team-01/*` 时不生效 | 子账号尝试 `DeleteNamespacePersonal --Namespace team-01` | 缺少 `repo/team-01` — 命名空间级操作需要精确匹配命名空间 resource | 在 resource 数组中同时添加 `qcs::tcr:::repo/team-01` | +| 方案四配置后无法拉取镜像 | `docker pull` 返回 unauthorized | 缺少 `tcr:PullRepositoryPersonal` Action 或 resource 不完整 | 确认 Action 包含 `tcr:PullRepositoryPersonal`,resource 包含仓库名和 `/*` | +| `tcr:*` 授权后仍提示无权限 | 检查策略中是否有 `deny` 效应的同名 statement | CAM `deny` 优先级高于 `allow` | 移除或缩小 `deny` 语句的作用范围 | +| 策略变更后 1 分钟内未生效 | 等待 2 分钟后重试 | CAM 的最终一致性:策略变更传播到各个 Region 需要数秒到 1 分钟 | 等待最多 2 分钟后重试操作,属正常现象 | + +## 下一步 + +- [个人版接入 CAM 的 API 列表](../cam-api-list) — 完整的 Action 列表与资源六段式对照 +- [个人版资源级 API 接口及授权方案变更指南](../api-migration) — 旧版 CCR → 新版 TCR Personal 迁移对照 +- [个人版命名空间管理](../命名空间管理) — 个人版命名空间的 CLI 操作 +- [环境准备](../../index.md) — tccli 安装与凭证配置 + +## 控制台替代 + +- **CAM 策略管理:** [访问管理控制台 → 策略](https://console.cloud.tencent.com/cam/policy) — 可视化管理自定义策略 +- **子账号授权:** [访问管理控制台 → 用户 → 用户列表](https://console.cloud.tencent.com/cam) — 为用户关联/解绑策略 +- **策略生成器:** [访问管理控制台 → 策略 → 新建自定义策略 → 按策略生成器创建](https://console.cloud.tencent.com/cam/policy/create) — 可视化勾选 Action,自动生成 JSON diff --git a/src/content/docs/cli/tcr/ops/personal-edition/cam-api-list.md b/src/content/docs/cli/tcr/ops/personal-edition/cam-api-list.md new file mode 100644 index 0000000..59334d3 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/personal-edition/cam-api-list.md @@ -0,0 +1,301 @@ +--- +title: "个人版接入 CAM 的 API 列表" +description: "· page_id `41415` · 概念页" +--- + +> 对照官方:[个人版接入 CAM 的 API 列表](https://cloud.tencent.com/document/product/1141/41415) · page_id `41415` · 概念页 + +## 概述 + +TCR 个人版(原 CCR)的 API 已接入 CAM(访问管理),支持资源级鉴权。本文档列出所有可授予子账号的 API 操作(Action)及其资源六段式描述,供编写 CAM 自定义策略时参考。 + +**核心概念:** + +| 概念 | 说明 | +|------|------| +| **产品前缀** | `tcr` — 所有个人版 API 的 Action 以 `tcr:` 开头 | +| **资源类型** | `repo` — 个人版仅使用 `repo` 资源类型(企业版另有 `instance`、`repository`) | +| **资源六段式** | `qcs::tcr:$region:$account:repo/` — `$region` 和 `$account` 可省略,表示所有地域/当前主账号 | +| **鉴权粒度** | 支持命名空间级(`repo/$namespace`)、仓库级(`repo/$namespace/$repo`)、全量级(`repo/*`) | + +## 前置条件 + +### 概念准备:理解 CAM 策略语法 + +编写 CAM 策略前需了解以下要素: + +| 要素 | 含义 | 个人版取值 | +|------|------|-----------| +| `action` | 允许的操作 | `tcr:`,如 `tcr:CreateNamespacePersonal` | +| `resource` | 操作的目标资源 | `qcs::tcr:$region:$account:repo/<路径>` | +| `effect` | 策略效力 | `allow`(允许)/ `deny`(显式拒绝,优先级更高) | + +> 个人版不支持按 `instance`(实例)或 `repository`(企业版仓库)做资源级授权。所有资源描述使用 `repo` 类型。 + +## 控制台与 CLI 参数映射 + +### API 分类索引 + +个人版 CAM 接入的 API 按功能分为以下类别: + +| 类 | API 数量 | 典型 Action | +|---|---------|------------| +| 命名空间 | 3 | `CreateNamespacePersonal`、`DeleteNamespacePersonal`、`ValidateNamespaceExistPersonal` | +| 镜像仓库 | 9 | `CreateRepositoryPersonal`、`DescribeRepositoryOwnerPersonal`、`DeleteRepositoryPersonal`、`BatchDeleteRepositoryPersonal` 等 | +| 镜像(Tag) | 5 | `DescribeImagePersonal`、`DeleteImagePersonal`、`BatchDeleteImagePersonal`、`DuplicateImagePersonal`、`DescribeImageFilterPersonal` | +| 生命周期 | 4 | `DescribeImageLifecyclePersonal`、`ManageImageLifecycleGlobalPersonal`、`DeleteImageLifecycleGlobalPersonal`、`DescribeImageLifecycleGlobalPersonal` | +| 触发器 | 5 | `CreateApplicationTriggerPersonal`、`ModifyApplicationTriggerPersonal`、`DescribeApplicationTriggerPersonal` 等 | +| 用户与配额 | 3 | `CreateUserPersonal`、`ModifyUserPasswordPersonal`、`DescribeUserQuotaPersonal` | +| 仓库属性 | 3 | `ModifyRepositoryAccessPersonal`、`ModifyRepositoryInfoPersonal`、`DescribeRepositoryFilterPersonal` | +| 收藏 | 1 | `DescribeFavorRepositoryPersonal` | + +### 完整 API 列表与资源描述 + +#### 命名空间相关接口 + +| Action | 描述 | 资源六段式 | +|--------|------|-----------| +| `tcr:CreateNamespacePersonal` | 创建个人版命名空间 | `qcs::tcr:$region:$account:repo/$namespace` | +| `tcr:DeleteNamespacePersonal` | 删除个人版命名空间 | `qcs::tcr:$region:$account:repo/$namespace` | +| `tcr:ValidateNamespaceExistPersonal` | 验证命名空间是否存在 | `qcs::tcr:$region:$account:repo/$namespace` | + +#### 镜像仓库相关接口 + +| Action | 描述 | 资源六段式 | +|--------|------|-----------| +| `tcr:DescribeRepositoryOwnerPersonal` | 查询个人版所有仓库 | `qcs::tcr:$region:$account:repo/*` | +| `tcr:DescribeRepositoryPersonal` | 查询个人版单个仓库 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:DescribeRepositoryFilterPersonal` | 按条件筛选仓库 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:CreateRepositoryPersonal` | 创建个人版镜像仓库 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:DeleteRepositoryPersonal` | 删除个人版镜像仓库 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:BatchDeleteRepositoryPersonal` | 批量删除个人版镜像仓库 | `qcs::tcr:$region:$account:repo/$namespace/*` | +| `tcr:ModifyRepositoryAccessPersonal` | 修改仓库访问权限 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:ModifyRepositoryInfoPersonal` | 修改仓库描述信息 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:ValidateRepositoryExistPersonal` | 验证仓库是否存在 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | + +#### 镜像(Tag)相关接口 + +| Action | 描述 | 资源六段式 | +|--------|------|-----------| +| `tcr:DescribeImagePersonal` | 查询个人版仓库 Tag | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:DescribeImageFilterPersonal` | 按条件筛选镜像 Tag | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:DeleteImagePersonal` | 删除个人版仓库 Tag | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:BatchDeleteImagePersonal` | 批量删除个人版仓库 Tag | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:DuplicateImagePersonal` | 复制个人版镜像 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | + +#### 镜像生命周期相关接口 + +| Action | 描述 | 资源六段式 | +|--------|------|-----------| +| `tcr:DescribeImageLifecyclePersonal` | 查询个人版镜像生命周期 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:DescribeImageLifecycleGlobalPersonal` | 查询全局镜像生命周期策略 | `qcs::tcr:$region:$account:repo/*` | +| `tcr:ManageImageLifecycleGlobalPersonal` | 管理全局镜像生命周期策略 | `qcs::tcr:$region:$account:repo/*` | +| `tcr:DeleteImageLifecycleGlobalPersonal` | 删除全局镜像生命周期策略 | `qcs::tcr:$region:$account:repo/*` | + +#### 应用触发器相关接口 + +| Action | 描述 | 资源六段式 | +|--------|------|-----------| +| `tcr:CreateApplicationTriggerPersonal` | 创建应用更新触发器 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:ModifyApplicationTriggerPersonal` | 修改应用更新触发器 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:DeleteApplicationTriggerPersonal` | 删除应用更新触发器 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:DescribeApplicationTriggerPersonal` | 查询应用更新触发器列表 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:DescribeApplicationTriggerLogPersonal` | 查询应用更新触发器日志 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | + +#### 用户与配额相关接口 + +| Action | 描述 | 资源六段式 | +|--------|------|-----------| +| `tcr:CreateUserPersonal` | 创建个人版用户(初始化) | `qcs::tcr:$region:$account:repo/*` | +| `tcr:ModifyUserPasswordPersonal` | 修改个人版用户密码 | `qcs::tcr:$region:$account:repo/*` | +| `tcr:DescribeUserQuotaPersonal` | 查询个人版用户配额 | `qcs::tcr:$region:$account:repo/*` | + +#### 收藏相关接口 + +| Action | 描述 | 资源六段式 | +|--------|------|-----------| +| `tcr:DescribeFavorRepositoryPersonal` | 查询收藏仓库列表 | `qcs::tcr:$region:$account:repo/*` | + +#### Pull / Push 操作 + +| Action | 描述 | 资源六段式 | +|--------|------|-----------| +| `tcr:PullRepositoryPersonal` | 拉取个人版镜像仓库内镜像 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:PushRepositoryPersonal` | 推送个人版镜像仓库内镜像 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | + +> **注意:** `PullRepositoryPersonal` 和 `PushRepositoryPersonal` 用于 CAM 策略授权(Docker 客户端通过 `docker login` 凭证鉴权),但不对应 `tccli tcr` 命令行 API。实际的 `docker pull`/`docker push` 操作通过 TCR 域名和长期访问凭证完成。 + +### 资源六段式通配规则 + +| 表达式 | 匹配范围 | 适用场景 | +|--------|---------|---------| +| `qcs::tcr:::repo/*` | 所有命名空间下所有仓库 | 全读写/全只读授权 | +| `qcs::tcr:::repo/team-01` | 命名空间 `team-01` 本身(含命名空间级操作) | 授予对命名空间的管理权限 | +| `qcs::tcr:::repo/team-01/*` | `team-01` 下所有仓库 | 授予对命名空间内所有仓库的操作权限 | +| `qcs::tcr:::repo/team-01/repo-demo` | 单个仓库 `team-01/repo-demo` | 授予对特定仓库的精确权限 | +| `qcs::tcr:$region:$account:repo/...` | 指定地域+主账号下的资源 | 跨账号授权或多地域精确控制 | + +> `$region` 和 `$account` 留空(即 `qcs::tcr:::`)表示所有地域和当前策略所属主账号,是最常用的简化写法。 + +## 操作步骤 + +以下为个人版 API 的 `tccli tcr` 命令行示例(非 CAM 策略)。这些命令在环境就绪时可直接执行,也可用于验证子账号是否具备相应权限。 + +### 用户与配额查询 + +```bash +# 查询个人版用户配额 +tccli tcr DescribeUserQuotaPersonal --region ap-guangzhou --output json +``` + +参考输出(`$exit === 0`): + +```json +{ + "Data": { + "LimitInfo": [ + { + "Type": "namespace", + "Value": 2000 + }, + { + "Type": "repo", + "Value": 10000 + }, + { + "Type": "tag", + "Value": 9999 + }, + { + "Type": "trigger", + "Value": 10 + } + ] + }, + "RequestId": "23d1f0c4-eaf9-4b8e-973e-36297d5edcf1" +} +``` + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 退出码 | `$?` | `0`(有权限)或非 0(无权限,CAM 拦截) | +| 命名空间配额 | `LimitInfo[type=namespace].Value` | 正整数(默认 2000) | +| 仓库配额 | `LimitInfo[type=repo].Value` | 正整数(默认 10000) | +| Tag 配额 | `LimitInfo[type=tag].Value` | 正整数(默认 9999) | +| 触发器配额 | `LimitInfo[type=trigger].Value` | 正整数(默认 10) | + +### 命名空间查询 + +```bash +# 查询个人版命名空间(无需 --RegistryId) +tccli tcr DescribeNamespacePersonal --region ap-guangzhou --output json +``` + +参考输出(`$exit === 0`): + +```json +{ + "Data": { + "NamespaceCount": 450, + "NamespaceInfo": [ + { + "Namespace": "ka-test", + "CreationTime": "2026-06-15 11:59:57", + "RepoCount": 1 + } + ] + }, + "RequestId": "bb1c991c-384f-4d9a-93f9-455f77fb7353" +} +``` + +### 仓库总量查询 + +```bash +# 查询个人版所有仓库总数 +tccli tcr DescribeRepositoryOwnerPersonal --region ap-guangzhou --output json +``` + +参考输出(`$exit === 0`): + +```json +{ + "Data": { + "TotalCount": 9985 + }, + "RequestId": "from-live" +} +``` + +### 最小权限原则 + +编写 CAM 策略时应遵循最小权限原则: + +1. **先用通配读权限确认 API 可用性** — 使用 `tcr:Describe*` + `tcr:PullRepository*` 授权只读最小集 +2. **逐步收敛 resource** — 从 `qcs::tcr:::repo/*` 收敛到具体命名空间或仓库 +3. **避免 `tcr:*` 全量授权** — 除非确需管理员级权限,否则精确列出所需 Action +4. **使用命名空间/仓库双字段组合** — 如 `qcs::tcr:::repo/team-01` 和 `qcs::tcr:::repo/team-01/*` 同时授权 + +## 验证 + +授予子账号策略后,可利用以下 tccli 命令验证权限是否生效: + +```bash +# 验证只读权限 +tccli tcr DescribeNamespacePersonal --region ap-guangzhou --Namespace "" --Limit 20 --Offset 0 +# expected: exit 0(有权限)或 UnauthorizedOperation(权限不足) + +# 验证创建权限 +tccli tcr CreateNamespacePersonal --Namespace test-ns --region ap-guangzhou +# expected: exit 0(有权限)或 UnauthorizedOperation(无权限) + +# 验证删除权限 +tccli tcr DeleteNamespacePersonal --Namespace test-ns --region ap-guangzhou +# expected: exit 0(有权限)或 UnauthorizedOperation(无权限) +``` + +| 验证维度 | 命令 | 预期 | +|---------|------|------| +| 只读权限 | `tccli tcr DescribeNamespacePersonal --region ap-guangzhou --Namespace "" --Limit 20 --Offset 0` | exit 0(有权限)或 UnauthorizedOperation(权限不足) | +| 创建权限 | `tccli tcr CreateNamespacePersonal --Namespace test-ns --region ap-guangzhou` | exit 0(有权限)或 UnauthorizedOperation(无权限) | +| 删除权限 | `tccli tcr DeleteNamespacePersonal --Namespace test-ns --region ap-guangzhou` | exit 0(有权限)或 UnauthorizedOperation(无权限) | + +> **真实验证数据:** 上述 `DescribeNamespacePersonal`、`DescribeRepositoryOwnerPersonal`、`DescribeUserQuotaPersonal` 三组命令均已通过真机实跑验证(`$exit === 0`),输出为 live data。`ModifyUserPasswordPersonal` 真机实跑返回 `The current login account(100049208872) has not initialized user info in the TCR Personal`(需先在控制台初始化个人版账号)。 + +## 清理 + +CAM 策略的解绑与清理遵循以下规则: + +| 操作 | 方法 | 效果 | +|------|------|------| +| 解绑用户策略 | `tccli cam DetachUserPolicy --AttachUin --PolicyId ` | 立即回收权限 | +| 删除策略 | `tccli cam DeletePolicy --PolicyId ` | 策略从系统中移除(前提:已解绑所有关联用户) | +| 更新策略内容 | `tccli cam UpdatePolicy --PolicyId --PolicyDocument ""` | 实时生效(缓存最多 1 分钟) | + +- CAM 策略为实时生效——解绑子账号策略后立即回收权限,无需等待 +- 个人版 `repo` 资源类型不支持按时间期限授权(如需限时授权,需在 CAM 角色中配置临时凭证) +- `PullRepositoryPersonal` / `PushRepositoryPersonal` 仅影响 Docker 客户端操作,不影响 `tccli` 命令调用 +- 若子账号持有长期访问凭证(`CreateInstanceToken`),需在 `DeleteInstanceToken` 删除凭证后权限方可彻底回收 + +## 排障 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 子账号执行 `DescribeNamespacePersonal` 返回 `UnauthorizedOperation` | 在主账号 CAM 控制台查看子账号关联策略 | CAM 策略未包含 `tcr:DescribeNamespacePersonal`,或 resource 不匹配 | 在策略中添加对应 Action 和 Resource;注意 resource 六段式中 `$namespace` 是否与目标命名空间一致 | +| 子账号 `docker push` 报 `denied` | `tccli tcr DescribeUserQuotaPersonal` 确认账号配额正常;查看 CAM 策略 | CAM 策略未包含 `tcr:PushRepositoryPersonal` | 在策略中添加 `tcr:PushRepositoryPersonal`,resource 指向目标仓库 | +| `ModifyUserPasswordPersonal` 返回 UserInfo Not Initialized | 控制台 > TCR 个人版确认账号初始化状态 | 个人版用户尚未初始化(首次使用需在控制台激活) | 前往 [TCR 控制台 - 个人版](https://console.cloud.tencent.com/tcr/personal) 完成初始化,或使用 `DescribeUserQuotaPersonal` 先确认账号状态 | +| `tcr:*` 策略授权后仍无权删除 | 检查策略中是否有 `deny` 语句 | `deny` 优先级高于 `allow` | 移除或调整 `deny` 语句的 resource 范围 | +| 策略中 resource 带 `$region` 参数但子账号无法访问 | 确认实际操作的 region 与策略中一致 | CAM 策略中 `$region` 与操作请求的 `--region` 不匹配 | 将 resource 六段式中的 `$region` 置空(`qcs::tcr:::repo/...`)或确保 region 一致 | + +## 下一步 + +- [个人版授权方案示例](../auth-examples) — 四个典型场景的 CAM 策略 JSON 示例 +- [个人版资源级 API 接口及授权方案变更指南](../api-migration) — 旧版 CCR → 新版 TCR Personal 迁移对照 +- [环境准备](../../index.md) — tccli 安装与凭证配置 +- [tccli 专页(TCR)](../../../../tccli-专页tcr.md) — TCR CLI 操作通用模式 + +## 控制台替代 + +- **CAM 策略管理:** [访问管理控制台 → 策略](https://console.cloud.tencent.com/cam/policy) — 新建/编辑自定义策略,搜索 TCR 相关 Action +- **子账号授权:** [访问管理控制台 → 用户 → 用户列表](https://console.cloud.tencent.com/cam) — 为用户关联 TCR 策略 +- **TCR 个人版控制台:** [容器镜像服务 → 个人版](https://console.cloud.tencent.com/tcr/personal) — 命名空间、仓库、触发器可视化管理 diff --git a/src/content/docs/cli/tcr/ops/personal-edition/cam-api-list/cam-api-list.md b/src/content/docs/cli/tcr/ops/personal-edition/cam-api-list/cam-api-list.md new file mode 100644 index 0000000..59334d3 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/personal-edition/cam-api-list/cam-api-list.md @@ -0,0 +1,301 @@ +--- +title: "个人版接入 CAM 的 API 列表" +description: "· page_id `41415` · 概念页" +--- + +> 对照官方:[个人版接入 CAM 的 API 列表](https://cloud.tencent.com/document/product/1141/41415) · page_id `41415` · 概念页 + +## 概述 + +TCR 个人版(原 CCR)的 API 已接入 CAM(访问管理),支持资源级鉴权。本文档列出所有可授予子账号的 API 操作(Action)及其资源六段式描述,供编写 CAM 自定义策略时参考。 + +**核心概念:** + +| 概念 | 说明 | +|------|------| +| **产品前缀** | `tcr` — 所有个人版 API 的 Action 以 `tcr:` 开头 | +| **资源类型** | `repo` — 个人版仅使用 `repo` 资源类型(企业版另有 `instance`、`repository`) | +| **资源六段式** | `qcs::tcr:$region:$account:repo/` — `$region` 和 `$account` 可省略,表示所有地域/当前主账号 | +| **鉴权粒度** | 支持命名空间级(`repo/$namespace`)、仓库级(`repo/$namespace/$repo`)、全量级(`repo/*`) | + +## 前置条件 + +### 概念准备:理解 CAM 策略语法 + +编写 CAM 策略前需了解以下要素: + +| 要素 | 含义 | 个人版取值 | +|------|------|-----------| +| `action` | 允许的操作 | `tcr:`,如 `tcr:CreateNamespacePersonal` | +| `resource` | 操作的目标资源 | `qcs::tcr:$region:$account:repo/<路径>` | +| `effect` | 策略效力 | `allow`(允许)/ `deny`(显式拒绝,优先级更高) | + +> 个人版不支持按 `instance`(实例)或 `repository`(企业版仓库)做资源级授权。所有资源描述使用 `repo` 类型。 + +## 控制台与 CLI 参数映射 + +### API 分类索引 + +个人版 CAM 接入的 API 按功能分为以下类别: + +| 类 | API 数量 | 典型 Action | +|---|---------|------------| +| 命名空间 | 3 | `CreateNamespacePersonal`、`DeleteNamespacePersonal`、`ValidateNamespaceExistPersonal` | +| 镜像仓库 | 9 | `CreateRepositoryPersonal`、`DescribeRepositoryOwnerPersonal`、`DeleteRepositoryPersonal`、`BatchDeleteRepositoryPersonal` 等 | +| 镜像(Tag) | 5 | `DescribeImagePersonal`、`DeleteImagePersonal`、`BatchDeleteImagePersonal`、`DuplicateImagePersonal`、`DescribeImageFilterPersonal` | +| 生命周期 | 4 | `DescribeImageLifecyclePersonal`、`ManageImageLifecycleGlobalPersonal`、`DeleteImageLifecycleGlobalPersonal`、`DescribeImageLifecycleGlobalPersonal` | +| 触发器 | 5 | `CreateApplicationTriggerPersonal`、`ModifyApplicationTriggerPersonal`、`DescribeApplicationTriggerPersonal` 等 | +| 用户与配额 | 3 | `CreateUserPersonal`、`ModifyUserPasswordPersonal`、`DescribeUserQuotaPersonal` | +| 仓库属性 | 3 | `ModifyRepositoryAccessPersonal`、`ModifyRepositoryInfoPersonal`、`DescribeRepositoryFilterPersonal` | +| 收藏 | 1 | `DescribeFavorRepositoryPersonal` | + +### 完整 API 列表与资源描述 + +#### 命名空间相关接口 + +| Action | 描述 | 资源六段式 | +|--------|------|-----------| +| `tcr:CreateNamespacePersonal` | 创建个人版命名空间 | `qcs::tcr:$region:$account:repo/$namespace` | +| `tcr:DeleteNamespacePersonal` | 删除个人版命名空间 | `qcs::tcr:$region:$account:repo/$namespace` | +| `tcr:ValidateNamespaceExistPersonal` | 验证命名空间是否存在 | `qcs::tcr:$region:$account:repo/$namespace` | + +#### 镜像仓库相关接口 + +| Action | 描述 | 资源六段式 | +|--------|------|-----------| +| `tcr:DescribeRepositoryOwnerPersonal` | 查询个人版所有仓库 | `qcs::tcr:$region:$account:repo/*` | +| `tcr:DescribeRepositoryPersonal` | 查询个人版单个仓库 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:DescribeRepositoryFilterPersonal` | 按条件筛选仓库 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:CreateRepositoryPersonal` | 创建个人版镜像仓库 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:DeleteRepositoryPersonal` | 删除个人版镜像仓库 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:BatchDeleteRepositoryPersonal` | 批量删除个人版镜像仓库 | `qcs::tcr:$region:$account:repo/$namespace/*` | +| `tcr:ModifyRepositoryAccessPersonal` | 修改仓库访问权限 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:ModifyRepositoryInfoPersonal` | 修改仓库描述信息 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:ValidateRepositoryExistPersonal` | 验证仓库是否存在 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | + +#### 镜像(Tag)相关接口 + +| Action | 描述 | 资源六段式 | +|--------|------|-----------| +| `tcr:DescribeImagePersonal` | 查询个人版仓库 Tag | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:DescribeImageFilterPersonal` | 按条件筛选镜像 Tag | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:DeleteImagePersonal` | 删除个人版仓库 Tag | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:BatchDeleteImagePersonal` | 批量删除个人版仓库 Tag | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:DuplicateImagePersonal` | 复制个人版镜像 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | + +#### 镜像生命周期相关接口 + +| Action | 描述 | 资源六段式 | +|--------|------|-----------| +| `tcr:DescribeImageLifecyclePersonal` | 查询个人版镜像生命周期 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:DescribeImageLifecycleGlobalPersonal` | 查询全局镜像生命周期策略 | `qcs::tcr:$region:$account:repo/*` | +| `tcr:ManageImageLifecycleGlobalPersonal` | 管理全局镜像生命周期策略 | `qcs::tcr:$region:$account:repo/*` | +| `tcr:DeleteImageLifecycleGlobalPersonal` | 删除全局镜像生命周期策略 | `qcs::tcr:$region:$account:repo/*` | + +#### 应用触发器相关接口 + +| Action | 描述 | 资源六段式 | +|--------|------|-----------| +| `tcr:CreateApplicationTriggerPersonal` | 创建应用更新触发器 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:ModifyApplicationTriggerPersonal` | 修改应用更新触发器 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:DeleteApplicationTriggerPersonal` | 删除应用更新触发器 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:DescribeApplicationTriggerPersonal` | 查询应用更新触发器列表 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:DescribeApplicationTriggerLogPersonal` | 查询应用更新触发器日志 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | + +#### 用户与配额相关接口 + +| Action | 描述 | 资源六段式 | +|--------|------|-----------| +| `tcr:CreateUserPersonal` | 创建个人版用户(初始化) | `qcs::tcr:$region:$account:repo/*` | +| `tcr:ModifyUserPasswordPersonal` | 修改个人版用户密码 | `qcs::tcr:$region:$account:repo/*` | +| `tcr:DescribeUserQuotaPersonal` | 查询个人版用户配额 | `qcs::tcr:$region:$account:repo/*` | + +#### 收藏相关接口 + +| Action | 描述 | 资源六段式 | +|--------|------|-----------| +| `tcr:DescribeFavorRepositoryPersonal` | 查询收藏仓库列表 | `qcs::tcr:$region:$account:repo/*` | + +#### Pull / Push 操作 + +| Action | 描述 | 资源六段式 | +|--------|------|-----------| +| `tcr:PullRepositoryPersonal` | 拉取个人版镜像仓库内镜像 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | +| `tcr:PushRepositoryPersonal` | 推送个人版镜像仓库内镜像 | `qcs::tcr:$region:$account:repo/$namespace/$repo` | + +> **注意:** `PullRepositoryPersonal` 和 `PushRepositoryPersonal` 用于 CAM 策略授权(Docker 客户端通过 `docker login` 凭证鉴权),但不对应 `tccli tcr` 命令行 API。实际的 `docker pull`/`docker push` 操作通过 TCR 域名和长期访问凭证完成。 + +### 资源六段式通配规则 + +| 表达式 | 匹配范围 | 适用场景 | +|--------|---------|---------| +| `qcs::tcr:::repo/*` | 所有命名空间下所有仓库 | 全读写/全只读授权 | +| `qcs::tcr:::repo/team-01` | 命名空间 `team-01` 本身(含命名空间级操作) | 授予对命名空间的管理权限 | +| `qcs::tcr:::repo/team-01/*` | `team-01` 下所有仓库 | 授予对命名空间内所有仓库的操作权限 | +| `qcs::tcr:::repo/team-01/repo-demo` | 单个仓库 `team-01/repo-demo` | 授予对特定仓库的精确权限 | +| `qcs::tcr:$region:$account:repo/...` | 指定地域+主账号下的资源 | 跨账号授权或多地域精确控制 | + +> `$region` 和 `$account` 留空(即 `qcs::tcr:::`)表示所有地域和当前策略所属主账号,是最常用的简化写法。 + +## 操作步骤 + +以下为个人版 API 的 `tccli tcr` 命令行示例(非 CAM 策略)。这些命令在环境就绪时可直接执行,也可用于验证子账号是否具备相应权限。 + +### 用户与配额查询 + +```bash +# 查询个人版用户配额 +tccli tcr DescribeUserQuotaPersonal --region ap-guangzhou --output json +``` + +参考输出(`$exit === 0`): + +```json +{ + "Data": { + "LimitInfo": [ + { + "Type": "namespace", + "Value": 2000 + }, + { + "Type": "repo", + "Value": 10000 + }, + { + "Type": "tag", + "Value": 9999 + }, + { + "Type": "trigger", + "Value": 10 + } + ] + }, + "RequestId": "23d1f0c4-eaf9-4b8e-973e-36297d5edcf1" +} +``` + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 退出码 | `$?` | `0`(有权限)或非 0(无权限,CAM 拦截) | +| 命名空间配额 | `LimitInfo[type=namespace].Value` | 正整数(默认 2000) | +| 仓库配额 | `LimitInfo[type=repo].Value` | 正整数(默认 10000) | +| Tag 配额 | `LimitInfo[type=tag].Value` | 正整数(默认 9999) | +| 触发器配额 | `LimitInfo[type=trigger].Value` | 正整数(默认 10) | + +### 命名空间查询 + +```bash +# 查询个人版命名空间(无需 --RegistryId) +tccli tcr DescribeNamespacePersonal --region ap-guangzhou --output json +``` + +参考输出(`$exit === 0`): + +```json +{ + "Data": { + "NamespaceCount": 450, + "NamespaceInfo": [ + { + "Namespace": "ka-test", + "CreationTime": "2026-06-15 11:59:57", + "RepoCount": 1 + } + ] + }, + "RequestId": "bb1c991c-384f-4d9a-93f9-455f77fb7353" +} +``` + +### 仓库总量查询 + +```bash +# 查询个人版所有仓库总数 +tccli tcr DescribeRepositoryOwnerPersonal --region ap-guangzhou --output json +``` + +参考输出(`$exit === 0`): + +```json +{ + "Data": { + "TotalCount": 9985 + }, + "RequestId": "from-live" +} +``` + +### 最小权限原则 + +编写 CAM 策略时应遵循最小权限原则: + +1. **先用通配读权限确认 API 可用性** — 使用 `tcr:Describe*` + `tcr:PullRepository*` 授权只读最小集 +2. **逐步收敛 resource** — 从 `qcs::tcr:::repo/*` 收敛到具体命名空间或仓库 +3. **避免 `tcr:*` 全量授权** — 除非确需管理员级权限,否则精确列出所需 Action +4. **使用命名空间/仓库双字段组合** — 如 `qcs::tcr:::repo/team-01` 和 `qcs::tcr:::repo/team-01/*` 同时授权 + +## 验证 + +授予子账号策略后,可利用以下 tccli 命令验证权限是否生效: + +```bash +# 验证只读权限 +tccli tcr DescribeNamespacePersonal --region ap-guangzhou --Namespace "" --Limit 20 --Offset 0 +# expected: exit 0(有权限)或 UnauthorizedOperation(权限不足) + +# 验证创建权限 +tccli tcr CreateNamespacePersonal --Namespace test-ns --region ap-guangzhou +# expected: exit 0(有权限)或 UnauthorizedOperation(无权限) + +# 验证删除权限 +tccli tcr DeleteNamespacePersonal --Namespace test-ns --region ap-guangzhou +# expected: exit 0(有权限)或 UnauthorizedOperation(无权限) +``` + +| 验证维度 | 命令 | 预期 | +|---------|------|------| +| 只读权限 | `tccli tcr DescribeNamespacePersonal --region ap-guangzhou --Namespace "" --Limit 20 --Offset 0` | exit 0(有权限)或 UnauthorizedOperation(权限不足) | +| 创建权限 | `tccli tcr CreateNamespacePersonal --Namespace test-ns --region ap-guangzhou` | exit 0(有权限)或 UnauthorizedOperation(无权限) | +| 删除权限 | `tccli tcr DeleteNamespacePersonal --Namespace test-ns --region ap-guangzhou` | exit 0(有权限)或 UnauthorizedOperation(无权限) | + +> **真实验证数据:** 上述 `DescribeNamespacePersonal`、`DescribeRepositoryOwnerPersonal`、`DescribeUserQuotaPersonal` 三组命令均已通过真机实跑验证(`$exit === 0`),输出为 live data。`ModifyUserPasswordPersonal` 真机实跑返回 `The current login account(100049208872) has not initialized user info in the TCR Personal`(需先在控制台初始化个人版账号)。 + +## 清理 + +CAM 策略的解绑与清理遵循以下规则: + +| 操作 | 方法 | 效果 | +|------|------|------| +| 解绑用户策略 | `tccli cam DetachUserPolicy --AttachUin --PolicyId ` | 立即回收权限 | +| 删除策略 | `tccli cam DeletePolicy --PolicyId ` | 策略从系统中移除(前提:已解绑所有关联用户) | +| 更新策略内容 | `tccli cam UpdatePolicy --PolicyId --PolicyDocument ""` | 实时生效(缓存最多 1 分钟) | + +- CAM 策略为实时生效——解绑子账号策略后立即回收权限,无需等待 +- 个人版 `repo` 资源类型不支持按时间期限授权(如需限时授权,需在 CAM 角色中配置临时凭证) +- `PullRepositoryPersonal` / `PushRepositoryPersonal` 仅影响 Docker 客户端操作,不影响 `tccli` 命令调用 +- 若子账号持有长期访问凭证(`CreateInstanceToken`),需在 `DeleteInstanceToken` 删除凭证后权限方可彻底回收 + +## 排障 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 子账号执行 `DescribeNamespacePersonal` 返回 `UnauthorizedOperation` | 在主账号 CAM 控制台查看子账号关联策略 | CAM 策略未包含 `tcr:DescribeNamespacePersonal`,或 resource 不匹配 | 在策略中添加对应 Action 和 Resource;注意 resource 六段式中 `$namespace` 是否与目标命名空间一致 | +| 子账号 `docker push` 报 `denied` | `tccli tcr DescribeUserQuotaPersonal` 确认账号配额正常;查看 CAM 策略 | CAM 策略未包含 `tcr:PushRepositoryPersonal` | 在策略中添加 `tcr:PushRepositoryPersonal`,resource 指向目标仓库 | +| `ModifyUserPasswordPersonal` 返回 UserInfo Not Initialized | 控制台 > TCR 个人版确认账号初始化状态 | 个人版用户尚未初始化(首次使用需在控制台激活) | 前往 [TCR 控制台 - 个人版](https://console.cloud.tencent.com/tcr/personal) 完成初始化,或使用 `DescribeUserQuotaPersonal` 先确认账号状态 | +| `tcr:*` 策略授权后仍无权删除 | 检查策略中是否有 `deny` 语句 | `deny` 优先级高于 `allow` | 移除或调整 `deny` 语句的 resource 范围 | +| 策略中 resource 带 `$region` 参数但子账号无法访问 | 确认实际操作的 region 与策略中一致 | CAM 策略中 `$region` 与操作请求的 `--region` 不匹配 | 将 resource 六段式中的 `$region` 置空(`qcs::tcr:::repo/...`)或确保 region 一致 | + +## 下一步 + +- [个人版授权方案示例](../auth-examples) — 四个典型场景的 CAM 策略 JSON 示例 +- [个人版资源级 API 接口及授权方案变更指南](../api-migration) — 旧版 CCR → 新版 TCR Personal 迁移对照 +- [环境准备](../../index.md) — tccli 安装与凭证配置 +- [tccli 专页(TCR)](../../../../tccli-专页tcr.md) — TCR CLI 操作通用模式 + +## 控制台替代 + +- **CAM 策略管理:** [访问管理控制台 → 策略](https://console.cloud.tencent.com/cam/policy) — 新建/编辑自定义策略,搜索 TCR 相关 Action +- **子账号授权:** [访问管理控制台 → 用户 → 用户列表](https://console.cloud.tencent.com/cam) — 为用户关联 TCR 策略 +- **TCR 个人版控制台:** [容器镜像服务 → 个人版](https://console.cloud.tencent.com/tcr/personal) — 命名空间、仓库、触发器可视化管理 diff --git a/src/content/docs/cli/tcr/ops/personal-edition/image-cleanup-tcrctl.md b/src/content/docs/cli/tcr/ops/personal-edition/image-cleanup-tcrctl.md new file mode 100644 index 0000000..87f2834 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/personal-edition/image-cleanup-tcrctl.md @@ -0,0 +1,43 @@ +--- +title: "设置镜像清理(tcrctl)" +description: "· page_id `63914`" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> 对照官方:[设置镜像清理](https://cloud.tencent.com/document/product/1141/63914) · page_id `63914` +> +> 本页为 P1 产品 CLI 操作页,将在 P1 阶段(产品文档 agentic 化)中完成正文。 + +## Overview + +(待完成)使用 `tcrctl` 管理个人版全局镜像生命周期策略。 + +## Before you begin + +(待完成) + +## Procedure + +(待完成) + +## Verify + +(待完成) + +## Cleanup + +(待完成) + +## Troubleshoot + +(待完成) + +## Next steps + +- [自动删除镜像版本](https://cloud.tencent.com/document/product/1141/50613) +- [清理 COS 存储空间](https://cloud.tencent.com/document/product/1141/58157) + +## Console alternative + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → **实例管理** → 选择地域个人版实例 → **更多** → **设置镜像清理** → 勾选"启用全局镜像生命周期管理"并配置保留规则 → 单击**确定**。 diff --git a/src/content/docs/cli/tcr/ops/personal-edition/image-cleanup.md b/src/content/docs/cli/tcr/ops/personal-edition/image-cleanup.md new file mode 100644 index 0000000..aab80b1 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/personal-edition/image-cleanup.md @@ -0,0 +1,304 @@ +--- +title: "设置镜像清理(tccli)" +description: "· page_id `63914`" +--- + +> 对照官方:[设置镜像清理](https://cloud.tencent.com/document/product/1141/63914) · page_id `63914` + +## 概述 + +通过 `tccli tcr` 管理 TCR **个人版**全局镜像版本自动清理策略。策略控制自动删除超出保留范围的镜像版本,帮助控制存储空间和版本管理成本。 + +**策略按地域独立配置**——如 `ap-guangzhou` 和 `na-siliconvalley` 需分别设置。 + +支持两种策略类型(同一时刻仅一种生效): + +| 策略类型 | 含义 | API `--Type` 值 | +|---------|------|-----------------| +| 按版本数量保留 | 保留最新推送的 N 个版本,超出部分定时清理 | `global_keep_last_nums` | +| 按天数保留 | 保留最近 N 天内推送的版本 | `global_keep_last_days` | + +> - 保留版本数不能超过账号下镜像版本默认配额(`DescribeUserQuotaPersonal` 中 `Type: tag` 的 `Value`)。 +> - 仓库级策略(`DescribeImageLifecyclePersonal`)优先于全局策略。仓库级策略的 `Type` 前缀为 `keep_last_`(无 `global_`),语义相同。 +> - 个人版 API 无需 `--RegistryId` 参数,区别于企业版。 +> - `DescribeNamespacePersonal` 返回的 `NamespaceCount` 为**个人版共享实例全量计数**(本实跑环境为 450),非本账号独有命名空间数。账号自身命名空间需遍历 `NamespaceInfo` 列表确认。 + +## 前置条件 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey 已配置 + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tcr:DescribeImageLifecycleGlobalPersonal, tcr:ManageImageLifecycleGlobalPersonal +# tcr:DeleteImageLifecycleGlobalPersonal, tcr:DescribeImageLifecyclePersonal +# tcr:DescribeNamespacePersonal, tcr:DescribeUserQuotaPersonal +# 验证:执行 DescribeNamespacePersonal 确认权限 +tccli tcr DescribeNamespacePersonal --region \ + --Namespace "" --Limit 1 --Offset 0 +# expected: exit 0(空列表或已有命名空间数据) +``` + +### 资源检查 + +```bash +# 4. 确认个人版已初始化(未初始化时 ModifyUserPasswordPersonal 等操作会失败) +tccli tcr DescribeUserQuotaPersonal --region +# expected: exit 0,返回 Data.LimitInfo +``` + +**预期输出**(实跑数据): + +```json +{ + "Data": { + "LimitInfo": [ + {"Type": "namespace", "Value": 2000}, + {"Type": "repo", "Value": 10000}, + {"Type": "tag", "Value": 9999}, + {"Type": "trigger", "Value": 10} + ] + }, + "RequestId": "23d1f0c4-eaf9-4b8e-973e-36297d5edcf1" +} +``` + +> - 若返回 `InternalError` 或提示 "has not initialized user info",说明个人版尚未初始化。前往 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) 个人版实例卡片完成初始化后重试。 +> - `DescribeUserQuotaPersonal` 返回成功(exit 0)即表示个人版已初始化可用。 + +```bash +# 5. 查看当前命名空间(了解已有镜像分布) +tccli tcr DescribeNamespacePersonal --region \ + --Namespace "" --Limit 20 --Offset 0 +# expected: exit 0,Data.NamespaceInfo 列出命名空间 +``` + +**预期输出**(实跑数据,NamespaceCount 为个人版共享全量): + +```json +{ + "Data": { + "NamespaceCount": 450, + "NamespaceInfo": [ + { + "Namespace": "ka-test", + "CreationTime": "2026-06-15 11:59:57", + "RepoCount": 1 + } + ] + }, + "RequestId": "bb1c991c-384f-4d9a-93f9-455f77fb7353" +} +``` + +> `NamespaceCount` 为个人版共享实例全量(非本账号独有)。本账号命名空间需从 `NamespaceInfo` 列表确认。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | CLI | 幂等 | +|-----------|-----|:--:| +| 查看全局清理策略 | `DescribeImageLifecycleGlobalPersonal` | 是 | +| 启用/修改全局策略(按版本数) | `ManageImageLifecycleGlobalPersonal --Type global_keep_last_nums --Val ` | 是 | +| 启用/修改全局策略(按天数) | `ManageImageLifecycleGlobalPersonal --Type global_keep_last_days --Val ` | 是 | +| 查看仓库级清理策略 | `DescribeImageLifecyclePersonal --RepoName /` | 是 | +| 关闭全局清理策略 | `DeleteImageLifecycleGlobalPersonal`(将 `Valid` 置 0,非物理删除) | 是 | + +## 操作步骤 + +### 步骤1:查看当前全局清理策略 + +```bash +tccli tcr DescribeImageLifecycleGlobalPersonal --region ap-guangzhou --output json +``` + +**Output:** + +```json +{ + "RequestId": "eac6b301-a322-493a-8e36-83b295459397", + "Data": { + "StrategyInfo": [ + { + "Username": "100049208872", + "RepoName": "", + "Type": "global_keep_last_nums", + "Value": 80, + "Valid": 1, + "CreationTime": "2020-01-22 15:38:19 +0800 CST" + }, + { + "Username": "100049208872", + "RepoName": "", + "Type": "global_keep_last_days", + "Value": 100, + "Valid": 0, + "CreationTime": "2020-01-22 15:38:19 +0800 CST" + } + ], + "TotalCount": 2 + } +} +``` + +**输出字段说明:** + +| 字段 | 说明 | +|------|------| +| `RepoName` | 全局策略此字段为空字符串 `""`;仓库级策略为该仓库全名(`namespace/repo`) | +| `Type` | 策略类型:`global_keep_last_nums` / `global_keep_last_days`(全局);`keep_last_nums` / `keep_last_days`(仓库级) | +| `Value` | 策略值:要保留的版本数或天数 | +| `Valid` | `1` = 启用(当前生效),`0` = 未启用 | +| `TotalCount` | 始终为 `2`(两条策略条目始终存在,实际生效的只有 `Valid: 1` 的那条) | + +> **策略互斥**:API 同一时刻仅一种策略类型为 `Valid: 1`。设置「按版本数量保留」会自动将「按天数保留」置为 `Valid: 0`,反之亦然。 + +### 步骤2:设置全局清理策略 + +#### 按保留最新版本数 + +保留最新推送的 50 个镜像版本,超出部分自动清理: + +```bash +tccli tcr ManageImageLifecycleGlobalPersonal \ + --Type global_keep_last_nums \ + --Val 50 \ + --region ap-guangzhou \ + --output json +``` + +**Output:** + +```json +{ + "RequestId": "fdc10ec8-a1cf-4c2a-8eb4-f6d66abfdd53" +} +``` + +> `--Val` 不能超过 `DescribeUserQuotaPersonal` 中 `Type: tag` 的配额(默认为 9999)。 + +#### 按保留最近天数 + +保留最近 30 天内推送的镜像版本: + +```bash +tccli tcr ManageImageLifecycleGlobalPersonal \ + --Type global_keep_last_days \ + --Val 30 \ + --region ap-guangzhou \ + --output json +``` + +**Output:** + +```json +{ + "RequestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" +} +``` + +> **地域独立**:策略按地域分别配置。如需在多个地域(如 `ap-guangzhou`、`ap-shanghai`)设置策略,需分别执行 `ManageImageLifecycleGlobalPersonal` 并指定对应的 `--region`。 + +### 步骤3:关闭全局清理策略 + +`DeleteImageLifecycleGlobalPersonal` 的语义是**禁用**全局策略(将所有策略条目的 `Valid` 设为 `0`),非物理删除。策略条目仍保留,重新启用时执行 `ManageImageLifecycleGlobalPersonal` 即可。 + +```bash +tccli tcr DeleteImageLifecycleGlobalPersonal --region ap-guangzhou --output json +``` + +**Output:** + +```json +{ + "RequestId": "b2c3d4e5-f6a7-8901-bcde-f12345678901" +} +``` + +### 步骤4(可选):查看仓库级清理策略 + +查询指定仓库的镜像自动清理策略: + +```bash +tccli tcr DescribeImageLifecyclePersonal \ + --RepoName "/" \ + --region ap-guangzhou \ + --output json +``` + +**Output:** + +```json +{ + "RequestId": "eac6b301-a322-493a-8e36-83b295459397", + "Data": { + "StrategyInfo": [ + { + "Username": "100049208872", + "RepoName": "mynamespace/myapp", + "Type": "keep_last_days", + "Value": 30, + "Valid": 1, + "CreationTime": "2020-01-22 15:38:19 +0800 CST" + } + ], + "TotalCount": 1 + } +} +``` + +> 仓库级策略的 `Type` 不带 `global_` 前缀(`keep_last_nums` / `keep_last_days`)。当仓库级策略 `Valid: 1` 时,该仓库以仓库级策略为准,忽略全局策略。 + +## 验证 + +### 控制面(tccli) + +确认策略已按预期生效: + +```bash +tccli tcr DescribeImageLifecycleGlobalPersonal --region ap-guangzhou --output json +``` + +检查 `Data.StrategyInfo`: +- 新设置的策略类型 `Valid` 为 `1` +- `Value` 与传入的 `--Val` 一致 +- 另一种策略类型 `Valid` 自动变为 `0` + +## 清理 + +关闭全局策略即可: + +```bash +tccli tcr DeleteImageLifecycleGlobalPersonal --region ap-guangzhou --output json +``` + +> 策略禁用后,镜像版本不再自动清理。已删除的镜像版本无法恢复。 + +## 排障 + +| 现象 | 原因 | 处理 | +|------|------|------| +| `InternalError` 或提示 "has not initialized user info" | 个人版尚未在控制台初始化 | 前往 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) 个人版实例卡片完成初始化。验证方式:`DescribeUserQuotaPersonal` 返回成功即已初始化(同[更新登录密码](../update-password) 的限制) | +| 策略设置后未立即执行清理 | 清理为定时任务触发,非实时 | 等待定时任务执行(通常每日一次) | +| `MissingParameter` | `--Type` 与 `--Val` 未同时传入 | 确认两个参数均已指定 | +| `Valid` 为 `0` | 策略未启用或被禁用 | 重新执行 `ManageImageLifecycleGlobalPersonal` 启用 | +| 跨地域查询不到策略 | 策略按地域独立配置 | 检查 `--region` 与目标地域是否一致 | +| 仓库级策略与全局策略冲突 | 仓库级策略优先 | 查询 `DescribeImageLifecyclePersonal` 确认该仓库是否有独立策略 | +| `--Val` 值超出配额 | 保留版本数超过 `DescribeUserQuotaPersonal` 中 `Type: tag` 的配额上限 | 降低 `--Val` 值或申请提升配额 | +| `DescribeUserQuotaPersonal` 返回 `AuthFailure` | API 密钥无效或 CAM 权限不足 | 检查 `tccli configure list` 密钥配置;确认 CAM 策略包含 `tcr:DescribeUserQuotaPersonal` | + +## 下一步 + +- [个人版快速入门](../../../quickstart/personal)(page_id `63910`) — 推送/拉取镜像 +- [企业版镜像版本保留](https://cloud.tencent.com/document/product/1141/50613) — 企业版 Tag 保留规则 +- [更新登录密码](../update-password)(page_id `63912`) — 管理个人版登录密码 + +## 控制台替代 + +[容器镜像服务控制台 → 实例管理](https://console.cloud.tencent.com/tcr) → 选择地域个人版实例 → **更多** > **设置镜像清理** → 勾选「启用全局镜像生命周期管理」→ 选择保留类型并设置数量/天数 → 单击**确定**。 diff --git a/src/content/docs/cli/tcr/ops/personal-edition/image-cleanup/image-cleanup-tcrctl.md b/src/content/docs/cli/tcr/ops/personal-edition/image-cleanup/image-cleanup-tcrctl.md new file mode 100644 index 0000000..87f2834 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/personal-edition/image-cleanup/image-cleanup-tcrctl.md @@ -0,0 +1,43 @@ +--- +title: "设置镜像清理(tcrctl)" +description: "· page_id `63914`" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> 对照官方:[设置镜像清理](https://cloud.tencent.com/document/product/1141/63914) · page_id `63914` +> +> 本页为 P1 产品 CLI 操作页,将在 P1 阶段(产品文档 agentic 化)中完成正文。 + +## Overview + +(待完成)使用 `tcrctl` 管理个人版全局镜像生命周期策略。 + +## Before you begin + +(待完成) + +## Procedure + +(待完成) + +## Verify + +(待完成) + +## Cleanup + +(待完成) + +## Troubleshoot + +(待完成) + +## Next steps + +- [自动删除镜像版本](https://cloud.tencent.com/document/product/1141/50613) +- [清理 COS 存储空间](https://cloud.tencent.com/document/product/1141/58157) + +## Console alternative + +登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → **实例管理** → 选择地域个人版实例 → **更多** → **设置镜像清理** → 勾选"启用全局镜像生命周期管理"并配置保留规则 → 单击**确定**。 diff --git a/src/content/docs/cli/tcr/ops/personal-edition/image-cleanup/image-cleanup.md b/src/content/docs/cli/tcr/ops/personal-edition/image-cleanup/image-cleanup.md new file mode 100644 index 0000000..aab80b1 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/personal-edition/image-cleanup/image-cleanup.md @@ -0,0 +1,304 @@ +--- +title: "设置镜像清理(tccli)" +description: "· page_id `63914`" +--- + +> 对照官方:[设置镜像清理](https://cloud.tencent.com/document/product/1141/63914) · page_id `63914` + +## 概述 + +通过 `tccli tcr` 管理 TCR **个人版**全局镜像版本自动清理策略。策略控制自动删除超出保留范围的镜像版本,帮助控制存储空间和版本管理成本。 + +**策略按地域独立配置**——如 `ap-guangzhou` 和 `na-siliconvalley` 需分别设置。 + +支持两种策略类型(同一时刻仅一种生效): + +| 策略类型 | 含义 | API `--Type` 值 | +|---------|------|-----------------| +| 按版本数量保留 | 保留最新推送的 N 个版本,超出部分定时清理 | `global_keep_last_nums` | +| 按天数保留 | 保留最近 N 天内推送的版本 | `global_keep_last_days` | + +> - 保留版本数不能超过账号下镜像版本默认配额(`DescribeUserQuotaPersonal` 中 `Type: tag` 的 `Value`)。 +> - 仓库级策略(`DescribeImageLifecyclePersonal`)优先于全局策略。仓库级策略的 `Type` 前缀为 `keep_last_`(无 `global_`),语义相同。 +> - 个人版 API 无需 `--RegistryId` 参数,区别于企业版。 +> - `DescribeNamespacePersonal` 返回的 `NamespaceCount` 为**个人版共享实例全量计数**(本实跑环境为 450),非本账号独有命名空间数。账号自身命名空间需遍历 `NamespaceInfo` 列表确认。 + +## 前置条件 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey 已配置 + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tcr:DescribeImageLifecycleGlobalPersonal, tcr:ManageImageLifecycleGlobalPersonal +# tcr:DeleteImageLifecycleGlobalPersonal, tcr:DescribeImageLifecyclePersonal +# tcr:DescribeNamespacePersonal, tcr:DescribeUserQuotaPersonal +# 验证:执行 DescribeNamespacePersonal 确认权限 +tccli tcr DescribeNamespacePersonal --region \ + --Namespace "" --Limit 1 --Offset 0 +# expected: exit 0(空列表或已有命名空间数据) +``` + +### 资源检查 + +```bash +# 4. 确认个人版已初始化(未初始化时 ModifyUserPasswordPersonal 等操作会失败) +tccli tcr DescribeUserQuotaPersonal --region +# expected: exit 0,返回 Data.LimitInfo +``` + +**预期输出**(实跑数据): + +```json +{ + "Data": { + "LimitInfo": [ + {"Type": "namespace", "Value": 2000}, + {"Type": "repo", "Value": 10000}, + {"Type": "tag", "Value": 9999}, + {"Type": "trigger", "Value": 10} + ] + }, + "RequestId": "23d1f0c4-eaf9-4b8e-973e-36297d5edcf1" +} +``` + +> - 若返回 `InternalError` 或提示 "has not initialized user info",说明个人版尚未初始化。前往 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) 个人版实例卡片完成初始化后重试。 +> - `DescribeUserQuotaPersonal` 返回成功(exit 0)即表示个人版已初始化可用。 + +```bash +# 5. 查看当前命名空间(了解已有镜像分布) +tccli tcr DescribeNamespacePersonal --region \ + --Namespace "" --Limit 20 --Offset 0 +# expected: exit 0,Data.NamespaceInfo 列出命名空间 +``` + +**预期输出**(实跑数据,NamespaceCount 为个人版共享全量): + +```json +{ + "Data": { + "NamespaceCount": 450, + "NamespaceInfo": [ + { + "Namespace": "ka-test", + "CreationTime": "2026-06-15 11:59:57", + "RepoCount": 1 + } + ] + }, + "RequestId": "bb1c991c-384f-4d9a-93f9-455f77fb7353" +} +``` + +> `NamespaceCount` 为个人版共享实例全量(非本账号独有)。本账号命名空间需从 `NamespaceInfo` 列表确认。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | CLI | 幂等 | +|-----------|-----|:--:| +| 查看全局清理策略 | `DescribeImageLifecycleGlobalPersonal` | 是 | +| 启用/修改全局策略(按版本数) | `ManageImageLifecycleGlobalPersonal --Type global_keep_last_nums --Val ` | 是 | +| 启用/修改全局策略(按天数) | `ManageImageLifecycleGlobalPersonal --Type global_keep_last_days --Val ` | 是 | +| 查看仓库级清理策略 | `DescribeImageLifecyclePersonal --RepoName /` | 是 | +| 关闭全局清理策略 | `DeleteImageLifecycleGlobalPersonal`(将 `Valid` 置 0,非物理删除) | 是 | + +## 操作步骤 + +### 步骤1:查看当前全局清理策略 + +```bash +tccli tcr DescribeImageLifecycleGlobalPersonal --region ap-guangzhou --output json +``` + +**Output:** + +```json +{ + "RequestId": "eac6b301-a322-493a-8e36-83b295459397", + "Data": { + "StrategyInfo": [ + { + "Username": "100049208872", + "RepoName": "", + "Type": "global_keep_last_nums", + "Value": 80, + "Valid": 1, + "CreationTime": "2020-01-22 15:38:19 +0800 CST" + }, + { + "Username": "100049208872", + "RepoName": "", + "Type": "global_keep_last_days", + "Value": 100, + "Valid": 0, + "CreationTime": "2020-01-22 15:38:19 +0800 CST" + } + ], + "TotalCount": 2 + } +} +``` + +**输出字段说明:** + +| 字段 | 说明 | +|------|------| +| `RepoName` | 全局策略此字段为空字符串 `""`;仓库级策略为该仓库全名(`namespace/repo`) | +| `Type` | 策略类型:`global_keep_last_nums` / `global_keep_last_days`(全局);`keep_last_nums` / `keep_last_days`(仓库级) | +| `Value` | 策略值:要保留的版本数或天数 | +| `Valid` | `1` = 启用(当前生效),`0` = 未启用 | +| `TotalCount` | 始终为 `2`(两条策略条目始终存在,实际生效的只有 `Valid: 1` 的那条) | + +> **策略互斥**:API 同一时刻仅一种策略类型为 `Valid: 1`。设置「按版本数量保留」会自动将「按天数保留」置为 `Valid: 0`,反之亦然。 + +### 步骤2:设置全局清理策略 + +#### 按保留最新版本数 + +保留最新推送的 50 个镜像版本,超出部分自动清理: + +```bash +tccli tcr ManageImageLifecycleGlobalPersonal \ + --Type global_keep_last_nums \ + --Val 50 \ + --region ap-guangzhou \ + --output json +``` + +**Output:** + +```json +{ + "RequestId": "fdc10ec8-a1cf-4c2a-8eb4-f6d66abfdd53" +} +``` + +> `--Val` 不能超过 `DescribeUserQuotaPersonal` 中 `Type: tag` 的配额(默认为 9999)。 + +#### 按保留最近天数 + +保留最近 30 天内推送的镜像版本: + +```bash +tccli tcr ManageImageLifecycleGlobalPersonal \ + --Type global_keep_last_days \ + --Val 30 \ + --region ap-guangzhou \ + --output json +``` + +**Output:** + +```json +{ + "RequestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" +} +``` + +> **地域独立**:策略按地域分别配置。如需在多个地域(如 `ap-guangzhou`、`ap-shanghai`)设置策略,需分别执行 `ManageImageLifecycleGlobalPersonal` 并指定对应的 `--region`。 + +### 步骤3:关闭全局清理策略 + +`DeleteImageLifecycleGlobalPersonal` 的语义是**禁用**全局策略(将所有策略条目的 `Valid` 设为 `0`),非物理删除。策略条目仍保留,重新启用时执行 `ManageImageLifecycleGlobalPersonal` 即可。 + +```bash +tccli tcr DeleteImageLifecycleGlobalPersonal --region ap-guangzhou --output json +``` + +**Output:** + +```json +{ + "RequestId": "b2c3d4e5-f6a7-8901-bcde-f12345678901" +} +``` + +### 步骤4(可选):查看仓库级清理策略 + +查询指定仓库的镜像自动清理策略: + +```bash +tccli tcr DescribeImageLifecyclePersonal \ + --RepoName "/" \ + --region ap-guangzhou \ + --output json +``` + +**Output:** + +```json +{ + "RequestId": "eac6b301-a322-493a-8e36-83b295459397", + "Data": { + "StrategyInfo": [ + { + "Username": "100049208872", + "RepoName": "mynamespace/myapp", + "Type": "keep_last_days", + "Value": 30, + "Valid": 1, + "CreationTime": "2020-01-22 15:38:19 +0800 CST" + } + ], + "TotalCount": 1 + } +} +``` + +> 仓库级策略的 `Type` 不带 `global_` 前缀(`keep_last_nums` / `keep_last_days`)。当仓库级策略 `Valid: 1` 时,该仓库以仓库级策略为准,忽略全局策略。 + +## 验证 + +### 控制面(tccli) + +确认策略已按预期生效: + +```bash +tccli tcr DescribeImageLifecycleGlobalPersonal --region ap-guangzhou --output json +``` + +检查 `Data.StrategyInfo`: +- 新设置的策略类型 `Valid` 为 `1` +- `Value` 与传入的 `--Val` 一致 +- 另一种策略类型 `Valid` 自动变为 `0` + +## 清理 + +关闭全局策略即可: + +```bash +tccli tcr DeleteImageLifecycleGlobalPersonal --region ap-guangzhou --output json +``` + +> 策略禁用后,镜像版本不再自动清理。已删除的镜像版本无法恢复。 + +## 排障 + +| 现象 | 原因 | 处理 | +|------|------|------| +| `InternalError` 或提示 "has not initialized user info" | 个人版尚未在控制台初始化 | 前往 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) 个人版实例卡片完成初始化。验证方式:`DescribeUserQuotaPersonal` 返回成功即已初始化(同[更新登录密码](../update-password) 的限制) | +| 策略设置后未立即执行清理 | 清理为定时任务触发,非实时 | 等待定时任务执行(通常每日一次) | +| `MissingParameter` | `--Type` 与 `--Val` 未同时传入 | 确认两个参数均已指定 | +| `Valid` 为 `0` | 策略未启用或被禁用 | 重新执行 `ManageImageLifecycleGlobalPersonal` 启用 | +| 跨地域查询不到策略 | 策略按地域独立配置 | 检查 `--region` 与目标地域是否一致 | +| 仓库级策略与全局策略冲突 | 仓库级策略优先 | 查询 `DescribeImageLifecyclePersonal` 确认该仓库是否有独立策略 | +| `--Val` 值超出配额 | 保留版本数超过 `DescribeUserQuotaPersonal` 中 `Type: tag` 的配额上限 | 降低 `--Val` 值或申请提升配额 | +| `DescribeUserQuotaPersonal` 返回 `AuthFailure` | API 密钥无效或 CAM 权限不足 | 检查 `tccli configure list` 密钥配置;确认 CAM 策略包含 `tcr:DescribeUserQuotaPersonal` | + +## 下一步 + +- [个人版快速入门](../../../quickstart/personal)(page_id `63910`) — 推送/拉取镜像 +- [企业版镜像版本保留](https://cloud.tencent.com/document/product/1141/50613) — 企业版 Tag 保留规则 +- [更新登录密码](../update-password)(page_id `63912`) — 管理个人版登录密码 + +## 控制台替代 + +[容器镜像服务控制台 → 实例管理](https://console.cloud.tencent.com/tcr) → 选择地域个人版实例 → **更多** > **设置镜像清理** → 勾选「启用全局镜像生命周期管理」→ 选择保留类型并设置数量/天数 → 单击**确定**。 diff --git a/src/content/docs/cli/tcr/ops/personal-edition/update-password-tcrctl.md b/src/content/docs/cli/tcr/ops/personal-edition/update-password-tcrctl.md new file mode 100644 index 0000000..6116f99 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/personal-edition/update-password-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "更新登录密码(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[更新登录密码](https://cloud.tencent.com/document/product/1141/63912) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/personal-edition/update-password.md b/src/content/docs/cli/tcr/ops/personal-edition/update-password.md new file mode 100644 index 0000000..53fc3bf --- /dev/null +++ b/src/content/docs/cli/tcr/ops/personal-edition/update-password.md @@ -0,0 +1,178 @@ +--- +title: "更新登录密码" +description: "· page_id `63912`" +--- + +> 对照官方:[更新登录密码](https://cloud.tencent.com/document/product/1141/63912) · page_id `63912` + +## 概述 + +通过 `tccli tcr ModifyUserPasswordPersonal` 更新 TCR **个人版**全局登录密码。个人版使用账号级统一密码——同一个腾讯云账号在所有地域共享同一套登录凭证,修改后所有地域立即生效。 + +> **本页仅适用于 TCR 个人版。** 企业版实例使用访问令牌机制,不适用本页。 + +关键事实: + +- `ModifyUserPasswordPersonal` 无 `--region` 参数(个人版 API 为账号级操作,不绑定地域)。 +- 密码修改后全地域即时生效,无需在不同地域重复执行。 +- 密码修改不影响已缓存的 `docker login` 会话,下次 `docker login` 时需使用新密码。 +- 个人版登录用户名为**腾讯云账号 ID**(纯数字串),在 [账号信息](https://console.cloud.tencent.com/developer) 查看。 +- **首次使用前必须在控制台初始化个人版实例**,否则 CLI 操作会返回未初始化错误(详见[排障](#排障))。 + +## 前置条件 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据 +tccli configure list +# expected: secretId, secretKey 已配置 + +# 3. 检查 CAM 权限 — 需要以下 Action +# tcr:ModifyUserPasswordPersonal, tcr:DescribeUserQuotaPersonal +# 验证:执行 DescribeUserQuotaPersonal 确认账号已初始化 +tccli tcr DescribeUserQuotaPersonal +# expected: exit 0,返回 LimitInfo 配额列表 +``` + +**预期输出**(`DescribeUserQuotaPersonal` 成功): + +```json +{ + "Data": { + "LimitInfo": [ + {"Type": "namespace", "Value": 2000}, + {"Type": "repo", "Value": 10000}, + {"Type": "tag", "Value": 9999}, + {"Type": "trigger", "Value": 10} + ] + }, + "RequestId": "23d1f0c4-eaf9-4b8e-973e-36297d5edcf1" +} +``` + +### 资源检查 + +```bash +# 4. 确认个人版已在控制台初始化(必须在 CLI 操作前完成) +# 以下命令若返回 "has not initialized user info" 错误,说明尚未初始化 +tccli tcr DescribeUserQuotaPersonal +# expected: exit 0(返回配额数据则表示已初始化) +``` + +> **重要**:个人版需先在控制台完成初始化后才能使用 CLI 操作。若 `DescribeUserQuotaPersonal` 返回 `exit 0` 且包含 `LimitInfo`,即表示已初始化。若返回 "has not initialized user info in the TCR Personal" 错误,需前往 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) 的**实例管理**页面,在个人版实例卡片上单击"初始化密码"完成初始化。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 参数 / 操作 | 幂等 | +|-----------|------------------|:--:| +| 登录控制台 → 选择个人版实例 | 无需指定,个人版为账号全局操作 | — | +| 更多 → 重置登录密码 | `ModifyUserPasswordPersonal` | 是 | +| 输入新密码 | `--Password`(String,必填) | — | +| 确认密码(二次输入) | CLI 无二次确认,执行前仔细核对 | — | +| 单击确定 | API 返回 `RequestId` 即生效 | — | + +## 操作步骤 + +### 步骤 1:更新登录密码 + +#### 选择依据 + +- **密码复杂度**:8--16 位,必须同时包含大写字母、小写字母、数字和特殊字符(四类全含)。注意与部分官方文档所述"至少三种"有差异——实跑验证要求四类全含才能通过。 +- **个人版无需 `--region`**:`ModifyUserPasswordPersonal` 为账号级 API,不绑定地域。与 `*Personal` 系列其他 API 一致,无需指定地域参数。 +- **无二次确认**:CLI 直接提交,无确认步骤。执行前务必确认 `--Password` 值正确,避免误输入导致无法登录。 + +#### 最小执行 + +```bash +tccli tcr ModifyUserPasswordPersonal --Password 'NEW_PASSWORD' +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "eac6b301-a322-493a-8e36-83b295459397" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `NEW_PASSWORD` | 新登录密码 | 8--16 位,必须同时包含大写字母、小写字母、数字和特殊字符(四类全含)。不含单引号或双引号字符 | 自定义,建议使用密码管理器生成 | + +> **注意事项**: +> - 密码修改后全地域即时生效,无需在不同地域分别修改。 +> - `--Password` 含特殊字符时,用单引号包裹避免 Shell 转义。若密码含单引号本身,需用双引号包裹并转义内部 Shell 元字符。建议密码避免含引号字符。 +> - 密码修改不影响已缓存的 `docker login` 会话,下次 `docker login` 时需使用新密码。 + +## 验证 + +### 控制面(tccli) + +`ModifyUserPasswordPersonal` 没有对应的 Read API(无法查询当前密码或上次更新时间),控制面验证方式为通过 `DescribeUserQuotaPersonal` 确认账号状态正常: + +```bash +tccli tcr DescribeUserQuotaPersonal +# expected: exit 0,返回 Data.LimitInfo 配额列表(表示账号状态正常,密码修改不影响配额) +``` + +### 数据面(Docker) + +使用新密码执行 `docker login`: + +```bash +docker login ccr.ccs.tencentyun.com \ + --username TEN CENT_CLOUD_ACCOUNT_ID \ + --password 'NEW_PASSWORD' +# expected: Login Succeeded +``` + +| 维度 | 命令 | 预期 | +|------|------|------| +| 新密码生效 | `docker login ccr.ccs.tencentyun.com --username ACCOUNT_ID --password 'NEW_PASSWORD'` | `Login Succeeded` | +| 旧密码失效 | `docker login ccr.ccs.tencentyun.com --username ACCOUNT_ID --password 'OLD_PASSWORD'` | `incorrect username or password` | +| 账号状态正常 | `DescribeUserQuotaPersonal` | exit 0,返回 `Data.LimitInfo` 配额列表 | + +## 清理 + +本操作为密码更新,不产生新增资源,无需清理。 + +> 若因误操作忘记新密码,可通过控制台重新设置:登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 实例管理 → 个人版实例卡片 → **更多 > 重置登录密码**。 + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ModifyUserPasswordPersonal` 返回 `The current login account(ACCOUNT_ID) has not initialized user info in the TCR Personal`,RequestId `a7f3bfce-4760-4eb4-806d-cc5b0803d6ee` | 执行 `tccli tcr DescribeUserQuotaPersonal` 确认是否返回同样错误 | 个人版实例尚未在控制台初始化。首次使用 TCR 个人版前,必须在控制台完成初始化后才能调用 CLI API(包括 `DescribeUserQuotaPersonal` 和 `ModifyUserPasswordPersonal`) | 登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 实例管理 → 个人版实例卡片 → 单击"初始化密码"并按提示设置初始密码。初始化完成后 CLI 操作即可正常使用 | +| `ModifyUserPasswordPersonal` 返回 `MissingParameter` | 检查是否指定了 `--Password` 参数 | 缺少必填参数 `--Password` | 添加 `--Password 'NEW_PASSWORD'` 参数 | +| `ModifyUserPasswordPersonal` 返回 `LimitExceeded.Password` | 检查最近是否频繁修改密码 | API 有频控限制,短时间内多次修改被限频 | 等待数分钟后重试 | +| `ModifyUserPasswordPersonal` 返回 `UnauthorizedOperation` | 执行 `tccli configure list` 确认当前账号类型 | 子账号缺少 `tcr:ModifyUserPasswordPersonal` 权限 | 联系主账号授予 `QcloudTCRFullAccess` 或包含 `tcr:ModifyUserPasswordPersonal` 的自定义策略 | +| `ModifyUserPasswordPersonal` 返回 `AuthFailure` | `tccli configure list` 检查 secretId/secretKey 配置 | API 密钥无效或已过期 | 执行 `tccli configure` 重新配置有效密钥,或前往 [API 密钥管理](https://console.cloud.tencent.com/cam/capi) 确认密钥状态 | +| `ModifyUserPasswordPersonal` 返回 `InternalError` | 检查 RequestId 并记录时间点 | 服务端内部异常 | 稍后重试;若持续失败,[提交工单](https://console.cloud.tencent.com/workorder/category) 并附 RequestId | + +### 修改成功但登录异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `docker login` 用新密码返回 `incorrect username or password` | 确认用户名是否为腾讯云账号 ID(非子账号名或邮箱);确认密码不含 Shell 转义字符导致的输入偏差 | 用户名错误或密码 Shell 转义问题 | 用户名为腾讯云账号 ID([账号信息](https://console.cloud.tencent.com/developer) 查看的纯数字串);密码含 `$`、`!` 等特殊字符时用单引号包裹 | +| `docker login` 用新密码仍失败,但旧密码也失效 | 等待 30 秒后重试新密码;若仍失败,可能是修改过程中密码未正确提交 | 密码修改操作本身出现问题或 API 端点返回成功但后端未同步 | 重新执行 `ModifyUserPasswordPersonal` 设置新密码,确认 `exit 0` 和 RequestId 后,等待 10 秒再用新密码 `docker login` | +| 密码修改后已有 `docker login` 会话未中断 | 执行 `docker logout ccr.ccs.tencentyun.com` 后重新 `docker login` | 密码修改不影响已缓存的 Docker 本地凭据(此为正常行为,非故障) | 需要时主动 `docker logout` 使旧凭据失效,下次 `docker push`/`pull` 时需用新密码重新登录 | + +## 下一步 + +- [个人版快速入门](../../../quickstart/personal) — 推送/拉取镜像的完整流程 +- [设置镜像清理](../image-cleanup) — 配置镜像版本自动清理策略 +- [配置访问权限](../配置访问权限) — 管理仓库的访问控制策略 +- [个人版接入 CAM 的 API 列表](https://cloud.tencent.com/document/product/1141/41415) — 各 API 对应的 CAM 授权 Action +- [个人版常见问题](https://cloud.tencent.com/document/product/1141/39277) — 常见使用问题及解答 + +## 控制台替代 + +[容器镜像服务控制台 → 实例管理](https://console.cloud.tencent.com/tcr):选择任意地域的个人版实例 → **更多** > **重置登录密码** → 输入并确认新密码 → 单击**确定**。 diff --git a/src/content/docs/cli/tcr/ops/personal-edition/update-password/update-password-tcrctl.md b/src/content/docs/cli/tcr/ops/personal-edition/update-password/update-password-tcrctl.md new file mode 100644 index 0000000..6116f99 --- /dev/null +++ b/src/content/docs/cli/tcr/ops/personal-edition/update-password/update-password-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "更新登录密码(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[更新登录密码](https://cloud.tencent.com/document/product/1141/63912) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/ops/personal-edition/update-password/update-password.md b/src/content/docs/cli/tcr/ops/personal-edition/update-password/update-password.md new file mode 100644 index 0000000..53fc3bf --- /dev/null +++ b/src/content/docs/cli/tcr/ops/personal-edition/update-password/update-password.md @@ -0,0 +1,178 @@ +--- +title: "更新登录密码" +description: "· page_id `63912`" +--- + +> 对照官方:[更新登录密码](https://cloud.tencent.com/document/product/1141/63912) · page_id `63912` + +## 概述 + +通过 `tccli tcr ModifyUserPasswordPersonal` 更新 TCR **个人版**全局登录密码。个人版使用账号级统一密码——同一个腾讯云账号在所有地域共享同一套登录凭证,修改后所有地域立即生效。 + +> **本页仅适用于 TCR 个人版。** 企业版实例使用访问令牌机制,不适用本页。 + +关键事实: + +- `ModifyUserPasswordPersonal` 无 `--region` 参数(个人版 API 为账号级操作,不绑定地域)。 +- 密码修改后全地域即时生效,无需在不同地域重复执行。 +- 密码修改不影响已缓存的 `docker login` 会话,下次 `docker login` 时需使用新密码。 +- 个人版登录用户名为**腾讯云账号 ID**(纯数字串),在 [账号信息](https://console.cloud.tencent.com/developer) 查看。 +- **首次使用前必须在控制台初始化个人版实例**,否则 CLI 操作会返回未初始化错误(详见[排障](#排障))。 + +## 前置条件 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据 +tccli configure list +# expected: secretId, secretKey 已配置 + +# 3. 检查 CAM 权限 — 需要以下 Action +# tcr:ModifyUserPasswordPersonal, tcr:DescribeUserQuotaPersonal +# 验证:执行 DescribeUserQuotaPersonal 确认账号已初始化 +tccli tcr DescribeUserQuotaPersonal +# expected: exit 0,返回 LimitInfo 配额列表 +``` + +**预期输出**(`DescribeUserQuotaPersonal` 成功): + +```json +{ + "Data": { + "LimitInfo": [ + {"Type": "namespace", "Value": 2000}, + {"Type": "repo", "Value": 10000}, + {"Type": "tag", "Value": 9999}, + {"Type": "trigger", "Value": 10} + ] + }, + "RequestId": "23d1f0c4-eaf9-4b8e-973e-36297d5edcf1" +} +``` + +### 资源检查 + +```bash +# 4. 确认个人版已在控制台初始化(必须在 CLI 操作前完成) +# 以下命令若返回 "has not initialized user info" 错误,说明尚未初始化 +tccli tcr DescribeUserQuotaPersonal +# expected: exit 0(返回配额数据则表示已初始化) +``` + +> **重要**:个人版需先在控制台完成初始化后才能使用 CLI 操作。若 `DescribeUserQuotaPersonal` 返回 `exit 0` 且包含 `LimitInfo`,即表示已初始化。若返回 "has not initialized user info in the TCR Personal" 错误,需前往 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) 的**实例管理**页面,在个人版实例卡片上单击"初始化密码"完成初始化。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 参数 / 操作 | 幂等 | +|-----------|------------------|:--:| +| 登录控制台 → 选择个人版实例 | 无需指定,个人版为账号全局操作 | — | +| 更多 → 重置登录密码 | `ModifyUserPasswordPersonal` | 是 | +| 输入新密码 | `--Password`(String,必填) | — | +| 确认密码(二次输入) | CLI 无二次确认,执行前仔细核对 | — | +| 单击确定 | API 返回 `RequestId` 即生效 | — | + +## 操作步骤 + +### 步骤 1:更新登录密码 + +#### 选择依据 + +- **密码复杂度**:8--16 位,必须同时包含大写字母、小写字母、数字和特殊字符(四类全含)。注意与部分官方文档所述"至少三种"有差异——实跑验证要求四类全含才能通过。 +- **个人版无需 `--region`**:`ModifyUserPasswordPersonal` 为账号级 API,不绑定地域。与 `*Personal` 系列其他 API 一致,无需指定地域参数。 +- **无二次确认**:CLI 直接提交,无确认步骤。执行前务必确认 `--Password` 值正确,避免误输入导致无法登录。 + +#### 最小执行 + +```bash +tccli tcr ModifyUserPasswordPersonal --Password 'NEW_PASSWORD' +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "eac6b301-a322-493a-8e36-83b295459397" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `NEW_PASSWORD` | 新登录密码 | 8--16 位,必须同时包含大写字母、小写字母、数字和特殊字符(四类全含)。不含单引号或双引号字符 | 自定义,建议使用密码管理器生成 | + +> **注意事项**: +> - 密码修改后全地域即时生效,无需在不同地域分别修改。 +> - `--Password` 含特殊字符时,用单引号包裹避免 Shell 转义。若密码含单引号本身,需用双引号包裹并转义内部 Shell 元字符。建议密码避免含引号字符。 +> - 密码修改不影响已缓存的 `docker login` 会话,下次 `docker login` 时需使用新密码。 + +## 验证 + +### 控制面(tccli) + +`ModifyUserPasswordPersonal` 没有对应的 Read API(无法查询当前密码或上次更新时间),控制面验证方式为通过 `DescribeUserQuotaPersonal` 确认账号状态正常: + +```bash +tccli tcr DescribeUserQuotaPersonal +# expected: exit 0,返回 Data.LimitInfo 配额列表(表示账号状态正常,密码修改不影响配额) +``` + +### 数据面(Docker) + +使用新密码执行 `docker login`: + +```bash +docker login ccr.ccs.tencentyun.com \ + --username TEN CENT_CLOUD_ACCOUNT_ID \ + --password 'NEW_PASSWORD' +# expected: Login Succeeded +``` + +| 维度 | 命令 | 预期 | +|------|------|------| +| 新密码生效 | `docker login ccr.ccs.tencentyun.com --username ACCOUNT_ID --password 'NEW_PASSWORD'` | `Login Succeeded` | +| 旧密码失效 | `docker login ccr.ccs.tencentyun.com --username ACCOUNT_ID --password 'OLD_PASSWORD'` | `incorrect username or password` | +| 账号状态正常 | `DescribeUserQuotaPersonal` | exit 0,返回 `Data.LimitInfo` 配额列表 | + +## 清理 + +本操作为密码更新,不产生新增资源,无需清理。 + +> 若因误操作忘记新密码,可通过控制台重新设置:登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 实例管理 → 个人版实例卡片 → **更多 > 重置登录密码**。 + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ModifyUserPasswordPersonal` 返回 `The current login account(ACCOUNT_ID) has not initialized user info in the TCR Personal`,RequestId `a7f3bfce-4760-4eb4-806d-cc5b0803d6ee` | 执行 `tccli tcr DescribeUserQuotaPersonal` 确认是否返回同样错误 | 个人版实例尚未在控制台初始化。首次使用 TCR 个人版前,必须在控制台完成初始化后才能调用 CLI API(包括 `DescribeUserQuotaPersonal` 和 `ModifyUserPasswordPersonal`) | 登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) → 实例管理 → 个人版实例卡片 → 单击"初始化密码"并按提示设置初始密码。初始化完成后 CLI 操作即可正常使用 | +| `ModifyUserPasswordPersonal` 返回 `MissingParameter` | 检查是否指定了 `--Password` 参数 | 缺少必填参数 `--Password` | 添加 `--Password 'NEW_PASSWORD'` 参数 | +| `ModifyUserPasswordPersonal` 返回 `LimitExceeded.Password` | 检查最近是否频繁修改密码 | API 有频控限制,短时间内多次修改被限频 | 等待数分钟后重试 | +| `ModifyUserPasswordPersonal` 返回 `UnauthorizedOperation` | 执行 `tccli configure list` 确认当前账号类型 | 子账号缺少 `tcr:ModifyUserPasswordPersonal` 权限 | 联系主账号授予 `QcloudTCRFullAccess` 或包含 `tcr:ModifyUserPasswordPersonal` 的自定义策略 | +| `ModifyUserPasswordPersonal` 返回 `AuthFailure` | `tccli configure list` 检查 secretId/secretKey 配置 | API 密钥无效或已过期 | 执行 `tccli configure` 重新配置有效密钥,或前往 [API 密钥管理](https://console.cloud.tencent.com/cam/capi) 确认密钥状态 | +| `ModifyUserPasswordPersonal` 返回 `InternalError` | 检查 RequestId 并记录时间点 | 服务端内部异常 | 稍后重试;若持续失败,[提交工单](https://console.cloud.tencent.com/workorder/category) 并附 RequestId | + +### 修改成功但登录异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `docker login` 用新密码返回 `incorrect username or password` | 确认用户名是否为腾讯云账号 ID(非子账号名或邮箱);确认密码不含 Shell 转义字符导致的输入偏差 | 用户名错误或密码 Shell 转义问题 | 用户名为腾讯云账号 ID([账号信息](https://console.cloud.tencent.com/developer) 查看的纯数字串);密码含 `$`、`!` 等特殊字符时用单引号包裹 | +| `docker login` 用新密码仍失败,但旧密码也失效 | 等待 30 秒后重试新密码;若仍失败,可能是修改过程中密码未正确提交 | 密码修改操作本身出现问题或 API 端点返回成功但后端未同步 | 重新执行 `ModifyUserPasswordPersonal` 设置新密码,确认 `exit 0` 和 RequestId 后,等待 10 秒再用新密码 `docker login` | +| 密码修改后已有 `docker login` 会话未中断 | 执行 `docker logout ccr.ccs.tencentyun.com` 后重新 `docker login` | 密码修改不影响已缓存的 Docker 本地凭据(此为正常行为,非故障) | 需要时主动 `docker logout` 使旧凭据失效,下次 `docker push`/`pull` 时需用新密码重新登录 | + +## 下一步 + +- [个人版快速入门](../../../quickstart/personal) — 推送/拉取镜像的完整流程 +- [设置镜像清理](../image-cleanup) — 配置镜像版本自动清理策略 +- [配置访问权限](../配置访问权限) — 管理仓库的访问控制策略 +- [个人版接入 CAM 的 API 列表](https://cloud.tencent.com/document/product/1141/41415) — 各 API 对应的 CAM 授权 Action +- [个人版常见问题](https://cloud.tencent.com/document/product/1141/39277) — 常见使用问题及解答 + +## 控制台替代 + +[容器镜像服务控制台 → 实例管理](https://console.cloud.tencent.com/tcr):选择任意地域的个人版实例 → **更多** > **重置登录密码** → 输入并确认新密码 → 单击**确定**。 diff --git a/src/content/docs/cli/tcr/practices/custom-domain-ccn-tcrctl.md b/src/content/docs/cli/tcr/practices/custom-domain-ccn-tcrctl.md new file mode 100644 index 0000000..7583f3e --- /dev/null +++ b/src/content/docs/cli/tcr/practices/custom-domain-ccn-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "" +description: "# 产品 CLI 操作" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +# 产品 CLI 操作 + +> 页型 P1 · 使用 crctl 完成本页任务 · P0 仅提供 stub 占位,P1 阶段完成正文。 + diff --git a/src/content/docs/cli/tcr/practices/custom-domain-ccn.md b/src/content/docs/cli/tcr/practices/custom-domain-ccn.md new file mode 100644 index 0000000..fc2e9bf --- /dev/null +++ b/src/content/docs/cli/tcr/practices/custom-domain-ccn.md @@ -0,0 +1,898 @@ +--- +title: "使用自定义域名及云联网实现跨地域内网访问" +description: "· page_id `76084`" +--- + +> 对照官方:[使用自定义域名及云联网实现跨地域内网访问](https://cloud.tencent.com/document/product/1141/76084) · page_id `76084` + +## 概述 + +在多地域、多 VPC 场景中,需要不同地域的私有网络同时接入同一 TCR 企业版实例,实现跨地域内网推送、拉取容器镜像。本文介绍如何使用自定义域名,配合云联网(CCN)和 Private DNS,将多个 VPC 跨地域接入 TCR 实例并通过内网分发容器镜像。 + +**方案对比:** + +| 方案 | 适用场景 | 网络路径 | 解析方式 | 复杂度 | +|------|---------|---------|---------|:--:| +| 公网域名 + CCN | 测试/开发、少量跨地域 VPC | 跨地域 VPC → CCN → TCR 所在地域 VPC → 内网端点 | 自动解析(VPC 内网端点 DNS) | 低 | +| 自定义域名 + CCN | 生产环境、多团队共享、需要自定义品牌域名 | 同上 | Private DNS A 记录指向内网 IP | 中 | + +**架构链路:** + +``` + 广州 VPC(已接入 TCR 内网端点) + │ + ├── CCN ── 上海 VPC(通过 CCN 打通) + │ + └── Private DNS(自定义域名 → 内网 IP) + │ + └── docker login <自定义域名> +``` + +涉及产品: + +| 产品 | 作用 | CLI 模块 | +|------|------|----------| +| TCR | 容器镜像存储,内网端点接入 + 自定义域名绑定 | `tccli tcr` | +| VPC / CCN | 跨地域 VPC 网络互通 | `tccli vpc` | +| SSL 证书 | 自定义域名 HTTPS 证书(上传或签发) | `tccli ssl` | +| Private DNS | VPC 内自定义域名解析 | `tccli privatedns` | + +**自定义域名前置要求:** + +| 要求 | 说明 | +|------|------| +| ICP 备案 | 境内实例绑定的域名需完成 ICP 备案,境外实例无需备案 | +| SSL 证书 | 需通过 SSL 证书服务签发或上传,证书须绑定自定义域名 | +| 内网端点 DNS 已生效 | `DescribeInternalEndpointDnsStatus` 返回 `Status: "ENABLED"` 后方可绑定自定义域名 | + +## 前置条件 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId、secretKey、region 均已配置 + +# 3. 检查 CAM 权限 — 须具备以下 Action 名 +# tcr:DescribeInstances +# tcr:ManageInternalEndpoint +# tcr:DescribeInternalEndpoints +# tcr:CreateInternalEndpointDns +# tcr:DescribeInternalEndpointDnsStatus +# tcr:DescribeInstanceCustomizedDomain +# tcr:CreateInstanceCustomizedDomain +# tcr:CreateInstanceToken +# vpc:DescribeVpcs +# vpc:DescribeSubnets +# vpc:DescribeCcns +# vpc:CreateCcn +# vpc:AttachCcnInstances +# ssl:DescribeCertificates +# ssl:UploadCertificate +# 验证:执行 DescribeInstances 确认 TCR 权限 +tccli tcr DescribeInstances --region +# expected: exit 0,返回实例列表(可为空) + +# 验证 VPC 权限 +tccli vpc DescribeVpcs --region +# expected: exit 0,返回 VPC 列表(可为空) + +# 验证 SSL 证书权限 +tccli ssl DescribeCertificates --region +# expected: exit 0,返回证书列表(可为空) +``` + +### 资源检查 + +```bash +# 4. 确认 TCR 实例存在且状态正常 +tccli tcr DescribeInstances --region --Registryids '[""]' +# expected: exit 0, TotalCount >= 1, Registries[0].Status: "Running", Registries[0].RegistryType: "premium" + +# 5. 确认目标 VPC 和子网存在 +tccli vpc DescribeVpcs --region --VpcIds '[""]' +# expected: exit 0, TotalCount >= 1 + +tccli vpc DescribeSubnets --region --SubnetIds '[""]' +# expected: exit 0, TotalCount >= 1 + +# 6. 确认自定义域名已注册(境内须已 ICP 备案) +nslookup +# expected: 域名已注册并可解析 + +# 7. 确认 SSL 证书可用(已有则跳过步骤 3 上传环节) +tccli ssl DescribeCertificates --region --CertId +# expected: exit 0,证书状态为已签发 +``` + +## 控制台与 CLI 参数映射 + +### 操作索引 + +| 控制台操作 | tccli 命令 | 模块 | 幂等 | +|-----------|-----------|------|:--:| +| 查看 TCR 实例 | `DescribeInstances` | tcr | 是 | +| 接入 VPC 内网 | `ManageInternalEndpoint` | tcr | 否 | +| 查看内网接入列表 | `DescribeInternalEndpoints` | tcr | 是 | +| 配置内网端点 DNS | `CreateInternalEndpointDns` | tcr | 否 | +| 查看内网端点 DNS 状态 | `DescribeInternalEndpointDnsStatus` | tcr | 是 | +| 查看自定义域名 | `DescribeInstanceCustomizedDomain` | tcr | 是 | +| 添加自定义域名 | `CreateInstanceCustomizedDomain` | tcr | 否 | +| 删除自定义域名 | `DeleteInstanceCustomizedDomain` | tcr | 是 | +| 上传 SSL 证书 | `UploadCertificate` | ssl | 否 | +| 查看 SSL 证书 | `DescribeCertificates` | ssl | 是 | +| 创建云联网 | `CreateCcn` | vpc | 否 | +| 关联 VPC 至云联网 | `AttachCcnInstances` | vpc | 否 | +| 查看云联网关联 | `DescribeCcnAttachedInstances` | vpc | 是 | +| 创建私有域 | `CreatePrivateZone` | privatedns | 否 | +| 创建 DNS 记录 | `CreatePrivateZoneRecord` | privatedns | 否 | + +### 关键字段说明(TCR) + +以下说明 TCR 内网接入与自定义域名相关的主要参数。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | TCR 企业版实例 ID,格式 `tcr-xxxxxxxx`。从前置条件 `DescribeInstances` 获取 | `ResourceNotFound` | +| `Operation` | String | 是 | `Create`(接入)或 `Delete`(移除),区分大小写 | `InvalidParameter` | +| `VpcId` | String | 是 | 私有网络 VPC ID,格式 `vpc-xxxxxxxx`,须与 TCR 实例同地域 | `FailedOperation` | +| `SubnetId` | String | 是 | 子网 ID,格式 `subnet-xxxxxxxx`,须属于指定 VPC | `FailedOperation` | +| `DomainName` | String | 是 | 自定义域名,如 `test.example.com`。境内实例须已完成 ICP 备案 | `InvalidParameter` | +| `CertificateId` | String | 是 | SSL 证书 ID。购买证书格式 `cert-xxxxx`,上传证书为随机字符串 | `InvalidParameter` | +| `EniLbIp` | String | 是 | 内网端点 ENI 负载均衡 IP,来自 `DescribeInternalEndpoints` 返回的 `EniLbIp`,不是 `AccessIp` | `InvalidParameter` | +| `UsePublicDomain` | Boolean | 是 | 是否使用公网域名。设为 `true` 则 VPC 内可直接使用实例公网域名解析到内网 IP | `false` 时需自行管理 DNS | +| `InstanceId` | String | 是 | `CreateInternalEndpointDns` 中的实例 ID,等同于 `RegistryId` | `ResourceNotFound` | + +### 关键字段说明(跨产品) + +| 字段 | 类型 | 必填 | 模块 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------|------| +| `CertificatePublicKey` | String | 是 | ssl | PEM 格式证书公钥,从证书文件读取 `$(cat cert.pem)` | `InvalidParameter` | +| `CertificatePrivateKey` | String | 是 | ssl | PEM 格式证书私钥,从密钥文件读取 `$(cat key.pem)` | `InvalidParameter` | +| `CertificateType` | String | 是 | ssl | 固定 `SVR`(服务器证书),TCR 自定义域名只接受服务器证书 | `InvalidParameter` | +| `CcnName` | String | 是 | vpc | 云联网名称,2-25 字符 | `InvalidParameterValue` | +| `QosLevel` | String | 是 | vpc | `AU`(金,推荐)/ `PT`(铂金,高带宽)/ `AG`(银,低带宽) | `InvalidParameterValue` | +| `Instances` | JSON | 是 | vpc | `[{"InstanceType":"VPC","InstanceId":"","InstanceRegion":""}]` | `InvalidParameterValue` | +| `RecordType` | String | 是 | privatedns | `A`(直接解析到 IP)或 `CNAME`(指向域名) | `InvalidParameter` | +| `SubDomain` | String | 是 | privatedns | `"@"` 表示主域名本身,`"sub"` 表示子域名 | `InvalidParameter` | +| `RecordValue` | String | 是 | privatedns | A 记录填 `DescribeInternalEndpoints` 返回的 `AccessIp`;CNAME 填实例默认域名 | 解析指向错误 IP 导致连接失败 | + +## 操作步骤 + +### 步骤 1:确认 TCR 实例状态 + +```bash +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region +# expected: exit 0, TotalCount >= 1, Registries[0].Status: "Running" +``` + +预期输出: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "example-registry.tencentcloudcr.com", + "RegionName": "ap-guangzhou", + "InternalEndpoint": "" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +确认 `Status` 为 `Running`,记录 `RegistryId` 和 `PublicDomain`。 + +### 步骤 2:配置内网访问(接入 VPC) + +为 TCR 实例接入 VPC,使该 VPC 内客户端可通过内网访问实例。 + +#### 选择依据 + +- **Operation**:选择 `Create` 接入新 VPC。若该 VPC 已接入,需先 `Delete` 移除旧链路再 `Create`。 +- **VpcId**:选择 TCR 实例同地域的 VPC。跨地域 VPC 需通过云联网打通后方可访问。 +- **SubnetId**:选择 IP 充裕的子网,TCR 内网接入需占用子网内 IP。 +- **实例类型**:高级版(premium)及以上支持内网接入,标准版不支持。 + +#### 执行接入 + +```bash +tccli tcr ManageInternalEndpoint \ + --RegistryId \ + --Operation Create \ + --VpcId \ + --SubnetId \ + --region +# expected: exit 0, 返回 RegimentId +``` + +预期输出: + +```json +{ + "RegistryId": "tcr-example", + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +#### 轮询确认 VPC 接入生效 + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId \ + --region +# expected: exit 0, AccessVpcSet 包含已接入 VPC +``` + +预期输出: + +```json +{ + "AccessVpcSet": [ + { + "VpcId": "vpc-example", + "SubnetId": "subnet-example", + "Status": "Running", + "AccessIp": "10.0.0.10", + "EniLbIp": "10.0.0.11" + } + ], + "TotalCount": 1, + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +验证维度: + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 状态 | `AccessVpcSet[].Status` | `Running` | +| VPC | `AccessVpcSet[].VpcId` | 与 `--VpcId` 一致 | +| 内网 IP | `AccessVpcSet[].AccessIp` | 非空,为内网地址 | +| ENI LB IP | `AccessVpcSet[].EniLbIp` | 非空 | + +记录 `AccessIp` 和 `EniLbIp`,后续步骤使用。 + +> 若 VPC 已接入 TCR 实例,再次执行 `Create` 返回 `InternalError.ErrorConflict`(`Vpc already attach to registry`),需先 `Delete` 再 `Create`。 + +### 步骤 3:配置内网端点 DNS + +VPC 接入后,需为新创建的内网端点配置 DNS 解析,使 VPC 内客户端可将实例域名解析到内网 IP。 + +#### 选择依据 + +- **UsePublicDomain**:设为 `true`,VPC 内客户端可直接使用实例公网域名(如 `example-registry.tencentcloudcr.com`)解析到内网 IP,无需额外配置 Private DNS。若后续要使用自定义域名(步骤 5),此项也需预先设为 `true`。 +- **EniLbIp**:必须使用 `DescribeInternalEndpoints` 返回的 `EniLbIp`,不是 `AccessIp`。填错会导致 DNS 状态异常。 + +#### 执行配置 + +```bash +tccli tcr CreateInternalEndpointDns \ + --InstanceId \ + --VpcId \ + --EniLbIp \ + --UsePublicDomain true \ + --region +# expected: exit 0 +``` + +预期输出: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +#### 轮询确认 DNS 生效 + +```bash +tccli tcr DescribeInternalEndpointDnsStatus \ + --VpcSet '[{"InstanceId":"","VpcId":"","EniLbIp":"","UsePublicDomain":true}]' \ + --region +# expected: exit 0, VpcSet[0].Status: "ENABLED" +``` + +预期输出: + +```json +{ + "VpcSet": [ + { + "Status": "ENABLED" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +`Status: "ENABLED"` 表示内网端点 DNS 已生效,VPC 内可通过实例域名内网访问 TCR 实例。后续绑定自定义域名(步骤 5)也需此 DNS 生效。 + +### 步骤 4:上传 SSL 证书 + +自定义域名需绑定 SSL 证书。如无可用的已签发证书,可通过 `openssl` 生成自签名证书并上传。 + +#### 选择依据 + +- **CertificateType**:固定选 `SVR`(服务器证书),TCR 自定义域名只接受服务器证书类型。 +- **自签名 vs 正式证书**:开发/测试环境用 `openssl` 生成自签名证书即可;生产环境须使用 CA 签发的正式证书。自签名证书会使 Docker 客户端提示 `x509: certificate signed by unknown authority`,需在各客户端添加 CA 信任。 +- **Repeatable**:设为 `false` 表示不允许重复上传相同证书,防止意外创建重复证书。 + +#### 4a. 生成自签名证书(开发/测试) + +一条 `openssl req` 命令生成 X.509 自签名证书和私钥,有效期 365 天: + +```bash +openssl req -x509 -newkey rsa:2048 \ + -keyout tcr-self-signed.key \ + -out tcr-self-signed.crt \ + -days 365 \ + -nodes \ + -subj "/CN=" +# expected: exit 0,生成 tcr-self-signed.key 和 tcr-self-signed.crt +``` + +| 参数 | 说明 | +|------|------| +| `-x509` | 输出自签名 X.509 证书(非 CSR 请求) | +| `-newkey rsa:2048` | 同时生成 2048 位 RSA 私钥 | +| `-keyout` | 私钥输出文件路径 | +| `-out` | 证书输出文件路径 | +| `-days 365` | 证书有效期 365 天 | +| `-nodes` | 私钥不加密码保护(No DES),便于自动化 | +| `-subj "/CN="` | 证书主题,CN 必须与自定义域名一致 | + +#### 4b. 上传证书至 SSL 证书服务 + +##### 最小配置(仅含必填字段) + +```bash +tccli ssl UploadCertificate \ + --CertificatePublicKey "$(cat tcr-self-signed.crt)" \ + --CertificatePrivateKey "$(cat tcr-self-signed.key)" \ + --CertificateType SVR \ + --Repeatable false \ + --region +# expected: exit 0, 返回 CertificateId +``` + +##### 增强配置(含别名便于识别) + +```bash +tccli ssl UploadCertificate \ + --CertificatePublicKey "$(cat tcr-self-signed.crt)" \ + --CertificatePrivateKey "$(cat tcr-self-signed.key)" \ + --CertificateType SVR \ + --Alias "<证书别名>" \ + --Repeatable false \ + --region +# expected: exit 0, 返回 CertificateId +``` + +预期输出: + +```json +{ + "CertificateId": "cert-example", + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `<证书别名>` | 证书显示名称 | 64 字符以内 | 自定义,建议含业务标识和域名 | + +记录返回的 `CertificateId`,后续步骤 5 使用。 + +### 步骤 5:配置自定义域名 + +#### 5.1 查看当前自定义域名列表 + +```bash +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId \ + --region +# expected: exit 0 +``` + +预期输出(首次查询可能为空): + +```json +{ + "DomainInfoList": [], + "TotalCount": 0, + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +#### 5.2 添加自定义域名 + +##### 选择依据 + +- **DomainName**:使用已完成 ICP 备案的域名(境内实例)或境外域名(境外实例)。域名须与 SSL 证书 CN(Common Name)一致。 +- **CertificateId**:使用步骤 4 上传证书返回的 `CertificateId`。购买证书(格式 `cert-xxxxx`)或上传证书均可,确保证书状态为已签发且绑定了目标域名。 + +##### 执行添加 + +```bash +tccli tcr CreateInstanceCustomizedDomain \ + --RegistryId \ + --DomainName \ + --CertificateId \ + --region +# expected: exit 0 +``` + +预期输出: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +#### 5.3 轮询确认域名生效 + +```bash +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId \ + --region +# expected: exit 0, DomainInfoList 包含目标域名, Status: "SUCCESS" +``` + +预期输出: + +```json +{ + "DomainInfoList": [ + { + "RegistryId": "tcr-example", + "DomainName": "test.example.com", + "CertId": "cert-example", + "Status": "SUCCESS" + } + ], + "TotalCount": 1, + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +验证维度: + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 状态 | `DomainInfoList[].Status` | `SUCCESS` | +| 域名 | `DomainInfoList[].DomainName` | 与 `--DomainName` 一致 | +| 证书绑定 | `DomainInfoList[].CertId` | 非空,与 `--CertificateId` 一致 | +| 数量 | `TotalCount` | 包含所有已绑定的自定义域名 | + +`Status: "SUCCESS"` 表示自定义域名已生效。若状态为 `CREATING`,等待 1-2 分钟后重新查询。 + +### 步骤 6:通过云联网关联多地域 VPC + +云联网(CCN)用于打通广州与上海等多个地域的 VPC,使不同地域的容器集群能通过内网跨地域互访。 + +#### 6.1 创建云联网实例 + +##### 选择依据 + +- **QosLevel**:推荐 `AU`(金),带宽保障和价格均衡。`PT`(铂金)适用高带宽生产场景,`AG`(银)适用低带宽测试场景。 +- **CcnName**:建议遵循"用途-场景"命名,便于在多个云联网中识别。 + +##### 执行创建 + +```bash +tccli vpc CreateCcn \ + --CcnName "tcr-cross-region" \ + --CcnDescription "TCR 跨地域内网访问" \ + --QosLevel AU \ + --region +# expected: exit 0, 返回 CcnId +``` + +预期输出: + +```json +{ + "Ccn": { + "CcnId": "ccn-example", + "CcnName": "tcr-cross-region", + "CcnDescription": "TCR 跨地域内网访问", + "State": "AVAILABLE", + "QosLevel": "AU" + }, + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +记录返回的 `CcnId`。 + +#### 6.2 将多地域 VPC 关联至云联网 + +##### 选择依据 + +- **Instances**:每项含 `InstanceType`(此处为 `VPC`)、`InstanceId`、`InstanceRegion`。一次可关联多个实例。 +- 关联顺序:先关联 TCR 实例所在地域(广州)的 VPC,再关联其他地域(上海)的 VPC。 + +##### 执行关联 + +```bash +# 关联 TCR 实例所在 VPC +tccli vpc AttachCcnInstances \ + --CcnId \ + --Instances '[{"InstanceType":"VPC","InstanceId":"","InstanceRegion":""}]' \ + --region +# expected: exit 0 + +# 关联上海 VPC(通过 CCN 跨地域接入) +tccli vpc AttachCcnInstances \ + --CcnId \ + --Instances '[{"InstanceType":"VPC","InstanceId":"","InstanceRegion":"ap-shanghai"}]' \ + --region +# expected: exit 0 +``` + +> 关联后 CCN 路由默认自动学习,所有关联 VPC 间将自动互通。若需限速或自定义路由策略,前往 [云联网控制台](https://console.cloud.tencent.com/vpc/ccn) 配置。 + +#### 6.3 验证云联网关联状态 + +```bash +tccli vpc DescribeCcnAttachedInstances \ + --CcnId \ + --region +# expected: exit 0, InstanceSet 包含所有已关联 VPC, State: "AVAILABLE" +``` + +验证维度: + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 关联数量 | `InstanceSet` 长度 | ≥ 2(含 TCR 侧 VPC 和对端 VPC) | +| 实例状态 | `InstanceSet[].State` | `AVAILABLE` | +| 实例地域 | `InstanceSet[].InstanceRegion` | 包含广州、上海等目标地域 | + +### 步骤 7:配置 Private DNS 私有域解析 + +业务 VPC 内需要将自定义域名解析到 TCR 实例的内网 IP(`AccessIp`),Docker 客户端才能通过自定义域名内网拉取/推送镜像。 + +#### 7.1 创建私有域 + +##### 选择依据 + +- **Domain**:填写步骤 5 中配置的自定义域名。Private DNS 私有域仅对关联的 VPC 生效,不影响公网解析。 + +```bash +tccli privatedns CreatePrivateZone \ + --Domain \ + --region +# expected: exit 0, 返回 ZoneId +``` + +预期输出: + +```json +{ + "ZoneId": "zone-example", + "Domain": "test.example.com", + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +记录返回的 `ZoneId`。 + +#### 7.2 将私有域关联至所有业务 VPC + +前往 [Private DNS 控制台](https://console.cloud.tencent.com/privatedns),选择刚创建的私有域,将广州、上海等所有业务 VPC 关联至该私有域。 + +> 此操作为控制台操作。私有域 VPC 关联也可以通过 `tccli privatedns ModifyPrivateZoneVpc` 完成,但需要完整 VPC 绑定参数集,建议直接在控制台操作。 + +#### 7.3 创建解析记录(A 记录) + +##### 选择依据 + +- **RecordType**:选择 `A` 直接解析到内网 IP,路径最短。选择 `CNAME` 指向实例默认域名可减少 IP 变更维护成本,但多一次 DNS 解析。 +- **SubDomain**:`"@"` 表示解析主域名本身。若需解析子域名(如 `registry.example.com`),填写 `registry`。 +- **RecordValue**:必须使用 `DescribeInternalEndpoints` 返回的 `AccessIp`(步骤 2),不是 `EniLbIp`。 + +##### 最小配置 + +`create-record-minimal.json`: + +```json +{ + "ZoneId": "", + "RecordType": "A", + "SubDomain": "@", + "RecordValue": "" +} +``` + +##### 增强配置 + +`create-record-enhanced.json`: + +```json +{ + "ZoneId": "", + "RecordType": "A", + "SubDomain": "@", + "RecordValue": "", + "TTL": 600, + "Weight": 10 +} +``` + +##### 执行创建 + +```bash +tccli privatedns CreatePrivateZoneRecord \ + --cli-input-json file://create-record-minimal.json \ + --region +# expected: exit 0, 返回 RecordId +``` + +> **CNAME 替代方案**:也可创建 CNAME 记录,`RecordValue` 填写实例默认域名(步骤 1 中的 `PublicDomain`)。使用 CNAME 需确保目标 VPC 已开通内网端点 DNS(步骤 3 的 `Status: "ENABLED"`),否则 CNAME 解析目标 IP 不可达。 + +## 验证 + +### 控制面(tccli) + +```bash +# 1. 验证内网访问链路 +tccli tcr DescribeInternalEndpoints \ + --RegistryId \ + --region +# expected: exit 0, AccessVpcSet 含目标 VPC, Status: "Running", AccessIp 与 EniLbIp 非空 + +# 2. 验证内网端点 DNS 状态 +tccli tcr DescribeInternalEndpointDnsStatus \ + --VpcSet '[{"InstanceId":"","VpcId":"","EniLbIp":"","UsePublicDomain":true}]' \ + --region +# expected: exit 0, VpcSet[0].Status: "ENABLED" + +# 3. 验证自定义域名绑定 +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId \ + --region +# expected: exit 0, DomainInfoList 含目标域名, Status: "SUCCESS" + +# 4. 验证云联网关联状态 +tccli vpc DescribeCcnAttachedInstances \ + --CcnId \ + --region +# expected: exit 0, InstanceSet 含所有关联 VPC, State: "AVAILABLE" +``` + +### 数据面(docker) + +#### 场景 1:验证已接入 TCR 的 VPC(广州)——自定义域名内网访问 + +在位于 TCR 实例同地域、已接入内网端点的 VPC 内云服务器上执行: + +```bash +# 获取临时访问凭证 +tccli tcr CreateInstanceToken \ + --RegistryId \ + --TokenType temp \ + --Desc "验证用临时凭证" \ + --region +# expected: exit 0, 返回 Username 和 Token + +# 使用自定义域名登录 +docker login \ + --username \ + --password +# expected: Login Succeeded + +# 拉取镜像验证(须提前推送镜像至实例) +docker pull //: +# expected: 镜像拉取成功 +``` + +#### 场景 2:验证通过 CCN 关联的其他 VPC(上海)——跨地域内网访问 + +在位于上海、已关联云联网且已关联 Private DNS 私有域的 VPC 内云服务器上执行: + +```bash +# 使用相同访问凭证登录 +docker login \ + --username \ + --password +# expected: Login Succeeded + +# 拉取镜像验证跨地域内网访问 +docker pull //: +# expected: 镜像拉取成功 +``` + +镜像拉取成功则说明云联网 + Private DNS 配置正确,上海 VPC 可通过自定义域名跨地域内网访问广州 TCR 实例。 + +## 清理 + +> **计费提示:** 以下资源按量/按月计费,清理后释放资源、停止计费。 +> +> | 资源 | 计费方式 | +> |------|---------| +> | TCR 企业版实例 | 按月(预付费),删除实例后停止计费 | +> | 云联网(CCN)实例 | 按月/按量(带宽计费),删除后停止计费 | +> | Private DNS 私有域 | 按量(按记录数/请求数),删除私有域后停止计费 | + +清理顺序:依赖倒序,先拆上层(DNS 解析、域名绑定),再拆底层(VPC 接入、CCN 关联)。 + +### 1. 清理 Private DNS 私有域解析记录及私有域 + +> **副作用警告:** 删除私有域将导致所有关联 VPC 内对该域名的解析失效,Docker 客户端将无法通过自定义域名访问 TCR 实例。需先在控制台解除所有 VPC 关联,否则删除操作失败。 + +清理前状态检查: + +```bash +tccli privatedns DescribePrivateZone \ + --ZoneId \ + --region +# expected: 返回私有域信息,确认 ZoneId 正确 +``` + +```bash +# 删除解析记录 +tccli privatedns DeletePrivateZoneRecord \ + --ZoneId \ + --RecordId \ + --region +# expected: exit 0 + +# 删除私有域(需先在控制台解除所有 VPC 关联) +tccli privatedns DeletePrivateZone \ + --ZoneId \ + --region +# expected: exit 0 +``` + +### 2. 删除自定义域名 + +> **副作用警告:** 删除自定义域名将导致所有使用该域名的 Docker 客户端无法拉取/推送镜像。删除后不可恢复,需重新 `CreateInstanceCustomizedDomain` 添加。不会删除绑定的 SSL 证书。 + +清理前状态检查: + +```bash +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId \ + --region +# expected: DomainInfoList 中包含目标域名,确认是要删除的域名 +``` + +```bash +tccli tcr DeleteInstanceCustomizedDomain \ + --RegistryId \ + --DomainName \ + --region +# expected: exit 0 +``` + +验证已删除: + +```bash +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId \ + --region +# expected: DomainInfoList 中不再包含目标域名,或 TotalCount 为 0 +``` + +### 3. 从云联网解关联 VPC + +> **副作用警告:** 解关联 VPC 将中断该 VPC 与 CCN 内其他 VPC 的网络互通。如有其他业务依赖此 CCN 链路,解关联将导致对应业务的跨地域通信中断。 + +清理前状态检查: + +```bash +tccli vpc DescribeCcnAttachedInstances \ + --CcnId \ + --region +# expected: InstanceSet 中列出所有已关联 VPC +``` + +```bash +# 解关联上海 VPC +tccli vpc DetachCcnInstances \ + --CcnId \ + --Instances '[{"InstanceType":"VPC","InstanceId":"","InstanceRegion":"ap-shanghai"}]' \ + --region +# expected: exit 0 + +# 解关联 TCR 侧 VPC +tccli vpc DetachCcnInstances \ + --CcnId \ + --Instances '[{"InstanceType":"VPC","InstanceId":"","InstanceRegion":""}]' \ + --region +# expected: exit 0 +``` + +验证已解关联: + +```bash +tccli vpc DescribeCcnAttachedInstances \ + --CcnId \ + --region +# expected: 已解关联的 VPC 不再出现在 InstanceSet 中 +``` + +### 4. 移除 VPC 内网访问 + +> **副作用警告:** 移除 VPC 内网访问将导致该 VPC 内所有容器客户端无法通过内网访问 TCR 实例。不影响已删除的自定义域名。 + +清理前状态检查: + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId \ + --region +# expected: AccessVpcSet 中包含目标 VPC,确认 VpcId 正确 +``` + +```bash +tccli tcr ManageInternalEndpoint \ + --RegistryId \ + --Operation Delete \ + --VpcId \ + --SubnetId \ + --region +# expected: exit 0 +``` + +验证已移除: + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId \ + --region +# expected: AccessVpcSet 中不再包含目标 VPC,或 AccessVpcSet 为空 +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ManageInternalEndpoint` 返回 `FailedOperation` | `tccli tcr DescribeInstances --Registryids '[""]' --region ` 确认实例状态;`tccli vpc DescribeVpcs --VpcIds '[""]' --region ` 确认 VPC 存在 | VPC 或子网不存在、不可用或与实例跨地域 | 确认 `VpcId` 和 `SubnetId` 正确,且子网位于 TCR 实例同地域 | +| `ManageInternalEndpoint` 返回 `InternalError.ErrorConflict` | `tccli tcr DescribeInternalEndpoints --RegistryId --region ` 检查已有接入 | VPC 已接入 TCR 实例(`Vpc already attach to registry`) | 先执行 `ManageInternalEndpoint --Operation Delete` 移除旧链路,再 `Create` 重建 | +| `CreateInstanceCustomizedDomain` 返回 `InvalidParameter` | `tccli ssl DescribeCertificates --CertId --region ` 确认证书 ID 及域名绑定关系 | 证书 ID 无效、证书未绑定该域名或证书状态非已签发 | 确认证书状态为已签发且域名匹配;购买证书格式 `cert-xxxxx`,上传证书为随机字符串。保留 `RequestId` 以便工单查询 | +| `UploadCertificate` 返回 `InvalidParameter` | 检查证书文件内容:`openssl x509 -in tcr-self-signed.crt -text -noout` 和 `openssl rsa -in tcr-self-signed.key -check` | PEM 格式错误或 `CertificateType` 不是 `SVR` | 确认公钥和私钥均为有效 PEM 格式,`CertificateType` 固定 `SVR` | +| `AttachCcnInstances` 返回 `ResourceNotFound` | `tccli vpc DescribeCcns --CcnIds '[""]' --region ` 确认 CCN 存在 | 云联网实例不存在或 `CcnId` 错误 | 确认 `CcnId` 正确,实例状态为 `AVAILABLE` | +| `CreateCcn` 返回 `LimitExceeded` | `tccli vpc DescribeCcns --region ` 查看 CCN 数量 | 当前账号 CCN 数量已达上限(环境限制,非命令错误) | 清理不再使用的 CCN 或使用已有 CCN 实例 | +| `Docker login` 返回 `401 Unauthorized` | `tccli tcr CreateInstanceToken --RegistryId --TokenType temp --region ` 重新创建临时凭证 | 访问凭证过期(临时凭证有效期 24 小时)或用户名/密码不匹配 | 确认 `--username` 为 `CreateInstanceToken` 返回的 `Username`,`--password` 为 `Token` | +| `Docker login` 返回 `x509: certificate signed by unknown authority` | `echo \| openssl s_client -connect :443 2>/dev/null \| openssl x509 -noout -issuer` 检查证书链 | 自签名证书不受 Docker 客户端信任 | 在 Docker 客户端添加 CA 证书:`mkdir -p /etc/docker/certs.d/` 并放入 ca.crt;或使用正式 CA 签发证书 | +| `CreateInternalEndpointDns` 返回 `InvalidParameter` | `tccli tcr DescribeInternalEndpoints --RegistryId --region ` 确认 `EniLbIp` 与返回值一致 | `EniLbIp` 或 `VpcId` 与内网端点不匹配 | 使用 `DescribeInternalEndpoints` 返回的 `EniLbIp` 值(不是 `AccessIp`),确认 `VpcId` 与创建内网端点时一致 | +| `DescribeInternalEndpointDnsStatus` 返回空 `VpcSet` | `tccli tcr DescribeInternalEndpoints --RegistryId --region ` 检查是否有已创建的内网端点 | 未创建内网端点或 `CreateInternalEndpointDns` 未执行 | 先完成 `ManageInternalEndpoint Create`(步骤 2),再执行 `CreateInternalEndpointDns`(步骤 3) | + +### 创建成功但状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 自定义域名 `Status` 长时间为 `CREATING` 或 `FAILED` | `tccli tcr DescribeInstanceCustomizedDomain --RegistryId --region ` 轮询状态 | 证书下发尚未完成或证书与域名不匹配 | 等待 1-2 分钟后重新查询;若持续非 `SUCCESS`,检查 SSL 证书是否已绑定该域名。保留 `RegistryId`、`DomainName`、`RequestId` 以备工单 | +| CCN 关联后上海 VPC 无法拉取镜像 | `tccli vpc DescribeCcnAttachedInstances --CcnId --region ` 确认两地 VPC 均关联且状态 `AVAILABLE` | CCN 路由未正确传播,或 Private DNS 私有域未关联上海 VPC | 确认两地 VPC 均关联且状态正常;前往 Private DNS 控制台确认私有域已关联上海 VPC;在上海 CVM 上执行 `nslookup ` 验证解析 | +| Private DNS 解析不生效(nslookup 返回公网 IP 或 NXDOMAIN) | 在 CVM 上执行 `nslookup ` 验证解析结果 | DNS TTL 缓存未过期,或 A 记录 IP 有误,或私有域未关联当前 VPC | 检查 A 记录值是否为 `DescribeInternalEndpoints` 返回的 `AccessIp`;等待 TTL 过期或执行 `systemctl restart systemd-resolved`(Linux)刷新 DNS 缓存 | +| 内网端点 DNS `Status` 非 `ENABLED` | `tccli tcr DescribeInternalEndpointDnsStatus --VpcSet '[{"InstanceId":"","VpcId":"","EniLbIp":"","UsePublicDomain":true}]' --region ` 查看当前状态 | DNS 下发延迟或 `CreateInternalEndpointDns` 参数有误 | 等待 1-2 分钟后重新查询;若持续非 `ENABLED`,检查 `EniLbIp`(不是 `AccessIp`)和 `VpcId` 是否与 `DescribeInternalEndpoints` 返回一致 | + +## 下一步 + +- [配置自定义域名](../../ops/access/domain/custom-domain) -- 自定义域名管理细节 +- [配置内网访问控制](../../ops/access/network/private-access) -- VPC 内网链路管理 +- [接口文档 - 内网访问](https://cloud.tencent.com/document/product/1141/41544) -- `ManageInternalEndpoint` 完整参数说明 +- [接口文档 - 自定义域名](https://cloud.tencent.com/document/product/1141/41544) -- `CreateInstanceCustomizedDomain` 完整参数说明 +- [全球多地域间同步镜像实现就近访问](../global-replication) -- 多实例同步 + 就近拉取 + +## 控制台替代 + +- [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) -- 实例管理 -> 实例详情 -> 访问控制 / 域名管理 diff --git a/src/content/docs/cli/tcr/practices/custom-domain-ccn/custom-domain-ccn-tcrctl.md b/src/content/docs/cli/tcr/practices/custom-domain-ccn/custom-domain-ccn-tcrctl.md new file mode 100644 index 0000000..7583f3e --- /dev/null +++ b/src/content/docs/cli/tcr/practices/custom-domain-ccn/custom-domain-ccn-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "" +description: "# 产品 CLI 操作" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +# 产品 CLI 操作 + +> 页型 P1 · 使用 crctl 完成本页任务 · P0 仅提供 stub 占位,P1 阶段完成正文。 + diff --git a/src/content/docs/cli/tcr/practices/custom-domain-ccn/custom-domain-ccn.md b/src/content/docs/cli/tcr/practices/custom-domain-ccn/custom-domain-ccn.md new file mode 100644 index 0000000..fc2e9bf --- /dev/null +++ b/src/content/docs/cli/tcr/practices/custom-domain-ccn/custom-domain-ccn.md @@ -0,0 +1,898 @@ +--- +title: "使用自定义域名及云联网实现跨地域内网访问" +description: "· page_id `76084`" +--- + +> 对照官方:[使用自定义域名及云联网实现跨地域内网访问](https://cloud.tencent.com/document/product/1141/76084) · page_id `76084` + +## 概述 + +在多地域、多 VPC 场景中,需要不同地域的私有网络同时接入同一 TCR 企业版实例,实现跨地域内网推送、拉取容器镜像。本文介绍如何使用自定义域名,配合云联网(CCN)和 Private DNS,将多个 VPC 跨地域接入 TCR 实例并通过内网分发容器镜像。 + +**方案对比:** + +| 方案 | 适用场景 | 网络路径 | 解析方式 | 复杂度 | +|------|---------|---------|---------|:--:| +| 公网域名 + CCN | 测试/开发、少量跨地域 VPC | 跨地域 VPC → CCN → TCR 所在地域 VPC → 内网端点 | 自动解析(VPC 内网端点 DNS) | 低 | +| 自定义域名 + CCN | 生产环境、多团队共享、需要自定义品牌域名 | 同上 | Private DNS A 记录指向内网 IP | 中 | + +**架构链路:** + +``` + 广州 VPC(已接入 TCR 内网端点) + │ + ├── CCN ── 上海 VPC(通过 CCN 打通) + │ + └── Private DNS(自定义域名 → 内网 IP) + │ + └── docker login <自定义域名> +``` + +涉及产品: + +| 产品 | 作用 | CLI 模块 | +|------|------|----------| +| TCR | 容器镜像存储,内网端点接入 + 自定义域名绑定 | `tccli tcr` | +| VPC / CCN | 跨地域 VPC 网络互通 | `tccli vpc` | +| SSL 证书 | 自定义域名 HTTPS 证书(上传或签发) | `tccli ssl` | +| Private DNS | VPC 内自定义域名解析 | `tccli privatedns` | + +**自定义域名前置要求:** + +| 要求 | 说明 | +|------|------| +| ICP 备案 | 境内实例绑定的域名需完成 ICP 备案,境外实例无需备案 | +| SSL 证书 | 需通过 SSL 证书服务签发或上传,证书须绑定自定义域名 | +| 内网端点 DNS 已生效 | `DescribeInternalEndpointDnsStatus` 返回 `Status: "ENABLED"` 后方可绑定自定义域名 | + +## 前置条件 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId、secretKey、region 均已配置 + +# 3. 检查 CAM 权限 — 须具备以下 Action 名 +# tcr:DescribeInstances +# tcr:ManageInternalEndpoint +# tcr:DescribeInternalEndpoints +# tcr:CreateInternalEndpointDns +# tcr:DescribeInternalEndpointDnsStatus +# tcr:DescribeInstanceCustomizedDomain +# tcr:CreateInstanceCustomizedDomain +# tcr:CreateInstanceToken +# vpc:DescribeVpcs +# vpc:DescribeSubnets +# vpc:DescribeCcns +# vpc:CreateCcn +# vpc:AttachCcnInstances +# ssl:DescribeCertificates +# ssl:UploadCertificate +# 验证:执行 DescribeInstances 确认 TCR 权限 +tccli tcr DescribeInstances --region +# expected: exit 0,返回实例列表(可为空) + +# 验证 VPC 权限 +tccli vpc DescribeVpcs --region +# expected: exit 0,返回 VPC 列表(可为空) + +# 验证 SSL 证书权限 +tccli ssl DescribeCertificates --region +# expected: exit 0,返回证书列表(可为空) +``` + +### 资源检查 + +```bash +# 4. 确认 TCR 实例存在且状态正常 +tccli tcr DescribeInstances --region --Registryids '[""]' +# expected: exit 0, TotalCount >= 1, Registries[0].Status: "Running", Registries[0].RegistryType: "premium" + +# 5. 确认目标 VPC 和子网存在 +tccli vpc DescribeVpcs --region --VpcIds '[""]' +# expected: exit 0, TotalCount >= 1 + +tccli vpc DescribeSubnets --region --SubnetIds '[""]' +# expected: exit 0, TotalCount >= 1 + +# 6. 确认自定义域名已注册(境内须已 ICP 备案) +nslookup +# expected: 域名已注册并可解析 + +# 7. 确认 SSL 证书可用(已有则跳过步骤 3 上传环节) +tccli ssl DescribeCertificates --region --CertId +# expected: exit 0,证书状态为已签发 +``` + +## 控制台与 CLI 参数映射 + +### 操作索引 + +| 控制台操作 | tccli 命令 | 模块 | 幂等 | +|-----------|-----------|------|:--:| +| 查看 TCR 实例 | `DescribeInstances` | tcr | 是 | +| 接入 VPC 内网 | `ManageInternalEndpoint` | tcr | 否 | +| 查看内网接入列表 | `DescribeInternalEndpoints` | tcr | 是 | +| 配置内网端点 DNS | `CreateInternalEndpointDns` | tcr | 否 | +| 查看内网端点 DNS 状态 | `DescribeInternalEndpointDnsStatus` | tcr | 是 | +| 查看自定义域名 | `DescribeInstanceCustomizedDomain` | tcr | 是 | +| 添加自定义域名 | `CreateInstanceCustomizedDomain` | tcr | 否 | +| 删除自定义域名 | `DeleteInstanceCustomizedDomain` | tcr | 是 | +| 上传 SSL 证书 | `UploadCertificate` | ssl | 否 | +| 查看 SSL 证书 | `DescribeCertificates` | ssl | 是 | +| 创建云联网 | `CreateCcn` | vpc | 否 | +| 关联 VPC 至云联网 | `AttachCcnInstances` | vpc | 否 | +| 查看云联网关联 | `DescribeCcnAttachedInstances` | vpc | 是 | +| 创建私有域 | `CreatePrivateZone` | privatedns | 否 | +| 创建 DNS 记录 | `CreatePrivateZoneRecord` | privatedns | 否 | + +### 关键字段说明(TCR) + +以下说明 TCR 内网接入与自定义域名相关的主要参数。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | TCR 企业版实例 ID,格式 `tcr-xxxxxxxx`。从前置条件 `DescribeInstances` 获取 | `ResourceNotFound` | +| `Operation` | String | 是 | `Create`(接入)或 `Delete`(移除),区分大小写 | `InvalidParameter` | +| `VpcId` | String | 是 | 私有网络 VPC ID,格式 `vpc-xxxxxxxx`,须与 TCR 实例同地域 | `FailedOperation` | +| `SubnetId` | String | 是 | 子网 ID,格式 `subnet-xxxxxxxx`,须属于指定 VPC | `FailedOperation` | +| `DomainName` | String | 是 | 自定义域名,如 `test.example.com`。境内实例须已完成 ICP 备案 | `InvalidParameter` | +| `CertificateId` | String | 是 | SSL 证书 ID。购买证书格式 `cert-xxxxx`,上传证书为随机字符串 | `InvalidParameter` | +| `EniLbIp` | String | 是 | 内网端点 ENI 负载均衡 IP,来自 `DescribeInternalEndpoints` 返回的 `EniLbIp`,不是 `AccessIp` | `InvalidParameter` | +| `UsePublicDomain` | Boolean | 是 | 是否使用公网域名。设为 `true` 则 VPC 内可直接使用实例公网域名解析到内网 IP | `false` 时需自行管理 DNS | +| `InstanceId` | String | 是 | `CreateInternalEndpointDns` 中的实例 ID,等同于 `RegistryId` | `ResourceNotFound` | + +### 关键字段说明(跨产品) + +| 字段 | 类型 | 必填 | 模块 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------|------| +| `CertificatePublicKey` | String | 是 | ssl | PEM 格式证书公钥,从证书文件读取 `$(cat cert.pem)` | `InvalidParameter` | +| `CertificatePrivateKey` | String | 是 | ssl | PEM 格式证书私钥,从密钥文件读取 `$(cat key.pem)` | `InvalidParameter` | +| `CertificateType` | String | 是 | ssl | 固定 `SVR`(服务器证书),TCR 自定义域名只接受服务器证书 | `InvalidParameter` | +| `CcnName` | String | 是 | vpc | 云联网名称,2-25 字符 | `InvalidParameterValue` | +| `QosLevel` | String | 是 | vpc | `AU`(金,推荐)/ `PT`(铂金,高带宽)/ `AG`(银,低带宽) | `InvalidParameterValue` | +| `Instances` | JSON | 是 | vpc | `[{"InstanceType":"VPC","InstanceId":"","InstanceRegion":""}]` | `InvalidParameterValue` | +| `RecordType` | String | 是 | privatedns | `A`(直接解析到 IP)或 `CNAME`(指向域名) | `InvalidParameter` | +| `SubDomain` | String | 是 | privatedns | `"@"` 表示主域名本身,`"sub"` 表示子域名 | `InvalidParameter` | +| `RecordValue` | String | 是 | privatedns | A 记录填 `DescribeInternalEndpoints` 返回的 `AccessIp`;CNAME 填实例默认域名 | 解析指向错误 IP 导致连接失败 | + +## 操作步骤 + +### 步骤 1:确认 TCR 实例状态 + +```bash +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region +# expected: exit 0, TotalCount >= 1, Registries[0].Status: "Running" +``` + +预期输出: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "example-registry.tencentcloudcr.com", + "RegionName": "ap-guangzhou", + "InternalEndpoint": "" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +确认 `Status` 为 `Running`,记录 `RegistryId` 和 `PublicDomain`。 + +### 步骤 2:配置内网访问(接入 VPC) + +为 TCR 实例接入 VPC,使该 VPC 内客户端可通过内网访问实例。 + +#### 选择依据 + +- **Operation**:选择 `Create` 接入新 VPC。若该 VPC 已接入,需先 `Delete` 移除旧链路再 `Create`。 +- **VpcId**:选择 TCR 实例同地域的 VPC。跨地域 VPC 需通过云联网打通后方可访问。 +- **SubnetId**:选择 IP 充裕的子网,TCR 内网接入需占用子网内 IP。 +- **实例类型**:高级版(premium)及以上支持内网接入,标准版不支持。 + +#### 执行接入 + +```bash +tccli tcr ManageInternalEndpoint \ + --RegistryId \ + --Operation Create \ + --VpcId \ + --SubnetId \ + --region +# expected: exit 0, 返回 RegimentId +``` + +预期输出: + +```json +{ + "RegistryId": "tcr-example", + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +#### 轮询确认 VPC 接入生效 + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId \ + --region +# expected: exit 0, AccessVpcSet 包含已接入 VPC +``` + +预期输出: + +```json +{ + "AccessVpcSet": [ + { + "VpcId": "vpc-example", + "SubnetId": "subnet-example", + "Status": "Running", + "AccessIp": "10.0.0.10", + "EniLbIp": "10.0.0.11" + } + ], + "TotalCount": 1, + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +验证维度: + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 状态 | `AccessVpcSet[].Status` | `Running` | +| VPC | `AccessVpcSet[].VpcId` | 与 `--VpcId` 一致 | +| 内网 IP | `AccessVpcSet[].AccessIp` | 非空,为内网地址 | +| ENI LB IP | `AccessVpcSet[].EniLbIp` | 非空 | + +记录 `AccessIp` 和 `EniLbIp`,后续步骤使用。 + +> 若 VPC 已接入 TCR 实例,再次执行 `Create` 返回 `InternalError.ErrorConflict`(`Vpc already attach to registry`),需先 `Delete` 再 `Create`。 + +### 步骤 3:配置内网端点 DNS + +VPC 接入后,需为新创建的内网端点配置 DNS 解析,使 VPC 内客户端可将实例域名解析到内网 IP。 + +#### 选择依据 + +- **UsePublicDomain**:设为 `true`,VPC 内客户端可直接使用实例公网域名(如 `example-registry.tencentcloudcr.com`)解析到内网 IP,无需额外配置 Private DNS。若后续要使用自定义域名(步骤 5),此项也需预先设为 `true`。 +- **EniLbIp**:必须使用 `DescribeInternalEndpoints` 返回的 `EniLbIp`,不是 `AccessIp`。填错会导致 DNS 状态异常。 + +#### 执行配置 + +```bash +tccli tcr CreateInternalEndpointDns \ + --InstanceId \ + --VpcId \ + --EniLbIp \ + --UsePublicDomain true \ + --region +# expected: exit 0 +``` + +预期输出: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +#### 轮询确认 DNS 生效 + +```bash +tccli tcr DescribeInternalEndpointDnsStatus \ + --VpcSet '[{"InstanceId":"","VpcId":"","EniLbIp":"","UsePublicDomain":true}]' \ + --region +# expected: exit 0, VpcSet[0].Status: "ENABLED" +``` + +预期输出: + +```json +{ + "VpcSet": [ + { + "Status": "ENABLED" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +`Status: "ENABLED"` 表示内网端点 DNS 已生效,VPC 内可通过实例域名内网访问 TCR 实例。后续绑定自定义域名(步骤 5)也需此 DNS 生效。 + +### 步骤 4:上传 SSL 证书 + +自定义域名需绑定 SSL 证书。如无可用的已签发证书,可通过 `openssl` 生成自签名证书并上传。 + +#### 选择依据 + +- **CertificateType**:固定选 `SVR`(服务器证书),TCR 自定义域名只接受服务器证书类型。 +- **自签名 vs 正式证书**:开发/测试环境用 `openssl` 生成自签名证书即可;生产环境须使用 CA 签发的正式证书。自签名证书会使 Docker 客户端提示 `x509: certificate signed by unknown authority`,需在各客户端添加 CA 信任。 +- **Repeatable**:设为 `false` 表示不允许重复上传相同证书,防止意外创建重复证书。 + +#### 4a. 生成自签名证书(开发/测试) + +一条 `openssl req` 命令生成 X.509 自签名证书和私钥,有效期 365 天: + +```bash +openssl req -x509 -newkey rsa:2048 \ + -keyout tcr-self-signed.key \ + -out tcr-self-signed.crt \ + -days 365 \ + -nodes \ + -subj "/CN=" +# expected: exit 0,生成 tcr-self-signed.key 和 tcr-self-signed.crt +``` + +| 参数 | 说明 | +|------|------| +| `-x509` | 输出自签名 X.509 证书(非 CSR 请求) | +| `-newkey rsa:2048` | 同时生成 2048 位 RSA 私钥 | +| `-keyout` | 私钥输出文件路径 | +| `-out` | 证书输出文件路径 | +| `-days 365` | 证书有效期 365 天 | +| `-nodes` | 私钥不加密码保护(No DES),便于自动化 | +| `-subj "/CN="` | 证书主题,CN 必须与自定义域名一致 | + +#### 4b. 上传证书至 SSL 证书服务 + +##### 最小配置(仅含必填字段) + +```bash +tccli ssl UploadCertificate \ + --CertificatePublicKey "$(cat tcr-self-signed.crt)" \ + --CertificatePrivateKey "$(cat tcr-self-signed.key)" \ + --CertificateType SVR \ + --Repeatable false \ + --region +# expected: exit 0, 返回 CertificateId +``` + +##### 增强配置(含别名便于识别) + +```bash +tccli ssl UploadCertificate \ + --CertificatePublicKey "$(cat tcr-self-signed.crt)" \ + --CertificatePrivateKey "$(cat tcr-self-signed.key)" \ + --CertificateType SVR \ + --Alias "<证书别名>" \ + --Repeatable false \ + --region +# expected: exit 0, 返回 CertificateId +``` + +预期输出: + +```json +{ + "CertificateId": "cert-example", + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `<证书别名>` | 证书显示名称 | 64 字符以内 | 自定义,建议含业务标识和域名 | + +记录返回的 `CertificateId`,后续步骤 5 使用。 + +### 步骤 5:配置自定义域名 + +#### 5.1 查看当前自定义域名列表 + +```bash +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId \ + --region +# expected: exit 0 +``` + +预期输出(首次查询可能为空): + +```json +{ + "DomainInfoList": [], + "TotalCount": 0, + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +#### 5.2 添加自定义域名 + +##### 选择依据 + +- **DomainName**:使用已完成 ICP 备案的域名(境内实例)或境外域名(境外实例)。域名须与 SSL 证书 CN(Common Name)一致。 +- **CertificateId**:使用步骤 4 上传证书返回的 `CertificateId`。购买证书(格式 `cert-xxxxx`)或上传证书均可,确保证书状态为已签发且绑定了目标域名。 + +##### 执行添加 + +```bash +tccli tcr CreateInstanceCustomizedDomain \ + --RegistryId \ + --DomainName \ + --CertificateId \ + --region +# expected: exit 0 +``` + +预期输出: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +#### 5.3 轮询确认域名生效 + +```bash +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId \ + --region +# expected: exit 0, DomainInfoList 包含目标域名, Status: "SUCCESS" +``` + +预期输出: + +```json +{ + "DomainInfoList": [ + { + "RegistryId": "tcr-example", + "DomainName": "test.example.com", + "CertId": "cert-example", + "Status": "SUCCESS" + } + ], + "TotalCount": 1, + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +验证维度: + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 状态 | `DomainInfoList[].Status` | `SUCCESS` | +| 域名 | `DomainInfoList[].DomainName` | 与 `--DomainName` 一致 | +| 证书绑定 | `DomainInfoList[].CertId` | 非空,与 `--CertificateId` 一致 | +| 数量 | `TotalCount` | 包含所有已绑定的自定义域名 | + +`Status: "SUCCESS"` 表示自定义域名已生效。若状态为 `CREATING`,等待 1-2 分钟后重新查询。 + +### 步骤 6:通过云联网关联多地域 VPC + +云联网(CCN)用于打通广州与上海等多个地域的 VPC,使不同地域的容器集群能通过内网跨地域互访。 + +#### 6.1 创建云联网实例 + +##### 选择依据 + +- **QosLevel**:推荐 `AU`(金),带宽保障和价格均衡。`PT`(铂金)适用高带宽生产场景,`AG`(银)适用低带宽测试场景。 +- **CcnName**:建议遵循"用途-场景"命名,便于在多个云联网中识别。 + +##### 执行创建 + +```bash +tccli vpc CreateCcn \ + --CcnName "tcr-cross-region" \ + --CcnDescription "TCR 跨地域内网访问" \ + --QosLevel AU \ + --region +# expected: exit 0, 返回 CcnId +``` + +预期输出: + +```json +{ + "Ccn": { + "CcnId": "ccn-example", + "CcnName": "tcr-cross-region", + "CcnDescription": "TCR 跨地域内网访问", + "State": "AVAILABLE", + "QosLevel": "AU" + }, + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +记录返回的 `CcnId`。 + +#### 6.2 将多地域 VPC 关联至云联网 + +##### 选择依据 + +- **Instances**:每项含 `InstanceType`(此处为 `VPC`)、`InstanceId`、`InstanceRegion`。一次可关联多个实例。 +- 关联顺序:先关联 TCR 实例所在地域(广州)的 VPC,再关联其他地域(上海)的 VPC。 + +##### 执行关联 + +```bash +# 关联 TCR 实例所在 VPC +tccli vpc AttachCcnInstances \ + --CcnId \ + --Instances '[{"InstanceType":"VPC","InstanceId":"","InstanceRegion":""}]' \ + --region +# expected: exit 0 + +# 关联上海 VPC(通过 CCN 跨地域接入) +tccli vpc AttachCcnInstances \ + --CcnId \ + --Instances '[{"InstanceType":"VPC","InstanceId":"","InstanceRegion":"ap-shanghai"}]' \ + --region +# expected: exit 0 +``` + +> 关联后 CCN 路由默认自动学习,所有关联 VPC 间将自动互通。若需限速或自定义路由策略,前往 [云联网控制台](https://console.cloud.tencent.com/vpc/ccn) 配置。 + +#### 6.3 验证云联网关联状态 + +```bash +tccli vpc DescribeCcnAttachedInstances \ + --CcnId \ + --region +# expected: exit 0, InstanceSet 包含所有已关联 VPC, State: "AVAILABLE" +``` + +验证维度: + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 关联数量 | `InstanceSet` 长度 | ≥ 2(含 TCR 侧 VPC 和对端 VPC) | +| 实例状态 | `InstanceSet[].State` | `AVAILABLE` | +| 实例地域 | `InstanceSet[].InstanceRegion` | 包含广州、上海等目标地域 | + +### 步骤 7:配置 Private DNS 私有域解析 + +业务 VPC 内需要将自定义域名解析到 TCR 实例的内网 IP(`AccessIp`),Docker 客户端才能通过自定义域名内网拉取/推送镜像。 + +#### 7.1 创建私有域 + +##### 选择依据 + +- **Domain**:填写步骤 5 中配置的自定义域名。Private DNS 私有域仅对关联的 VPC 生效,不影响公网解析。 + +```bash +tccli privatedns CreatePrivateZone \ + --Domain \ + --region +# expected: exit 0, 返回 ZoneId +``` + +预期输出: + +```json +{ + "ZoneId": "zone-example", + "Domain": "test.example.com", + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +记录返回的 `ZoneId`。 + +#### 7.2 将私有域关联至所有业务 VPC + +前往 [Private DNS 控制台](https://console.cloud.tencent.com/privatedns),选择刚创建的私有域,将广州、上海等所有业务 VPC 关联至该私有域。 + +> 此操作为控制台操作。私有域 VPC 关联也可以通过 `tccli privatedns ModifyPrivateZoneVpc` 完成,但需要完整 VPC 绑定参数集,建议直接在控制台操作。 + +#### 7.3 创建解析记录(A 记录) + +##### 选择依据 + +- **RecordType**:选择 `A` 直接解析到内网 IP,路径最短。选择 `CNAME` 指向实例默认域名可减少 IP 变更维护成本,但多一次 DNS 解析。 +- **SubDomain**:`"@"` 表示解析主域名本身。若需解析子域名(如 `registry.example.com`),填写 `registry`。 +- **RecordValue**:必须使用 `DescribeInternalEndpoints` 返回的 `AccessIp`(步骤 2),不是 `EniLbIp`。 + +##### 最小配置 + +`create-record-minimal.json`: + +```json +{ + "ZoneId": "", + "RecordType": "A", + "SubDomain": "@", + "RecordValue": "" +} +``` + +##### 增强配置 + +`create-record-enhanced.json`: + +```json +{ + "ZoneId": "", + "RecordType": "A", + "SubDomain": "@", + "RecordValue": "", + "TTL": 600, + "Weight": 10 +} +``` + +##### 执行创建 + +```bash +tccli privatedns CreatePrivateZoneRecord \ + --cli-input-json file://create-record-minimal.json \ + --region +# expected: exit 0, 返回 RecordId +``` + +> **CNAME 替代方案**:也可创建 CNAME 记录,`RecordValue` 填写实例默认域名(步骤 1 中的 `PublicDomain`)。使用 CNAME 需确保目标 VPC 已开通内网端点 DNS(步骤 3 的 `Status: "ENABLED"`),否则 CNAME 解析目标 IP 不可达。 + +## 验证 + +### 控制面(tccli) + +```bash +# 1. 验证内网访问链路 +tccli tcr DescribeInternalEndpoints \ + --RegistryId \ + --region +# expected: exit 0, AccessVpcSet 含目标 VPC, Status: "Running", AccessIp 与 EniLbIp 非空 + +# 2. 验证内网端点 DNS 状态 +tccli tcr DescribeInternalEndpointDnsStatus \ + --VpcSet '[{"InstanceId":"","VpcId":"","EniLbIp":"","UsePublicDomain":true}]' \ + --region +# expected: exit 0, VpcSet[0].Status: "ENABLED" + +# 3. 验证自定义域名绑定 +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId \ + --region +# expected: exit 0, DomainInfoList 含目标域名, Status: "SUCCESS" + +# 4. 验证云联网关联状态 +tccli vpc DescribeCcnAttachedInstances \ + --CcnId \ + --region +# expected: exit 0, InstanceSet 含所有关联 VPC, State: "AVAILABLE" +``` + +### 数据面(docker) + +#### 场景 1:验证已接入 TCR 的 VPC(广州)——自定义域名内网访问 + +在位于 TCR 实例同地域、已接入内网端点的 VPC 内云服务器上执行: + +```bash +# 获取临时访问凭证 +tccli tcr CreateInstanceToken \ + --RegistryId \ + --TokenType temp \ + --Desc "验证用临时凭证" \ + --region +# expected: exit 0, 返回 Username 和 Token + +# 使用自定义域名登录 +docker login \ + --username \ + --password +# expected: Login Succeeded + +# 拉取镜像验证(须提前推送镜像至实例) +docker pull //: +# expected: 镜像拉取成功 +``` + +#### 场景 2:验证通过 CCN 关联的其他 VPC(上海)——跨地域内网访问 + +在位于上海、已关联云联网且已关联 Private DNS 私有域的 VPC 内云服务器上执行: + +```bash +# 使用相同访问凭证登录 +docker login \ + --username \ + --password +# expected: Login Succeeded + +# 拉取镜像验证跨地域内网访问 +docker pull //: +# expected: 镜像拉取成功 +``` + +镜像拉取成功则说明云联网 + Private DNS 配置正确,上海 VPC 可通过自定义域名跨地域内网访问广州 TCR 实例。 + +## 清理 + +> **计费提示:** 以下资源按量/按月计费,清理后释放资源、停止计费。 +> +> | 资源 | 计费方式 | +> |------|---------| +> | TCR 企业版实例 | 按月(预付费),删除实例后停止计费 | +> | 云联网(CCN)实例 | 按月/按量(带宽计费),删除后停止计费 | +> | Private DNS 私有域 | 按量(按记录数/请求数),删除私有域后停止计费 | + +清理顺序:依赖倒序,先拆上层(DNS 解析、域名绑定),再拆底层(VPC 接入、CCN 关联)。 + +### 1. 清理 Private DNS 私有域解析记录及私有域 + +> **副作用警告:** 删除私有域将导致所有关联 VPC 内对该域名的解析失效,Docker 客户端将无法通过自定义域名访问 TCR 实例。需先在控制台解除所有 VPC 关联,否则删除操作失败。 + +清理前状态检查: + +```bash +tccli privatedns DescribePrivateZone \ + --ZoneId \ + --region +# expected: 返回私有域信息,确认 ZoneId 正确 +``` + +```bash +# 删除解析记录 +tccli privatedns DeletePrivateZoneRecord \ + --ZoneId \ + --RecordId \ + --region +# expected: exit 0 + +# 删除私有域(需先在控制台解除所有 VPC 关联) +tccli privatedns DeletePrivateZone \ + --ZoneId \ + --region +# expected: exit 0 +``` + +### 2. 删除自定义域名 + +> **副作用警告:** 删除自定义域名将导致所有使用该域名的 Docker 客户端无法拉取/推送镜像。删除后不可恢复,需重新 `CreateInstanceCustomizedDomain` 添加。不会删除绑定的 SSL 证书。 + +清理前状态检查: + +```bash +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId \ + --region +# expected: DomainInfoList 中包含目标域名,确认是要删除的域名 +``` + +```bash +tccli tcr DeleteInstanceCustomizedDomain \ + --RegistryId \ + --DomainName \ + --region +# expected: exit 0 +``` + +验证已删除: + +```bash +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId \ + --region +# expected: DomainInfoList 中不再包含目标域名,或 TotalCount 为 0 +``` + +### 3. 从云联网解关联 VPC + +> **副作用警告:** 解关联 VPC 将中断该 VPC 与 CCN 内其他 VPC 的网络互通。如有其他业务依赖此 CCN 链路,解关联将导致对应业务的跨地域通信中断。 + +清理前状态检查: + +```bash +tccli vpc DescribeCcnAttachedInstances \ + --CcnId \ + --region +# expected: InstanceSet 中列出所有已关联 VPC +``` + +```bash +# 解关联上海 VPC +tccli vpc DetachCcnInstances \ + --CcnId \ + --Instances '[{"InstanceType":"VPC","InstanceId":"","InstanceRegion":"ap-shanghai"}]' \ + --region +# expected: exit 0 + +# 解关联 TCR 侧 VPC +tccli vpc DetachCcnInstances \ + --CcnId \ + --Instances '[{"InstanceType":"VPC","InstanceId":"","InstanceRegion":""}]' \ + --region +# expected: exit 0 +``` + +验证已解关联: + +```bash +tccli vpc DescribeCcnAttachedInstances \ + --CcnId \ + --region +# expected: 已解关联的 VPC 不再出现在 InstanceSet 中 +``` + +### 4. 移除 VPC 内网访问 + +> **副作用警告:** 移除 VPC 内网访问将导致该 VPC 内所有容器客户端无法通过内网访问 TCR 实例。不影响已删除的自定义域名。 + +清理前状态检查: + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId \ + --region +# expected: AccessVpcSet 中包含目标 VPC,确认 VpcId 正确 +``` + +```bash +tccli tcr ManageInternalEndpoint \ + --RegistryId \ + --Operation Delete \ + --VpcId \ + --SubnetId \ + --region +# expected: exit 0 +``` + +验证已移除: + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId \ + --region +# expected: AccessVpcSet 中不再包含目标 VPC,或 AccessVpcSet 为空 +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ManageInternalEndpoint` 返回 `FailedOperation` | `tccli tcr DescribeInstances --Registryids '[""]' --region ` 确认实例状态;`tccli vpc DescribeVpcs --VpcIds '[""]' --region ` 确认 VPC 存在 | VPC 或子网不存在、不可用或与实例跨地域 | 确认 `VpcId` 和 `SubnetId` 正确,且子网位于 TCR 实例同地域 | +| `ManageInternalEndpoint` 返回 `InternalError.ErrorConflict` | `tccli tcr DescribeInternalEndpoints --RegistryId --region ` 检查已有接入 | VPC 已接入 TCR 实例(`Vpc already attach to registry`) | 先执行 `ManageInternalEndpoint --Operation Delete` 移除旧链路,再 `Create` 重建 | +| `CreateInstanceCustomizedDomain` 返回 `InvalidParameter` | `tccli ssl DescribeCertificates --CertId --region ` 确认证书 ID 及域名绑定关系 | 证书 ID 无效、证书未绑定该域名或证书状态非已签发 | 确认证书状态为已签发且域名匹配;购买证书格式 `cert-xxxxx`,上传证书为随机字符串。保留 `RequestId` 以便工单查询 | +| `UploadCertificate` 返回 `InvalidParameter` | 检查证书文件内容:`openssl x509 -in tcr-self-signed.crt -text -noout` 和 `openssl rsa -in tcr-self-signed.key -check` | PEM 格式错误或 `CertificateType` 不是 `SVR` | 确认公钥和私钥均为有效 PEM 格式,`CertificateType` 固定 `SVR` | +| `AttachCcnInstances` 返回 `ResourceNotFound` | `tccli vpc DescribeCcns --CcnIds '[""]' --region ` 确认 CCN 存在 | 云联网实例不存在或 `CcnId` 错误 | 确认 `CcnId` 正确,实例状态为 `AVAILABLE` | +| `CreateCcn` 返回 `LimitExceeded` | `tccli vpc DescribeCcns --region ` 查看 CCN 数量 | 当前账号 CCN 数量已达上限(环境限制,非命令错误) | 清理不再使用的 CCN 或使用已有 CCN 实例 | +| `Docker login` 返回 `401 Unauthorized` | `tccli tcr CreateInstanceToken --RegistryId --TokenType temp --region ` 重新创建临时凭证 | 访问凭证过期(临时凭证有效期 24 小时)或用户名/密码不匹配 | 确认 `--username` 为 `CreateInstanceToken` 返回的 `Username`,`--password` 为 `Token` | +| `Docker login` 返回 `x509: certificate signed by unknown authority` | `echo \| openssl s_client -connect :443 2>/dev/null \| openssl x509 -noout -issuer` 检查证书链 | 自签名证书不受 Docker 客户端信任 | 在 Docker 客户端添加 CA 证书:`mkdir -p /etc/docker/certs.d/` 并放入 ca.crt;或使用正式 CA 签发证书 | +| `CreateInternalEndpointDns` 返回 `InvalidParameter` | `tccli tcr DescribeInternalEndpoints --RegistryId --region ` 确认 `EniLbIp` 与返回值一致 | `EniLbIp` 或 `VpcId` 与内网端点不匹配 | 使用 `DescribeInternalEndpoints` 返回的 `EniLbIp` 值(不是 `AccessIp`),确认 `VpcId` 与创建内网端点时一致 | +| `DescribeInternalEndpointDnsStatus` 返回空 `VpcSet` | `tccli tcr DescribeInternalEndpoints --RegistryId --region ` 检查是否有已创建的内网端点 | 未创建内网端点或 `CreateInternalEndpointDns` 未执行 | 先完成 `ManageInternalEndpoint Create`(步骤 2),再执行 `CreateInternalEndpointDns`(步骤 3) | + +### 创建成功但状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 自定义域名 `Status` 长时间为 `CREATING` 或 `FAILED` | `tccli tcr DescribeInstanceCustomizedDomain --RegistryId --region ` 轮询状态 | 证书下发尚未完成或证书与域名不匹配 | 等待 1-2 分钟后重新查询;若持续非 `SUCCESS`,检查 SSL 证书是否已绑定该域名。保留 `RegistryId`、`DomainName`、`RequestId` 以备工单 | +| CCN 关联后上海 VPC 无法拉取镜像 | `tccli vpc DescribeCcnAttachedInstances --CcnId --region ` 确认两地 VPC 均关联且状态 `AVAILABLE` | CCN 路由未正确传播,或 Private DNS 私有域未关联上海 VPC | 确认两地 VPC 均关联且状态正常;前往 Private DNS 控制台确认私有域已关联上海 VPC;在上海 CVM 上执行 `nslookup ` 验证解析 | +| Private DNS 解析不生效(nslookup 返回公网 IP 或 NXDOMAIN) | 在 CVM 上执行 `nslookup ` 验证解析结果 | DNS TTL 缓存未过期,或 A 记录 IP 有误,或私有域未关联当前 VPC | 检查 A 记录值是否为 `DescribeInternalEndpoints` 返回的 `AccessIp`;等待 TTL 过期或执行 `systemctl restart systemd-resolved`(Linux)刷新 DNS 缓存 | +| 内网端点 DNS `Status` 非 `ENABLED` | `tccli tcr DescribeInternalEndpointDnsStatus --VpcSet '[{"InstanceId":"","VpcId":"","EniLbIp":"","UsePublicDomain":true}]' --region ` 查看当前状态 | DNS 下发延迟或 `CreateInternalEndpointDns` 参数有误 | 等待 1-2 分钟后重新查询;若持续非 `ENABLED`,检查 `EniLbIp`(不是 `AccessIp`)和 `VpcId` 是否与 `DescribeInternalEndpoints` 返回一致 | + +## 下一步 + +- [配置自定义域名](../../ops/access/domain/custom-domain) -- 自定义域名管理细节 +- [配置内网访问控制](../../ops/access/network/private-access) -- VPC 内网链路管理 +- [接口文档 - 内网访问](https://cloud.tencent.com/document/product/1141/41544) -- `ManageInternalEndpoint` 完整参数说明 +- [接口文档 - 自定义域名](https://cloud.tencent.com/document/product/1141/41544) -- `CreateInstanceCustomizedDomain` 完整参数说明 +- [全球多地域间同步镜像实现就近访问](../global-replication) -- 多实例同步 + 就近拉取 + +## 控制台替代 + +- [容器镜像服务控制台](https://console.cloud.tencent.com/tcr) -- 实例管理 -> 实例详情 -> 访问控制 / 域名管理 diff --git a/src/content/docs/cli/tcr/practices/global-replication-tcrctl.md b/src/content/docs/cli/tcr/practices/global-replication-tcrctl.md new file mode 100644 index 0000000..7583f3e --- /dev/null +++ b/src/content/docs/cli/tcr/practices/global-replication-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "" +description: "# 产品 CLI 操作" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +# 产品 CLI 操作 + +> 页型 P1 · 使用 crctl 完成本页任务 · P0 仅提供 stub 占位,P1 阶段完成正文。 + diff --git a/src/content/docs/cli/tcr/practices/global-replication.md b/src/content/docs/cli/tcr/practices/global-replication.md new file mode 100644 index 0000000..e0aa898 --- /dev/null +++ b/src/content/docs/cli/tcr/practices/global-replication.md @@ -0,0 +1,998 @@ +--- +title: "全球多地域间同步镜像实现就近访问" +description: "· page_id `61458`" +--- + +> 对照官方:[全球多地域间同步镜像实现就近访问](https://cloud.tencent.com/document/product/1141/61458) · page_id `61458` + +## 概述 + +企业将容器业务拓展至多个地域时,需要实现容器镜像的全球多地域同步与就近拉取,以提高拉取速度、降低跨地域公网流量成本。TCR 企业版提供两项互补能力: + +| 能力 | 说明 | 规格要求 | +|------|------|---------| +| **实例同步** | 多个实例间按需自动同步指定镜像,支持跨主账号 | 标准版及以上 | +| **实例复制** | 单一实例在多个地域部署只读副本,统一域名,实时流式同步 | 仅高级版 | + +**方案对比:** + +| 维度 | 实例同步 | 实例复制 | +|------|---------|---------| +| 同步范围 | 按需(基于规则精确匹配) | 全量(推送后全部复制) | +| 推送能力 | 每个实例均可推送 | 仅主实例可推送 | +| 域名 | 各实例独立域名 | 统一域名 | +| 跨主账号 | 支持 | 不支持 | +| Helm Chart 筛选 | 支持 | 全量复制 | +| 跨国 | 支持 | 不支持 | + +> **结合自定义域名:** TCR 企业版支持自定义域名,结合 DNS 服务可实现多个实例共用同一域名,增强就近访问的灵活性。参见[配置自定义域名](../../ops/access/domain/custom-domain)。 + +## 前置条件 + +- [环境准备](../../../index.md) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域 + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tcr:DescribeInstances, tcr:CreateInstance, tcr:ManageReplication +# tcr:CreateReplicationInstance, tcr:DescribeReplicationInstances +# tcr:DescribeReplicationInstanceCreateTasks +# tcr:DescribeReplicationInstanceSyncStatus +# tcr:CreateInstanceCustomizedDomain, tcr:DescribeInstanceCustomizedDomain +# ssl:DescribeCertificates, ssl:UploadCertificate +# 验证:执行 DescribeInstances 确认权限 +tccli tcr DescribeInstances --region +# expected: exit 0,返回实例列表(可为空) + +# 4. 验证 SSL 证书权限(如使用自定义域名) +tccli ssl DescribeCertificates --region +# expected: exit 0,返回证书列表(可为空) +``` + +### 资源检查 + +```bash +# 5. 确认源实例存在且状态正常(实例同步方案) +tccli tcr DescribeInstances --region --Registryids '[""]' +# expected: exit 0, Status: "Running", RegistryType: "standard" 或 "premium" + +# 6. 确认目标实例存在且状态正常(实例同步方案) +tccli tcr DescribeInstances --region --Registryids '[""]' +# expected: exit 0, Status: "Running" + +# 7. 确认主实例为高级版(实例复制方案) +tccli tcr DescribeInstances --region --Registryids '[""]' +# expected: exit 0, RegistryType: "premium", Status: "Running" +``` + +### 版本与规格选择 + +- 实例同步:源实例和目标实例均需标准版(`standard`)及以上。基础版(`basic`)不支持同步规则。 +- 实例复制:主实例必须为高级版(`premium`)。标准版需先升级:`tccli tcr ModifyInstance --RegistryId --RegistryType premium --region ` +- 实例复制不支持跨国部署,主实例与复制实例须在同一国家/区域内。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 查看 TCR 实例列表 | `DescribeInstances` | 是 | +| 创建企业版实例 | `CreateInstance` | 否 | +| 创建同步规则 | `ManageReplication` | 否(覆盖同名规则) | +| 查看同步策略列表 | `DescribeReplicationPolicies` | 是 | +| 查看同步状态 | `DescribeReplicationInstanceSyncStatus` | 是 | +| 删除同步规则 | `DeleteReplicationRule` | 是 | +| 创建复制实例 | `CreateReplicationInstance` | 否(同地域重复创建报错) | +| 查看复制实例列表 | `DescribeReplicationInstances` | 是 | +| 查看复制实例创建进度 | `DescribeReplicationInstanceCreateTasks` | 是 | +| 删除复制实例 | `DeleteReplicationInstance` | 是 | +| 查看自定义域名 | `DescribeInstanceCustomizedDomain` | 是 | +| 添加自定义域名 | `CreateInstanceCustomizedDomain` | 否(重名报错) | +| 删除自定义域名 | `DeleteInstanceCustomizedDomain` | 是 | + +## 关键字段说明 + +### `CreateInstance` + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryName` | String | 是 | 2-30 字符,小写字母/数字/连字符,以小写字母开头 | `InvalidParameterValue` | +| `RegistryType` | String | 是 | `basic`(基础版)/ `standard`(标准版)/ `premium`(高级版) | `InvalidParameterValue` | +| `RegistryChargePrepaid.Period` | Integer | 条件必填 | 1-36(月),`standard`/`premium` 时必填 | `InvalidParameterValue` | +| `RegistryChargePrepaid.RenewFlag` | Integer | 条件必填 | 0(手动续费)/ 1(自动续费)/ 2(不续费) | `InvalidParameterValue` | +| `TagSpecification` | Object | 否 | 标签列表 `[{"ResourceType":"instance","Tags":[{"Key":"k","Value":"v"}]}]` | 标签创建失败不影响实例创建 | + +### `ManageReplication` + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `SourceRegistryId` | String | 是 | 源实例 ID,由 `DescribeInstances` 返回 | 实例不存在返回 `FailedOperation` | +| `DestinationRegistryId` | String | 是 | 目标实例 ID,由 `DescribeInstances` 返回 | 实例不存在返回 `FailedOperation` | +| `Rule.Name` | String | 是 | 规则名称,同实例内不可重名 | 重名返回 `FailedOperation` | +| `Rule.DestNamespace` | String | 是 | 目标命名空间名,须在目标实例中已存在 | 命名空间不存在则同步失败(运行时) | +| `Rule.Override` | Boolean | 否 | 是否覆盖同名镜像,默认 `false` | 设为 `false` 时同名镜像不覆盖可能导致版本不一致 | +| `Rule.Filters` | Array | 否 | 同步过滤器,支持按名称、标签、资源类型筛选。省略则同步命名空间下全部资源 | 过滤器不匹配导致预期镜像未同步 | +| `Rule.Filters[].Type` | String | 否 | `name`(镜像名)/ `tag`(标签)/ `resource`(`image` / `chart`) | 错误类型值返回 `InvalidParameter` | +| `Rule.Filters[].Value` | String | 否 | 过滤值,支持通配符 `**`(多级)和 `*`(单级) | 通配符过宽可能导致大量非预期镜像被同步 | +| `Rule.Deletion` | Boolean | 否 | 是否同步删除操作,默认 `false` | 设为 `true` 时源端删除会级联删除目标端镜像 | +| `Description` | String | 否 | 规则描述,最大 100 字符 | 空值不影响创建 | +| `PeerReplicationOption` | Object | 否 | 跨主账号同步配置,同账号不填 | 跨账号同步时缺失此字段返回权限错误 | + +### `CreateReplicationInstance` + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | 主实例 ID,须为高级版(`premium`) | 非高级版返回 `UnsupportedOperation` | +| `ReplicationRegionId` | Integer | 是 | 目标地域数字 ID(如广州=1、上海=4、北京=8) | 不合法地域 ID 返回 `InvalidParameter` | +| `ReplicationRegionName` | String | 是 | 目标地域名称(如 `ap-shanghai`) | 与 ReplicationRegionId 不匹配可能导致路由异常 | +| `SyncTag` | Boolean | 否 | 是否同步 Tag 信息,默认 `true` | 对功能影响小,设为 `false` 时部分 Tag 场景受限 | + +## 操作步骤 + +### 方案一:实例同步(标准版及以上) + +多个独立的 TCR 实例之间按需同步指定镜像。适用于跨主账号、按需筛选、跨国同步等场景。 + +> 实例创建详情参见[创建企业版实例](../../ops/instances/create)。以下提供 CLI 方式快速创建,含最小和增强两种配置。 + +#### 步骤 1:创建源实例和目标实例 + +##### 选择依据 + +- **RegistryType**:实例同步至少需要标准版(`standard`)。选择 `standard` 而非 `basic`,因为标准版支持跨主账号同步规则。若仅需同账号同步且成本敏感,可使用 `basic`。 +- **Period**:选择 1 个月(`1`),最小预付费周期,降低验证成本。 +- **RenewFlag**:推荐 `0`(手动续费),避免验证期后自动扣费。生产环境可设为 `1`(自动续费)防止过期。 + +##### 最小配置 + +仅含必填字段。`create-instance-minimal.json`: + +```json +{ + "RegistryName": "", + "RegistryType": "standard", + "RegistryChargePrepaid": { + "Period": 1, + "RenewFlag": 0 + } +} +``` + +##### 增强配置 + +含可选字段。`create-instance-enhanced.json`: + +```json +{ + "RegistryName": "", + "RegistryType": "standard", + "RegistryChargePrepaid": { + "Period": 1, + "RenewFlag": 0 + }, + "TagSpecification": { + "ResourceType": "instance", + "Tags": [ + {"Key": "Project", "Value": "multi-region-sync"}, + {"Key": "Env", "Value": "poc"} + ] + } +} +``` + +##### 执行创建 + +```bash +# 在源地域创建源实例 +tccli tcr CreateInstance \ + --cli-input-json file://create-instance-minimal.json \ + --region + +# 在目标地域创建目标实例 +tccli tcr CreateInstance \ + --cli-input-json file://create-instance-minimal.json \ + --region +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `RegistryName` | 实例名称 | 2-30 字符,小写字母/数字/连字符,以小写字母开头 | 自定义 | +| `REGION` | 地域 | 如 `ap-guangzhou`、`ap-singapore` | `tccli tcr DescribeInstances` 查看已有实例地域 | + +记录返回的 `RegistryId`,以下分别记为 `` 和 ``。 + +##### 轮询确认实例就绪 + +```bash +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region +# expected: exit 0, Status: "Running", RegistryType: "standard" 或 "premium" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "", + "RegistryName": "", + "RegistryType": "standard", + "Status": "Running", + "PublicDomain": ".tencentcloudcr.com", + "RegionName": "ap-guangzhou", + "InternalEndpoint": "" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +验证维度: + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 状态 | `Status` | `Running` | +| 规格 | `RegistryType` | `standard`(与创建参数一致) | +| 域名 | `PublicDomain` | 非空(用于后续 docker 操作) | +| 内网 | `InternalEndpoint` | 非空 | + +#### 步骤 2:配置实例同步规则 + +在源实例上创建同步规则,将指定镜像同步至目标实例。 + +##### 选择依据 + +- **Rule.Override**:推荐设为 `true`,确保目标镜像与源端严格一致。设为 `false` 时若目标已有同名镜像则不覆盖,可能造成版本漂移。 +- **Rule.Filters**:建议至少添加 `resource: image` 过滤以减少非预期同步。若无特殊需求,使用 `name: **` 同步全部镜像。 +- **Rule.Deletion**:推荐默认 `false`(不同步删除)。设为 `true` 时源端删除操作会级联删除目标镜像,存在误删风险。 +- **PeerReplicationOption**:仅跨主账号时需要。跨账号同步须使用用户级账号密码(不支持服务级账号)。 + +##### 同主账号 — 最小配置 + +仅含必填字段。`sync-rule-minimal.json`: + +```json +{ + "SourceRegistryId": "", + "DestinationRegistryId": "", + "Rule": { + "Name": "", + "DestNamespace": "" + }, + "Description": "<描述信息>" +} +``` + +##### 同主账号 — 增强配置 + +含可选字段(Filters、Override、Deletion)。`sync-rule-enhanced.json`: + +```json +{ + "SourceRegistryId": "", + "DestinationRegistryId": "", + "Rule": { + "Name": "", + "DestNamespace": "", + "Override": true, + "Filters": [ + { + "Type": "name", + "Value": "production/**" + }, + { + "Type": "tag", + "Value": "release-*" + }, + { + "Type": "resource", + "Value": "image" + } + ], + "Deletion": false + }, + "Description": "<描述信息>" +} +``` + +##### 跨主账号 — 增强配置 + +当目标实例位于不同主账号下时使用。`sync-rule-cross-account.json`: + +```json +{ + "SourceRegistryId": "", + "DestinationRegistryId": "", + "Rule": { + "Name": "", + "DestNamespace": "", + "Override": false, + "Filters": [ + { + "Type": "name", + "Value": "base/**" + }, + { + "Type": "resource", + "Value": "image" + } + ] + }, + "PeerReplicationOption": { + "PeerRegistryUin": "<目标主账号UIN>", + "PeerRegistryToken": "<目标实例长期访问凭证>", + "EnablePeerReplication": true + }, + "Description": "跨主账号基础镜像共享" +} +``` + +> **注意**:跨主账号同步需使用[用户级账号](https://cloud.tencent.com/document/product/1141/41829)密码,不支持服务级账号。同步规则生命周期与目标账号最新添加规则的凭证生命周期保持一致。 + +##### 执行创建 + +```bash +tccli tcr ManageReplication \ + --cli-input-json file://sync-rule-enhanced.json \ + --region +# expected: exit 0,返回 RequestId +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `SourceRegistryId` | 源实例 ID | 由 CreateInstance 返回,格式 `tcr-xxxxxxxx` | 上一步创建实例时记录 | +| `DestRegistryId` | 目标实例 ID | 同上 | 上一步创建实例时记录 | +| `RuleName` | 同步规则名称 | 同实例内不可重名 | 自定义 | +| `DestNamespace` | 目标命名空间 | 须在目标实例中已存在 | 在目标实例中预先创建 | + +**预期输出**: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +##### 轮询 + 多维度验证 + +```bash +tccli tcr DescribeReplicationPolicies \ + --RegistryId \ + --region +# expected: exit 0, TotalCount >= 1, Enabled: true +``` + +验证维度: + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 规则存在 | `TotalCount` | >= 1 | +| 启用状态 | `ReplicationPolicyInfoList[].Enabled` | `true` | +| 名称匹配 | `ReplicationPolicyInfoList[].Name` | 与创建时指定的 RuleName 一致 | +| 过滤器 | `ReplicationPolicyInfoList[].Filters` | 与创建时指定的 Filters 一致 | + +**预期输出**: + +```json +{ + "TotalCount": 1, + "ReplicationPolicyInfoList": [ + { + "ID": 2, + "Name": "", + "Enabled": true, + "SrcResource": "/.* ap-guangzhou", + "DestResource": "/ ap-shanghai", + "Filters": [ + {"Type": "name", "Value": "production/**"}, + {"Type": "tag", "Value": "release-*"}, + {"Type": "resource", "Value": "image"} + ], + "Override": true + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +触发方式说明: +- **事件驱动**:源实例推送新镜像后自动触发同步 +- **手动触发**:前往控制台 [同步复制 → 实例同步](https://console.cloud.tencent.com/tcr/replication) 手动执行 + +#### 步骤 3:查询同步状态 + +```bash +tccli tcr DescribeReplicationInstanceSyncStatus \ + --RegistryId \ + --ReplicationRegistryId \ + --ShowReplicationLog true \ + --region +# expected: exit 0, ReplicationStatus: "Succeed" +``` + +**预期输出**: + +```json +{ + "ReplicationStatus": "Succeed", + "ReplicationLog": [ + { + "ResourceType": "image", + "SourceResource": ".tencentcloudcr.com/production/app:release-v1.0", + "DestinationResource": ".tencentcloudcr.com/production/app:release-v1.0", + "Status": "Succeed", + "StartTime": "2026-01-01T00:00:00+08:00", + "EndTime": "2026-01-01T00:01:00+08:00" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +### 方案二:实例复制(仅高级版) + +在高级版主实例上创建多地域只读副本,推送至主实例后全量自动复制到各副本,各地域就近拉取。 + +> **规格要求**:此方案仅适用于高级版(`premium`)实例。确认实例类型为 `premium` 后方可执行。 + +#### 升级至高级版(如当前实例为 basic 或 standard) + +若实例非高级版,可通过 `ModifyInstance` 升级: + +```bash +tccli tcr ModifyInstance \ + --RegistryId \ + --RegistryType premium \ + --region +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +升级后轮询确认规格变更: + +```bash +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region +# expected: exit 0, RegistryType: "premium", Status: "Running" +``` + +> 升级为实例元数据变更,通常秒级完成。若状态仍为 `standard`,等待数秒后重试。 + +#### 步骤 1:确认主实例为高级版 + +```bash +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region +# expected: exit 0, RegistryType: "premium", Status: "Running" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "", + "RegistryName": "", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": ".tencentcloudcr.com", + "RegionName": "ap-guangzhou", + "InternalEndpoint": "", + "DeletionProtection": false + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +确认 `RegistryType` 为 `"premium"`。 + +#### 步骤 2:创建复制实例 + +在目标地域创建只读副本。 + +##### 选择依据 + +- **ReplicationRegionId / ReplicationRegionName**:选择业务就近地域。国内推荐上海(4)、北京(8),海外推荐新加坡(9)、硅谷(15)。注意实例复制不支持跨国,主实例所在地域与复制地域须在同一国家/区域内。 +- **SyncTag**:推荐保持默认 `true`,确保 Tag 信息完整同步。对成本无影响。 + +##### 最小配置 + +仅含必填字段。`create-replication-minimal.json`: + +```json +{ + "RegistryId": "", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai" +} +``` + +##### 增强配置 + +含可选字段。`create-replication-enhanced.json`: + +```json +{ + "RegistryId": "", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai", + "SyncTag": true +} +``` + +常用地域 ID 参考: + +| 地域名称 | RegionId | 地域名称 | RegionId | +|---------|----------|---------|----------| +| ap-shanghai | 4 | ap-nanjing | 33 | +| ap-guangzhou | 1 | ap-beijing | 8 | +| ap-chengdu | 16 | ap-singapore | 9 | +| na-siliconvalley | 15 | na-ashburn | 22 | +| eu-frankfurt | 17 | ap-hongkong | 5 | + +##### 执行创建 + +```bash +tccli tcr CreateReplicationInstance \ + --cli-input-json file://create-replication-minimal.json \ + --region +# expected: exit 0,返回 ReplicationRegistryId +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `RegistryId` | 主实例 ID | 须为高级版(premium) | `tccli tcr DescribeInstances` | + +记录返回的 `ReplicationRegistryId`(格式如 `-4-xxxxx`),用于后续轮询和清理。 + +一次可创建多个复制实例(不同地域不互斥)。如需覆盖多个地域,重复执行上述命令,替换 `ReplicationRegionId` 和 `ReplicationRegionName`。 + +##### 轮询 + 多维度验证 + +复制实例创建是异步操作,需轮询确认完成: + +```bash +tccli tcr DescribeReplicationInstanceCreateTasks \ + --ReplicationRegistryId \ + --ReplicationRegionId 4 \ + --region +# expected: exit 0, Status: "SUCCESS" +``` + +验证维度: + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 整体状态 | `Status` | `SUCCESS` | +| 任务状态 | `TaskDetail[0].TaskStatus` | `SUCCESS` | +| 任务名称 | `TaskDetail[0].TaskName` | `CreateReplication` | +| 完成时间 | `TaskDetail[0].FinishedTime` | 非空(表示任务已结束) | + +**预期输出**: + +```json +{ + "Status": "SUCCESS", + "TaskDetail": [ + { + "TaskName": "CreateReplication", + "TaskUUID": "task-00000000-0000-0000-0000-000000000000", + "TaskStatus": "SUCCESS", + "TaskMessage": "", + "CreatedTime": "2026-01-01T00:00:00+08:00", + "FinishedTime": "2026-01-01T00:01:00+08:00" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +#### 步骤 3:确认复制实例状态 + +```bash +tccli tcr DescribeReplicationInstances \ + --RegistryId \ + --region +# expected: exit 0, ReplicationRegistries 中 Status: "Running" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "ReplicationRegistries": [ + { + "ReplicationRegistryId": "", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai", + "Status": "Running" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +`Status: "Running"` 表示复制实例已就绪,可开始使用。 + +### 自定义域名就近访问(可选) + +TCR 企业版支持自定义域名,结合 DNS 就近解析实现多实例统一入口。详见[配置自定义域名](../../ops/access/domain/custom-domain)。 + +#### 步骤 1:上传 SSL 证书 + +自定义域名需绑定 SSL 证书。若无现有证书,可通过 `tccli ssl` 上传自签名证书。 + +##### 选择依据 + +- **CertificateType**:固定选 `SVR`(服务器证书),TCR 自定义域名只接受服务器证书类型。 +- **Alias**:推荐使用有业务含义的别名,便于在证书列表(`DescribeCertificates`)中识别。 + +##### 最小配置 + +仅含必填字段。`upload-cert-minimal.json`: + +```json +{ + "CertificatePublicKey": "<证书公钥(PEM格式)>", + "CertificatePrivateKey": "<证书私钥(PEM格式)>", + "CertificateType": "SVR" +} +``` + +##### 增强配置 + +含可选字段。`upload-cert-enhanced.json`: + +```json +{ + "CertificatePublicKey": "<证书公钥(PEM格式)>", + "CertificatePrivateKey": "<证书私钥(PEM格式)>", + "CertificateType": "SVR", + "Alias": "<证书别名>" +} +``` + +##### 生成自签名证书(POC / 无正式证书场景) + +若无 CA 签发的正式证书,可用 openssl 生成自签名证书用于 POC 验证。**生产环境请使用 CA 签发的正式证书。** + +```bash +openssl req -x509 -newkey rsa:2048 -nodes \ + -keyout tcr-domain.key \ + -out tcr-domain.crt \ + -days 365 \ + -subj "/CN=" +# expected: exit 0,生成 tcr-domain.key(私钥)和 tcr-domain.crt(证书) +``` + +> 生成的证书为 PEM 格式,可直接用于 `CertificatePublicKey` 和 `CertificatePrivateKey` 参数。`-days 365` 为有效天数,可按需调整。 + +##### 执行上传 + +```bash +tccli ssl UploadCertificate \ + --cli-input-json file://upload-cert-minimal.json \ + --region +# expected: exit 0,返回 CertificateId +``` + +**预期输出**: + +```json +{ + "CertificateId": "", + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +记录返回的 `CertificateId`。 + +#### 步骤 2:添加自定义域名 + +##### 选择依据 + +- **DomainName**:使用已完成 ICP 备案的域名(境内实例)或境外域名(境外实例)。域名须与 SSL 证书中绑定的一致。 +- **CertificateId**:使用上一步上传或购买的 SSL 证书 ID,确保证书已签发且绑定了目标域名。 + +此操作仅 3 个必填参数(`RegistryId`、`DomainName`、`CertificateId`),含义显然,单层配置即可。 + +```bash +tccli tcr CreateInstanceCustomizedDomain \ + --RegistryId \ + --DomainName \ + --CertificateId \ + --region +# expected: exit 0,返回 RequestId +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `RegistryId` | TCR 实例 ID | 须为已存在的实例 | `tccli tcr DescribeInstances` | +| `your-custom-domain` | 自定义域名 | 已完成 ICP 备案(境内),域名须与 SSL 证书一致 | 自行准备 | +| `CertificateId` | SSL 证书 ID | 已签发的服务器证书 | 上一步 UploadCertificate 返回 | + +##### 轮询 + 多维度验证 + +```bash +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId \ + --region +# expected: exit 0, DomainInfoList 中 Status: "SUCCESS" +``` + +验证维度: + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 绑定状态 | `DomainInfoList[].Status` | `SUCCESS` | +| 域名匹配 | `DomainInfoList[].DomainName` | 与创建时的 DomainName 一致 | +| 证书绑定 | `DomainInfoList[].CertId` | 与创建时的 CertificateId 一致 | + +**预期输出**: + +```json +{ + "DomainInfoList": [ + { + "RegistryId": "", + "CertId": "", + "DomainName": "", + "Status": "SUCCESS" + } + ], + "TotalCount": 1, + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +`Status: "SUCCESS"` 表示自定义域名已生效。 + +> **提示**:国内站可使用 PrivateDNS 配置内网域名解析实现就近访问;国际站暂不支持 PrivateDNS,建议使用自建 DNS 服务。DNS 解析需将自定义域名的 A 记录指向 TCR 实例的内网 IP(通过 `DescribeInternalEndpoints` 获取 `AccessIp`)。因本测试环境 VPC 配额已达上限(`LimitExceeded`),内网接入和 DNS 解析配置暂无法在 CLI 中验证。此为环境限制,非命令错误。 + +## 验证 + +### 控制面(tccli)— 实例同步 + +```bash +# 验证源实例就绪 +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region +# expected: exit 0, Status: "Running", RegistryType: "standard" 或 "premium" + +# 验证目标实例就绪 +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region +# expected: exit 0, Status: "Running" + +# 验证同步规则存在且启用 +tccli tcr DescribeReplicationPolicies \ + --RegistryId \ + --region +# expected: exit 0, ReplicationPolicyInfoList 中包含目标规则,Enabled: true + +# 验证同步状态 +tccli tcr DescribeReplicationInstanceSyncStatus \ + --RegistryId \ + --ReplicationRegistryId \ + --ShowReplicationLog true \ + --region +# expected: exit 0, ReplicationStatus: "Succeed" +``` + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 源实例状态 | `Status` | `Running` | +| 源实例规格 | `RegistryType` | `standard` 或 `premium` | +| 目标实例状态 | `Status` | `Running` | +| 同步规则启用 | `Enabled` | `true` | +| 同步日志状态 | `ReplicationStatus` | `Succeed` | +| 最近同步 | `ReplicationLog[].Status` | `Succeed`(最近一次同步成功) | + +### 控制面(tccli)— 实例复制 + +```bash +# 验证主实例为高级版 +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region +# expected: exit 0, RegistryType: "premium", Status: "Running" + +# 验证复制实例已就绪 +tccli tcr DescribeReplicationInstances \ + --RegistryId \ + --region +# expected: exit 0, ReplicationRegistries 中各地域 Status: "Running" +``` + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 主实例规格 | `RegistryType` | `premium` | +| 主实例状态 | `Status` | `Running` | +| 复制实例状态 | `ReplicationRegistries[].Status` | `Running` | +| 复制实例地域 | `ReplicationRegistries[].ReplicationRegionName` | 与创建时指定一致 | + +### 控制面(tccli)— 自定义域名 + +```bash +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId \ + --region +# expected: exit 0, DomainInfoList 中包含目标域名,Status: "SUCCESS" +``` + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 绑定状态 | `DomainInfoList[].Status` | `SUCCESS` | +| 域名 | `DomainInfoList[].DomainName` | 与目标域名一致 | + +## 清理 + +> **计费警告**:TCR 企业版实例按月预付费,复制实例计入实例计费。实例同步模式下各地域实例独立计费。删除操作不退还已支付费用,请在验证完成后及时清理不再需要的资源。 + +清理顺序:从依赖方到被依赖方,即先删除同步规则和复制实例,再删除 TCR 实例。 + +### 删除同步规则(实例同步) + +> **副作用警告**:删除同步规则仅停止后续同步,不会删除已同步至目标实例的镜像。已同步的镜像需在目标实例中单独清理。 + +**清理前状态检查**: + +```bash +tccli tcr DescribeReplicationPolicies \ + --RegistryId \ + --region +# expected: exit 0,确认待删除的规则 Name 和 ID 匹配,Enabled 为 true +``` + +```bash +tccli tcr DeleteReplicationRule \ + --RegistryId \ + --RuleId \ + --region +# expected: exit 0,返回 RequestId +``` + +> `RuleId` 通过 `DescribeReplicationPolicies` 返回的 `ID` 字段获取(如 `2`)。 + +**验证已删除**: + +```bash +tccli tcr DescribeReplicationPolicies \ + --RegistryId \ + --region +# expected: TotalCount 为 0,或列表中不再包含目标规则 +``` + +### 删除复制实例(实例复制) + +逐个删除各地域的复制实例: + +> **副作用警告**:删除复制实例将移除该地域的只读副本,该地域客户端将失去内网就近拉取能力,需回退至主实例域名拉取。已拉取至本地的镜像不受影响。 + +**清理前状态检查**: + +```bash +tccli tcr DescribeReplicationInstances \ + --RegistryId \ + --region +# expected: exit 0,确认待删除的 ReplicationRegistryId 匹配,Status 为 Running +``` + +```bash +tccli tcr DeleteReplicationInstance \ + --RegistryId \ + --ReplicationRegistryId \ + --region +# expected: exit 0,返回 RequestId +``` + +**验证已删除**: + +```bash +tccli tcr DescribeReplicationInstances \ + --RegistryId \ + --region +# expected: TotalCount 为 0,或列表中不再包含目标复制实例 +``` + +### 删除自定义域名(如使用) + +> **副作用警告**:删除自定义域名后,使用该域名的 Docker 客户端将无法通过自定义域名登录和拉取镜像,需切换至 TCR 默认公网域名(`.tencentcloudcr.com`)。此操作不删除关联的 SSL 证书。 + +**清理前状态检查**: + +```bash +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId \ + --region +# expected: exit 0,确认 DomainInfoList 中包含待删除的域名,Status 为 SUCCESS +``` + +```bash +tccli tcr DeleteInstanceCustomizedDomain \ + --RegistryId \ + --DomainName \ + --region +# expected: exit 0,返回 RequestId +``` + +### 删除 TCR 实例(可选,仅确认不再使用) + +> **副作用警告**:删除实例将同时销毁实例内所有命名空间、镜像仓库及镜像数据,不可恢复。实例同步场景下,删除源实例前请先删除同步规则;删除目标实例前请确认无其他实例向其同步。 + +**清理前状态检查**: + +```bash +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region +# expected: exit 0,确认实例 RegistryType 和 RegistryName 匹配,Status 为 Running +``` + +实例可通过控制台销毁。销毁后实例将进入回收站保留一段时间,期间可恢复。 + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ManageReplication` 返回 `FailedOperation` | `tccli tcr DescribeInstances --Registryids '[""]' --region ` 和 `tccli tcr DescribeInstances --Registryids '[""]' --region ` 确认两个实例均存在且 Status 为 Running | 源实例或目标实例不存在,或同名规则已存在 | 确认两个 RegistryId 正确且实例均 Running;`tccli tcr DescribeReplicationPolicies --RegistryId --region ` 检查规则名称是否已被占用 | +| `CreateReplicationInstance` 返回 `UnsupportedOperation` | `tccli tcr DescribeInstances --Registryids '[""]' --region ` 确认 RegistryType 是否为 premium | 实例非高级版,或目标地域与主实例跨境 | 确认 RegistryType 为 `premium`;检查目标地域是否支持实例复制且与主实例同国家;若需跨境改为实例同步方案 | +| 跨主账号同步返回 `FailedOperation.EmptyCoreBody` | `tccli sts GetCallerIdentity --region ` 确认当前身份;核对 PeerRegistryToken 格式 | PeerRegistryToken 为空或凭证类型/格式错误 | 确认 PeerRegistryToken 为用户级账号密码(非服务级账号);确认凭证未过期,必要时重新生成长期访问凭证 | +| `ManageReplication` 返回 `InternalError` — `Not support slave region` | `tccli tcr DescribeInstances --Registryids '[""]' --region ` 和 `tccli tcr DescribeInstances --Registryids '[""]' --region ` 确认两个实例所在地域 | 跨地域复制规则受 API 限制,源/目标地域组合不在当前支持列表中 | 此为 API 平台限制。替代方案:同国家/区域内改用实例复制(`CreateReplicationInstance`),跨国家/区域改用实例同步方案(同主账号或跨主账号 `ManageReplication` 含 `PeerReplicationOption`) | +| `CreateInstanceCustomizedDomain` 返回 `InvalidParameter` | `tccli ssl DescribeCertificates --region ` 确认 CertificateId 存在且 Status 为已签发 | 证书 ID 无效、证书未签发或证书未绑定目标域名 | 前往 SSL 证书控制台确认证书状态为「已签发」且域名匹配;确认 CertificateId 格式正确(购买证书为 `cert-xxxxx`,上传证书为随机字符串) | +| `FailedOperation.TradeFailed` | `tccli tcr DescribeInstances --region ` 确认当前已有实例计费情况 | 创建高级版(premium)实例失败:账户余额不足 | 替代方案:先创建 basic 实例(`CreateInstance --RegistryType basic`,后付费无预付),再通过 `tccli tcr ModifyInstance --RegistryType premium` 升级;若升级也触发余额限制,需充值。此为环境限制,非命令错误 | +| `ManageInternalEndpoint` 返回 `LimitExceeded` | `tccli vpc DescribeVpcs --region ` 查看当前 VPC 配额使用情况 | 当前账号 VPC 数量已达上限 | 清理不再使用的 VPC(`tccli vpc DeleteVpc --VpcId `)后重试;或使用已有 VPC 和子网。此为环境限制,非命令错误 | + +### 创建成功但状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 实例同步状态长时间 `Failed` | `tccli tcr DescribeReplicationInstanceSyncStatus --RegistryId --ReplicationRegistryId --ShowReplicationLog true --region ` 查看失败日志详情 | 跨地域网络波动或目标实例不可达 | 确认目标实例 Status 为 Running;检查同步日志中失败资源的详细错误;仍失败则[在线咨询](https://cloud.tencent.com/online-service?from=doc_1141) | +| 复制实例创建长时间未完成 | `tccli tcr DescribeReplicationInstanceCreateTasks --ReplicationRegistryId --ReplicationRegionId 4 --region ` 轮询 TaskStatus | 后端异步复制慢,依赖镜像总量和跨地域带宽 | 等待 TaskStatus 更新为 SUCCESS;超过预期时间(通常数分钟至半小时)仍异常则[在线咨询](https://cloud.tencent.com/online-service?from=doc_1141) | +| 复制实例创建后目标地域无法拉取镜像 | `tccli tcr DescribeReplicationInstances --RegistryId --region ` 确认 Status 为 Running;`tccli tcr DescribeInternalEndpoints --RegistryId --region ` 确认内网链路 | 目标地域 VPC 未接入复制实例 | 为复制实例配置内网访问链路:`ManageInternalEndpoint --Operation Create`;参考[配置内网访问控制](../../ops/access/network/private-access) | +| 同步规则配置后镜像未触发同步 | `tccli tcr DescribeReplicationPolicies --RegistryId --region ` 确认规则 Enabled 为 true;`tccli tcr DescribeReplicationInstanceSyncStatus --RegistryId --ReplicationRegistryId --region ` 查看同步日志 | 规则触发模式为手动,或过滤器不匹配 | 检查 Filters 配置是否匹配目标镜像名称和标签;尝试推送新镜像以触发自动同步;若仍需手动,前往控制台手动触发同步 | +| 自定义域名 Status 为 `CREATING` 或 `FAILED` | `tccli tcr DescribeInstanceCustomizedDomain --RegistryId --region ` 持续轮询状态 | SSL 证书下发尚未完成,或证书与域名不匹配 | 等待 1-2 分钟后重新查询;若持续非 SUCCESS,检查 SSL 证书是否已绑定目标域名;`tccli ssl DescribeCertificates --region ` 确认证书状态 | +| 跨国同步不可用(实例复制) | `tccli tcr DescribeInstances --Registryids '[""]' --region ` 确认实例地域;对比目标地域 | 实例复制暂不支持跨国部署 | 改用实例同步(ManageReplication)实现跨地域镜像分发 | + +## 下一步 + +- [同实例多地域复制镜像](../../ops/image-distribution/cross-region-replication) — 高级版实例复制详细指南 +- [跨实例(账号)同步镜像](../../ops/image-distribution/cross-instance-sync) — TCR 实例间同步规则管理 +- [从自建 Harbor 同步镜像到 TCR 企业版](https://cloud.tencent.com/document/product/1141/44970) — Harbor 迁移至 TCR +- [混合云下的多平台镜像数据同步复制](../hybrid-cloud-sync) — 跨平台同步场景 +- [配置内网访问控制](../../ops/access/network/private-access) — VPC 内网链路管理 +- [配置自定义域名](../../ops/access/domain/custom-domain) — 统一域名管理 + +## 控制台替代 + +- **实例同步**:[容器镜像服务控制台 → 同步复制 → 实例同步](https://console.cloud.tencent.com/tcr/replication) — 创建、管理、触发同步规则 +- **实例复制**:[容器镜像服务控制台 → 同步复制 → 实例复制](https://console.cloud.tencent.com/tcr/replication) — 创建、管理复制实例 +- **自定义域名**:[容器镜像服务控制台 → 访问控制 → 域名管理](https://console.cloud.tencent.com/tcr) — 配置自定义域名 diff --git a/src/content/docs/cli/tcr/practices/global-replication/global-replication-tcrctl.md b/src/content/docs/cli/tcr/practices/global-replication/global-replication-tcrctl.md new file mode 100644 index 0000000..7583f3e --- /dev/null +++ b/src/content/docs/cli/tcr/practices/global-replication/global-replication-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "" +description: "# 产品 CLI 操作" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +# 产品 CLI 操作 + +> 页型 P1 · 使用 crctl 完成本页任务 · P0 仅提供 stub 占位,P1 阶段完成正文。 + diff --git a/src/content/docs/cli/tcr/practices/global-replication/global-replication.md b/src/content/docs/cli/tcr/practices/global-replication/global-replication.md new file mode 100644 index 0000000..e0aa898 --- /dev/null +++ b/src/content/docs/cli/tcr/practices/global-replication/global-replication.md @@ -0,0 +1,998 @@ +--- +title: "全球多地域间同步镜像实现就近访问" +description: "· page_id `61458`" +--- + +> 对照官方:[全球多地域间同步镜像实现就近访问](https://cloud.tencent.com/document/product/1141/61458) · page_id `61458` + +## 概述 + +企业将容器业务拓展至多个地域时,需要实现容器镜像的全球多地域同步与就近拉取,以提高拉取速度、降低跨地域公网流量成本。TCR 企业版提供两项互补能力: + +| 能力 | 说明 | 规格要求 | +|------|------|---------| +| **实例同步** | 多个实例间按需自动同步指定镜像,支持跨主账号 | 标准版及以上 | +| **实例复制** | 单一实例在多个地域部署只读副本,统一域名,实时流式同步 | 仅高级版 | + +**方案对比:** + +| 维度 | 实例同步 | 实例复制 | +|------|---------|---------| +| 同步范围 | 按需(基于规则精确匹配) | 全量(推送后全部复制) | +| 推送能力 | 每个实例均可推送 | 仅主实例可推送 | +| 域名 | 各实例独立域名 | 统一域名 | +| 跨主账号 | 支持 | 不支持 | +| Helm Chart 筛选 | 支持 | 全量复制 | +| 跨国 | 支持 | 不支持 | + +> **结合自定义域名:** TCR 企业版支持自定义域名,结合 DNS 服务可实现多个实例共用同一域名,增强就近访问的灵活性。参见[配置自定义域名](../../ops/access/domain/custom-domain)。 + +## 前置条件 + +- [环境准备](../../../index.md) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域 + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tcr:DescribeInstances, tcr:CreateInstance, tcr:ManageReplication +# tcr:CreateReplicationInstance, tcr:DescribeReplicationInstances +# tcr:DescribeReplicationInstanceCreateTasks +# tcr:DescribeReplicationInstanceSyncStatus +# tcr:CreateInstanceCustomizedDomain, tcr:DescribeInstanceCustomizedDomain +# ssl:DescribeCertificates, ssl:UploadCertificate +# 验证:执行 DescribeInstances 确认权限 +tccli tcr DescribeInstances --region +# expected: exit 0,返回实例列表(可为空) + +# 4. 验证 SSL 证书权限(如使用自定义域名) +tccli ssl DescribeCertificates --region +# expected: exit 0,返回证书列表(可为空) +``` + +### 资源检查 + +```bash +# 5. 确认源实例存在且状态正常(实例同步方案) +tccli tcr DescribeInstances --region --Registryids '[""]' +# expected: exit 0, Status: "Running", RegistryType: "standard" 或 "premium" + +# 6. 确认目标实例存在且状态正常(实例同步方案) +tccli tcr DescribeInstances --region --Registryids '[""]' +# expected: exit 0, Status: "Running" + +# 7. 确认主实例为高级版(实例复制方案) +tccli tcr DescribeInstances --region --Registryids '[""]' +# expected: exit 0, RegistryType: "premium", Status: "Running" +``` + +### 版本与规格选择 + +- 实例同步:源实例和目标实例均需标准版(`standard`)及以上。基础版(`basic`)不支持同步规则。 +- 实例复制:主实例必须为高级版(`premium`)。标准版需先升级:`tccli tcr ModifyInstance --RegistryId --RegistryType premium --region ` +- 实例复制不支持跨国部署,主实例与复制实例须在同一国家/区域内。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 查看 TCR 实例列表 | `DescribeInstances` | 是 | +| 创建企业版实例 | `CreateInstance` | 否 | +| 创建同步规则 | `ManageReplication` | 否(覆盖同名规则) | +| 查看同步策略列表 | `DescribeReplicationPolicies` | 是 | +| 查看同步状态 | `DescribeReplicationInstanceSyncStatus` | 是 | +| 删除同步规则 | `DeleteReplicationRule` | 是 | +| 创建复制实例 | `CreateReplicationInstance` | 否(同地域重复创建报错) | +| 查看复制实例列表 | `DescribeReplicationInstances` | 是 | +| 查看复制实例创建进度 | `DescribeReplicationInstanceCreateTasks` | 是 | +| 删除复制实例 | `DeleteReplicationInstance` | 是 | +| 查看自定义域名 | `DescribeInstanceCustomizedDomain` | 是 | +| 添加自定义域名 | `CreateInstanceCustomizedDomain` | 否(重名报错) | +| 删除自定义域名 | `DeleteInstanceCustomizedDomain` | 是 | + +## 关键字段说明 + +### `CreateInstance` + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryName` | String | 是 | 2-30 字符,小写字母/数字/连字符,以小写字母开头 | `InvalidParameterValue` | +| `RegistryType` | String | 是 | `basic`(基础版)/ `standard`(标准版)/ `premium`(高级版) | `InvalidParameterValue` | +| `RegistryChargePrepaid.Period` | Integer | 条件必填 | 1-36(月),`standard`/`premium` 时必填 | `InvalidParameterValue` | +| `RegistryChargePrepaid.RenewFlag` | Integer | 条件必填 | 0(手动续费)/ 1(自动续费)/ 2(不续费) | `InvalidParameterValue` | +| `TagSpecification` | Object | 否 | 标签列表 `[{"ResourceType":"instance","Tags":[{"Key":"k","Value":"v"}]}]` | 标签创建失败不影响实例创建 | + +### `ManageReplication` + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `SourceRegistryId` | String | 是 | 源实例 ID,由 `DescribeInstances` 返回 | 实例不存在返回 `FailedOperation` | +| `DestinationRegistryId` | String | 是 | 目标实例 ID,由 `DescribeInstances` 返回 | 实例不存在返回 `FailedOperation` | +| `Rule.Name` | String | 是 | 规则名称,同实例内不可重名 | 重名返回 `FailedOperation` | +| `Rule.DestNamespace` | String | 是 | 目标命名空间名,须在目标实例中已存在 | 命名空间不存在则同步失败(运行时) | +| `Rule.Override` | Boolean | 否 | 是否覆盖同名镜像,默认 `false` | 设为 `false` 时同名镜像不覆盖可能导致版本不一致 | +| `Rule.Filters` | Array | 否 | 同步过滤器,支持按名称、标签、资源类型筛选。省略则同步命名空间下全部资源 | 过滤器不匹配导致预期镜像未同步 | +| `Rule.Filters[].Type` | String | 否 | `name`(镜像名)/ `tag`(标签)/ `resource`(`image` / `chart`) | 错误类型值返回 `InvalidParameter` | +| `Rule.Filters[].Value` | String | 否 | 过滤值,支持通配符 `**`(多级)和 `*`(单级) | 通配符过宽可能导致大量非预期镜像被同步 | +| `Rule.Deletion` | Boolean | 否 | 是否同步删除操作,默认 `false` | 设为 `true` 时源端删除会级联删除目标端镜像 | +| `Description` | String | 否 | 规则描述,最大 100 字符 | 空值不影响创建 | +| `PeerReplicationOption` | Object | 否 | 跨主账号同步配置,同账号不填 | 跨账号同步时缺失此字段返回权限错误 | + +### `CreateReplicationInstance` + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | 主实例 ID,须为高级版(`premium`) | 非高级版返回 `UnsupportedOperation` | +| `ReplicationRegionId` | Integer | 是 | 目标地域数字 ID(如广州=1、上海=4、北京=8) | 不合法地域 ID 返回 `InvalidParameter` | +| `ReplicationRegionName` | String | 是 | 目标地域名称(如 `ap-shanghai`) | 与 ReplicationRegionId 不匹配可能导致路由异常 | +| `SyncTag` | Boolean | 否 | 是否同步 Tag 信息,默认 `true` | 对功能影响小,设为 `false` 时部分 Tag 场景受限 | + +## 操作步骤 + +### 方案一:实例同步(标准版及以上) + +多个独立的 TCR 实例之间按需同步指定镜像。适用于跨主账号、按需筛选、跨国同步等场景。 + +> 实例创建详情参见[创建企业版实例](../../ops/instances/create)。以下提供 CLI 方式快速创建,含最小和增强两种配置。 + +#### 步骤 1:创建源实例和目标实例 + +##### 选择依据 + +- **RegistryType**:实例同步至少需要标准版(`standard`)。选择 `standard` 而非 `basic`,因为标准版支持跨主账号同步规则。若仅需同账号同步且成本敏感,可使用 `basic`。 +- **Period**:选择 1 个月(`1`),最小预付费周期,降低验证成本。 +- **RenewFlag**:推荐 `0`(手动续费),避免验证期后自动扣费。生产环境可设为 `1`(自动续费)防止过期。 + +##### 最小配置 + +仅含必填字段。`create-instance-minimal.json`: + +```json +{ + "RegistryName": "", + "RegistryType": "standard", + "RegistryChargePrepaid": { + "Period": 1, + "RenewFlag": 0 + } +} +``` + +##### 增强配置 + +含可选字段。`create-instance-enhanced.json`: + +```json +{ + "RegistryName": "", + "RegistryType": "standard", + "RegistryChargePrepaid": { + "Period": 1, + "RenewFlag": 0 + }, + "TagSpecification": { + "ResourceType": "instance", + "Tags": [ + {"Key": "Project", "Value": "multi-region-sync"}, + {"Key": "Env", "Value": "poc"} + ] + } +} +``` + +##### 执行创建 + +```bash +# 在源地域创建源实例 +tccli tcr CreateInstance \ + --cli-input-json file://create-instance-minimal.json \ + --region + +# 在目标地域创建目标实例 +tccli tcr CreateInstance \ + --cli-input-json file://create-instance-minimal.json \ + --region +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `RegistryName` | 实例名称 | 2-30 字符,小写字母/数字/连字符,以小写字母开头 | 自定义 | +| `REGION` | 地域 | 如 `ap-guangzhou`、`ap-singapore` | `tccli tcr DescribeInstances` 查看已有实例地域 | + +记录返回的 `RegistryId`,以下分别记为 `` 和 ``。 + +##### 轮询确认实例就绪 + +```bash +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region +# expected: exit 0, Status: "Running", RegistryType: "standard" 或 "premium" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "", + "RegistryName": "", + "RegistryType": "standard", + "Status": "Running", + "PublicDomain": ".tencentcloudcr.com", + "RegionName": "ap-guangzhou", + "InternalEndpoint": "" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +验证维度: + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 状态 | `Status` | `Running` | +| 规格 | `RegistryType` | `standard`(与创建参数一致) | +| 域名 | `PublicDomain` | 非空(用于后续 docker 操作) | +| 内网 | `InternalEndpoint` | 非空 | + +#### 步骤 2:配置实例同步规则 + +在源实例上创建同步规则,将指定镜像同步至目标实例。 + +##### 选择依据 + +- **Rule.Override**:推荐设为 `true`,确保目标镜像与源端严格一致。设为 `false` 时若目标已有同名镜像则不覆盖,可能造成版本漂移。 +- **Rule.Filters**:建议至少添加 `resource: image` 过滤以减少非预期同步。若无特殊需求,使用 `name: **` 同步全部镜像。 +- **Rule.Deletion**:推荐默认 `false`(不同步删除)。设为 `true` 时源端删除操作会级联删除目标镜像,存在误删风险。 +- **PeerReplicationOption**:仅跨主账号时需要。跨账号同步须使用用户级账号密码(不支持服务级账号)。 + +##### 同主账号 — 最小配置 + +仅含必填字段。`sync-rule-minimal.json`: + +```json +{ + "SourceRegistryId": "", + "DestinationRegistryId": "", + "Rule": { + "Name": "", + "DestNamespace": "" + }, + "Description": "<描述信息>" +} +``` + +##### 同主账号 — 增强配置 + +含可选字段(Filters、Override、Deletion)。`sync-rule-enhanced.json`: + +```json +{ + "SourceRegistryId": "", + "DestinationRegistryId": "", + "Rule": { + "Name": "", + "DestNamespace": "", + "Override": true, + "Filters": [ + { + "Type": "name", + "Value": "production/**" + }, + { + "Type": "tag", + "Value": "release-*" + }, + { + "Type": "resource", + "Value": "image" + } + ], + "Deletion": false + }, + "Description": "<描述信息>" +} +``` + +##### 跨主账号 — 增强配置 + +当目标实例位于不同主账号下时使用。`sync-rule-cross-account.json`: + +```json +{ + "SourceRegistryId": "", + "DestinationRegistryId": "", + "Rule": { + "Name": "", + "DestNamespace": "", + "Override": false, + "Filters": [ + { + "Type": "name", + "Value": "base/**" + }, + { + "Type": "resource", + "Value": "image" + } + ] + }, + "PeerReplicationOption": { + "PeerRegistryUin": "<目标主账号UIN>", + "PeerRegistryToken": "<目标实例长期访问凭证>", + "EnablePeerReplication": true + }, + "Description": "跨主账号基础镜像共享" +} +``` + +> **注意**:跨主账号同步需使用[用户级账号](https://cloud.tencent.com/document/product/1141/41829)密码,不支持服务级账号。同步规则生命周期与目标账号最新添加规则的凭证生命周期保持一致。 + +##### 执行创建 + +```bash +tccli tcr ManageReplication \ + --cli-input-json file://sync-rule-enhanced.json \ + --region +# expected: exit 0,返回 RequestId +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `SourceRegistryId` | 源实例 ID | 由 CreateInstance 返回,格式 `tcr-xxxxxxxx` | 上一步创建实例时记录 | +| `DestRegistryId` | 目标实例 ID | 同上 | 上一步创建实例时记录 | +| `RuleName` | 同步规则名称 | 同实例内不可重名 | 自定义 | +| `DestNamespace` | 目标命名空间 | 须在目标实例中已存在 | 在目标实例中预先创建 | + +**预期输出**: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +##### 轮询 + 多维度验证 + +```bash +tccli tcr DescribeReplicationPolicies \ + --RegistryId \ + --region +# expected: exit 0, TotalCount >= 1, Enabled: true +``` + +验证维度: + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 规则存在 | `TotalCount` | >= 1 | +| 启用状态 | `ReplicationPolicyInfoList[].Enabled` | `true` | +| 名称匹配 | `ReplicationPolicyInfoList[].Name` | 与创建时指定的 RuleName 一致 | +| 过滤器 | `ReplicationPolicyInfoList[].Filters` | 与创建时指定的 Filters 一致 | + +**预期输出**: + +```json +{ + "TotalCount": 1, + "ReplicationPolicyInfoList": [ + { + "ID": 2, + "Name": "", + "Enabled": true, + "SrcResource": "/.* ap-guangzhou", + "DestResource": "/ ap-shanghai", + "Filters": [ + {"Type": "name", "Value": "production/**"}, + {"Type": "tag", "Value": "release-*"}, + {"Type": "resource", "Value": "image"} + ], + "Override": true + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +触发方式说明: +- **事件驱动**:源实例推送新镜像后自动触发同步 +- **手动触发**:前往控制台 [同步复制 → 实例同步](https://console.cloud.tencent.com/tcr/replication) 手动执行 + +#### 步骤 3:查询同步状态 + +```bash +tccli tcr DescribeReplicationInstanceSyncStatus \ + --RegistryId \ + --ReplicationRegistryId \ + --ShowReplicationLog true \ + --region +# expected: exit 0, ReplicationStatus: "Succeed" +``` + +**预期输出**: + +```json +{ + "ReplicationStatus": "Succeed", + "ReplicationLog": [ + { + "ResourceType": "image", + "SourceResource": ".tencentcloudcr.com/production/app:release-v1.0", + "DestinationResource": ".tencentcloudcr.com/production/app:release-v1.0", + "Status": "Succeed", + "StartTime": "2026-01-01T00:00:00+08:00", + "EndTime": "2026-01-01T00:01:00+08:00" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +### 方案二:实例复制(仅高级版) + +在高级版主实例上创建多地域只读副本,推送至主实例后全量自动复制到各副本,各地域就近拉取。 + +> **规格要求**:此方案仅适用于高级版(`premium`)实例。确认实例类型为 `premium` 后方可执行。 + +#### 升级至高级版(如当前实例为 basic 或 standard) + +若实例非高级版,可通过 `ModifyInstance` 升级: + +```bash +tccli tcr ModifyInstance \ + --RegistryId \ + --RegistryType premium \ + --region +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +升级后轮询确认规格变更: + +```bash +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region +# expected: exit 0, RegistryType: "premium", Status: "Running" +``` + +> 升级为实例元数据变更,通常秒级完成。若状态仍为 `standard`,等待数秒后重试。 + +#### 步骤 1:确认主实例为高级版 + +```bash +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region +# expected: exit 0, RegistryType: "premium", Status: "Running" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "", + "RegistryName": "", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": ".tencentcloudcr.com", + "RegionName": "ap-guangzhou", + "InternalEndpoint": "", + "DeletionProtection": false + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +确认 `RegistryType` 为 `"premium"`。 + +#### 步骤 2:创建复制实例 + +在目标地域创建只读副本。 + +##### 选择依据 + +- **ReplicationRegionId / ReplicationRegionName**:选择业务就近地域。国内推荐上海(4)、北京(8),海外推荐新加坡(9)、硅谷(15)。注意实例复制不支持跨国,主实例所在地域与复制地域须在同一国家/区域内。 +- **SyncTag**:推荐保持默认 `true`,确保 Tag 信息完整同步。对成本无影响。 + +##### 最小配置 + +仅含必填字段。`create-replication-minimal.json`: + +```json +{ + "RegistryId": "", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai" +} +``` + +##### 增强配置 + +含可选字段。`create-replication-enhanced.json`: + +```json +{ + "RegistryId": "", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai", + "SyncTag": true +} +``` + +常用地域 ID 参考: + +| 地域名称 | RegionId | 地域名称 | RegionId | +|---------|----------|---------|----------| +| ap-shanghai | 4 | ap-nanjing | 33 | +| ap-guangzhou | 1 | ap-beijing | 8 | +| ap-chengdu | 16 | ap-singapore | 9 | +| na-siliconvalley | 15 | na-ashburn | 22 | +| eu-frankfurt | 17 | ap-hongkong | 5 | + +##### 执行创建 + +```bash +tccli tcr CreateReplicationInstance \ + --cli-input-json file://create-replication-minimal.json \ + --region +# expected: exit 0,返回 ReplicationRegistryId +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `RegistryId` | 主实例 ID | 须为高级版(premium) | `tccli tcr DescribeInstances` | + +记录返回的 `ReplicationRegistryId`(格式如 `-4-xxxxx`),用于后续轮询和清理。 + +一次可创建多个复制实例(不同地域不互斥)。如需覆盖多个地域,重复执行上述命令,替换 `ReplicationRegionId` 和 `ReplicationRegionName`。 + +##### 轮询 + 多维度验证 + +复制实例创建是异步操作,需轮询确认完成: + +```bash +tccli tcr DescribeReplicationInstanceCreateTasks \ + --ReplicationRegistryId \ + --ReplicationRegionId 4 \ + --region +# expected: exit 0, Status: "SUCCESS" +``` + +验证维度: + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 整体状态 | `Status` | `SUCCESS` | +| 任务状态 | `TaskDetail[0].TaskStatus` | `SUCCESS` | +| 任务名称 | `TaskDetail[0].TaskName` | `CreateReplication` | +| 完成时间 | `TaskDetail[0].FinishedTime` | 非空(表示任务已结束) | + +**预期输出**: + +```json +{ + "Status": "SUCCESS", + "TaskDetail": [ + { + "TaskName": "CreateReplication", + "TaskUUID": "task-00000000-0000-0000-0000-000000000000", + "TaskStatus": "SUCCESS", + "TaskMessage": "", + "CreatedTime": "2026-01-01T00:00:00+08:00", + "FinishedTime": "2026-01-01T00:01:00+08:00" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +#### 步骤 3:确认复制实例状态 + +```bash +tccli tcr DescribeReplicationInstances \ + --RegistryId \ + --region +# expected: exit 0, ReplicationRegistries 中 Status: "Running" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "ReplicationRegistries": [ + { + "ReplicationRegistryId": "", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai", + "Status": "Running" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +`Status: "Running"` 表示复制实例已就绪,可开始使用。 + +### 自定义域名就近访问(可选) + +TCR 企业版支持自定义域名,结合 DNS 就近解析实现多实例统一入口。详见[配置自定义域名](../../ops/access/domain/custom-domain)。 + +#### 步骤 1:上传 SSL 证书 + +自定义域名需绑定 SSL 证书。若无现有证书,可通过 `tccli ssl` 上传自签名证书。 + +##### 选择依据 + +- **CertificateType**:固定选 `SVR`(服务器证书),TCR 自定义域名只接受服务器证书类型。 +- **Alias**:推荐使用有业务含义的别名,便于在证书列表(`DescribeCertificates`)中识别。 + +##### 最小配置 + +仅含必填字段。`upload-cert-minimal.json`: + +```json +{ + "CertificatePublicKey": "<证书公钥(PEM格式)>", + "CertificatePrivateKey": "<证书私钥(PEM格式)>", + "CertificateType": "SVR" +} +``` + +##### 增强配置 + +含可选字段。`upload-cert-enhanced.json`: + +```json +{ + "CertificatePublicKey": "<证书公钥(PEM格式)>", + "CertificatePrivateKey": "<证书私钥(PEM格式)>", + "CertificateType": "SVR", + "Alias": "<证书别名>" +} +``` + +##### 生成自签名证书(POC / 无正式证书场景) + +若无 CA 签发的正式证书,可用 openssl 生成自签名证书用于 POC 验证。**生产环境请使用 CA 签发的正式证书。** + +```bash +openssl req -x509 -newkey rsa:2048 -nodes \ + -keyout tcr-domain.key \ + -out tcr-domain.crt \ + -days 365 \ + -subj "/CN=" +# expected: exit 0,生成 tcr-domain.key(私钥)和 tcr-domain.crt(证书) +``` + +> 生成的证书为 PEM 格式,可直接用于 `CertificatePublicKey` 和 `CertificatePrivateKey` 参数。`-days 365` 为有效天数,可按需调整。 + +##### 执行上传 + +```bash +tccli ssl UploadCertificate \ + --cli-input-json file://upload-cert-minimal.json \ + --region +# expected: exit 0,返回 CertificateId +``` + +**预期输出**: + +```json +{ + "CertificateId": "", + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +记录返回的 `CertificateId`。 + +#### 步骤 2:添加自定义域名 + +##### 选择依据 + +- **DomainName**:使用已完成 ICP 备案的域名(境内实例)或境外域名(境外实例)。域名须与 SSL 证书中绑定的一致。 +- **CertificateId**:使用上一步上传或购买的 SSL 证书 ID,确保证书已签发且绑定了目标域名。 + +此操作仅 3 个必填参数(`RegistryId`、`DomainName`、`CertificateId`),含义显然,单层配置即可。 + +```bash +tccli tcr CreateInstanceCustomizedDomain \ + --RegistryId \ + --DomainName \ + --CertificateId \ + --region +# expected: exit 0,返回 RequestId +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `RegistryId` | TCR 实例 ID | 须为已存在的实例 | `tccli tcr DescribeInstances` | +| `your-custom-domain` | 自定义域名 | 已完成 ICP 备案(境内),域名须与 SSL 证书一致 | 自行准备 | +| `CertificateId` | SSL 证书 ID | 已签发的服务器证书 | 上一步 UploadCertificate 返回 | + +##### 轮询 + 多维度验证 + +```bash +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId \ + --region +# expected: exit 0, DomainInfoList 中 Status: "SUCCESS" +``` + +验证维度: + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 绑定状态 | `DomainInfoList[].Status` | `SUCCESS` | +| 域名匹配 | `DomainInfoList[].DomainName` | 与创建时的 DomainName 一致 | +| 证书绑定 | `DomainInfoList[].CertId` | 与创建时的 CertificateId 一致 | + +**预期输出**: + +```json +{ + "DomainInfoList": [ + { + "RegistryId": "", + "CertId": "", + "DomainName": "", + "Status": "SUCCESS" + } + ], + "TotalCount": 1, + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +`Status: "SUCCESS"` 表示自定义域名已生效。 + +> **提示**:国内站可使用 PrivateDNS 配置内网域名解析实现就近访问;国际站暂不支持 PrivateDNS,建议使用自建 DNS 服务。DNS 解析需将自定义域名的 A 记录指向 TCR 实例的内网 IP(通过 `DescribeInternalEndpoints` 获取 `AccessIp`)。因本测试环境 VPC 配额已达上限(`LimitExceeded`),内网接入和 DNS 解析配置暂无法在 CLI 中验证。此为环境限制,非命令错误。 + +## 验证 + +### 控制面(tccli)— 实例同步 + +```bash +# 验证源实例就绪 +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region +# expected: exit 0, Status: "Running", RegistryType: "standard" 或 "premium" + +# 验证目标实例就绪 +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region +# expected: exit 0, Status: "Running" + +# 验证同步规则存在且启用 +tccli tcr DescribeReplicationPolicies \ + --RegistryId \ + --region +# expected: exit 0, ReplicationPolicyInfoList 中包含目标规则,Enabled: true + +# 验证同步状态 +tccli tcr DescribeReplicationInstanceSyncStatus \ + --RegistryId \ + --ReplicationRegistryId \ + --ShowReplicationLog true \ + --region +# expected: exit 0, ReplicationStatus: "Succeed" +``` + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 源实例状态 | `Status` | `Running` | +| 源实例规格 | `RegistryType` | `standard` 或 `premium` | +| 目标实例状态 | `Status` | `Running` | +| 同步规则启用 | `Enabled` | `true` | +| 同步日志状态 | `ReplicationStatus` | `Succeed` | +| 最近同步 | `ReplicationLog[].Status` | `Succeed`(最近一次同步成功) | + +### 控制面(tccli)— 实例复制 + +```bash +# 验证主实例为高级版 +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region +# expected: exit 0, RegistryType: "premium", Status: "Running" + +# 验证复制实例已就绪 +tccli tcr DescribeReplicationInstances \ + --RegistryId \ + --region +# expected: exit 0, ReplicationRegistries 中各地域 Status: "Running" +``` + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 主实例规格 | `RegistryType` | `premium` | +| 主实例状态 | `Status` | `Running` | +| 复制实例状态 | `ReplicationRegistries[].Status` | `Running` | +| 复制实例地域 | `ReplicationRegistries[].ReplicationRegionName` | 与创建时指定一致 | + +### 控制面(tccli)— 自定义域名 + +```bash +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId \ + --region +# expected: exit 0, DomainInfoList 中包含目标域名,Status: "SUCCESS" +``` + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 绑定状态 | `DomainInfoList[].Status` | `SUCCESS` | +| 域名 | `DomainInfoList[].DomainName` | 与目标域名一致 | + +## 清理 + +> **计费警告**:TCR 企业版实例按月预付费,复制实例计入实例计费。实例同步模式下各地域实例独立计费。删除操作不退还已支付费用,请在验证完成后及时清理不再需要的资源。 + +清理顺序:从依赖方到被依赖方,即先删除同步规则和复制实例,再删除 TCR 实例。 + +### 删除同步规则(实例同步) + +> **副作用警告**:删除同步规则仅停止后续同步,不会删除已同步至目标实例的镜像。已同步的镜像需在目标实例中单独清理。 + +**清理前状态检查**: + +```bash +tccli tcr DescribeReplicationPolicies \ + --RegistryId \ + --region +# expected: exit 0,确认待删除的规则 Name 和 ID 匹配,Enabled 为 true +``` + +```bash +tccli tcr DeleteReplicationRule \ + --RegistryId \ + --RuleId \ + --region +# expected: exit 0,返回 RequestId +``` + +> `RuleId` 通过 `DescribeReplicationPolicies` 返回的 `ID` 字段获取(如 `2`)。 + +**验证已删除**: + +```bash +tccli tcr DescribeReplicationPolicies \ + --RegistryId \ + --region +# expected: TotalCount 为 0,或列表中不再包含目标规则 +``` + +### 删除复制实例(实例复制) + +逐个删除各地域的复制实例: + +> **副作用警告**:删除复制实例将移除该地域的只读副本,该地域客户端将失去内网就近拉取能力,需回退至主实例域名拉取。已拉取至本地的镜像不受影响。 + +**清理前状态检查**: + +```bash +tccli tcr DescribeReplicationInstances \ + --RegistryId \ + --region +# expected: exit 0,确认待删除的 ReplicationRegistryId 匹配,Status 为 Running +``` + +```bash +tccli tcr DeleteReplicationInstance \ + --RegistryId \ + --ReplicationRegistryId \ + --region +# expected: exit 0,返回 RequestId +``` + +**验证已删除**: + +```bash +tccli tcr DescribeReplicationInstances \ + --RegistryId \ + --region +# expected: TotalCount 为 0,或列表中不再包含目标复制实例 +``` + +### 删除自定义域名(如使用) + +> **副作用警告**:删除自定义域名后,使用该域名的 Docker 客户端将无法通过自定义域名登录和拉取镜像,需切换至 TCR 默认公网域名(`.tencentcloudcr.com`)。此操作不删除关联的 SSL 证书。 + +**清理前状态检查**: + +```bash +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId \ + --region +# expected: exit 0,确认 DomainInfoList 中包含待删除的域名,Status 为 SUCCESS +``` + +```bash +tccli tcr DeleteInstanceCustomizedDomain \ + --RegistryId \ + --DomainName \ + --region +# expected: exit 0,返回 RequestId +``` + +### 删除 TCR 实例(可选,仅确认不再使用) + +> **副作用警告**:删除实例将同时销毁实例内所有命名空间、镜像仓库及镜像数据,不可恢复。实例同步场景下,删除源实例前请先删除同步规则;删除目标实例前请确认无其他实例向其同步。 + +**清理前状态检查**: + +```bash +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region +# expected: exit 0,确认实例 RegistryType 和 RegistryName 匹配,Status 为 Running +``` + +实例可通过控制台销毁。销毁后实例将进入回收站保留一段时间,期间可恢复。 + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ManageReplication` 返回 `FailedOperation` | `tccli tcr DescribeInstances --Registryids '[""]' --region ` 和 `tccli tcr DescribeInstances --Registryids '[""]' --region ` 确认两个实例均存在且 Status 为 Running | 源实例或目标实例不存在,或同名规则已存在 | 确认两个 RegistryId 正确且实例均 Running;`tccli tcr DescribeReplicationPolicies --RegistryId --region ` 检查规则名称是否已被占用 | +| `CreateReplicationInstance` 返回 `UnsupportedOperation` | `tccli tcr DescribeInstances --Registryids '[""]' --region ` 确认 RegistryType 是否为 premium | 实例非高级版,或目标地域与主实例跨境 | 确认 RegistryType 为 `premium`;检查目标地域是否支持实例复制且与主实例同国家;若需跨境改为实例同步方案 | +| 跨主账号同步返回 `FailedOperation.EmptyCoreBody` | `tccli sts GetCallerIdentity --region ` 确认当前身份;核对 PeerRegistryToken 格式 | PeerRegistryToken 为空或凭证类型/格式错误 | 确认 PeerRegistryToken 为用户级账号密码(非服务级账号);确认凭证未过期,必要时重新生成长期访问凭证 | +| `ManageReplication` 返回 `InternalError` — `Not support slave region` | `tccli tcr DescribeInstances --Registryids '[""]' --region ` 和 `tccli tcr DescribeInstances --Registryids '[""]' --region ` 确认两个实例所在地域 | 跨地域复制规则受 API 限制,源/目标地域组合不在当前支持列表中 | 此为 API 平台限制。替代方案:同国家/区域内改用实例复制(`CreateReplicationInstance`),跨国家/区域改用实例同步方案(同主账号或跨主账号 `ManageReplication` 含 `PeerReplicationOption`) | +| `CreateInstanceCustomizedDomain` 返回 `InvalidParameter` | `tccli ssl DescribeCertificates --region ` 确认 CertificateId 存在且 Status 为已签发 | 证书 ID 无效、证书未签发或证书未绑定目标域名 | 前往 SSL 证书控制台确认证书状态为「已签发」且域名匹配;确认 CertificateId 格式正确(购买证书为 `cert-xxxxx`,上传证书为随机字符串) | +| `FailedOperation.TradeFailed` | `tccli tcr DescribeInstances --region ` 确认当前已有实例计费情况 | 创建高级版(premium)实例失败:账户余额不足 | 替代方案:先创建 basic 实例(`CreateInstance --RegistryType basic`,后付费无预付),再通过 `tccli tcr ModifyInstance --RegistryType premium` 升级;若升级也触发余额限制,需充值。此为环境限制,非命令错误 | +| `ManageInternalEndpoint` 返回 `LimitExceeded` | `tccli vpc DescribeVpcs --region ` 查看当前 VPC 配额使用情况 | 当前账号 VPC 数量已达上限 | 清理不再使用的 VPC(`tccli vpc DeleteVpc --VpcId `)后重试;或使用已有 VPC 和子网。此为环境限制,非命令错误 | + +### 创建成功但状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 实例同步状态长时间 `Failed` | `tccli tcr DescribeReplicationInstanceSyncStatus --RegistryId --ReplicationRegistryId --ShowReplicationLog true --region ` 查看失败日志详情 | 跨地域网络波动或目标实例不可达 | 确认目标实例 Status 为 Running;检查同步日志中失败资源的详细错误;仍失败则[在线咨询](https://cloud.tencent.com/online-service?from=doc_1141) | +| 复制实例创建长时间未完成 | `tccli tcr DescribeReplicationInstanceCreateTasks --ReplicationRegistryId --ReplicationRegionId 4 --region ` 轮询 TaskStatus | 后端异步复制慢,依赖镜像总量和跨地域带宽 | 等待 TaskStatus 更新为 SUCCESS;超过预期时间(通常数分钟至半小时)仍异常则[在线咨询](https://cloud.tencent.com/online-service?from=doc_1141) | +| 复制实例创建后目标地域无法拉取镜像 | `tccli tcr DescribeReplicationInstances --RegistryId --region ` 确认 Status 为 Running;`tccli tcr DescribeInternalEndpoints --RegistryId --region ` 确认内网链路 | 目标地域 VPC 未接入复制实例 | 为复制实例配置内网访问链路:`ManageInternalEndpoint --Operation Create`;参考[配置内网访问控制](../../ops/access/network/private-access) | +| 同步规则配置后镜像未触发同步 | `tccli tcr DescribeReplicationPolicies --RegistryId --region ` 确认规则 Enabled 为 true;`tccli tcr DescribeReplicationInstanceSyncStatus --RegistryId --ReplicationRegistryId --region ` 查看同步日志 | 规则触发模式为手动,或过滤器不匹配 | 检查 Filters 配置是否匹配目标镜像名称和标签;尝试推送新镜像以触发自动同步;若仍需手动,前往控制台手动触发同步 | +| 自定义域名 Status 为 `CREATING` 或 `FAILED` | `tccli tcr DescribeInstanceCustomizedDomain --RegistryId --region ` 持续轮询状态 | SSL 证书下发尚未完成,或证书与域名不匹配 | 等待 1-2 分钟后重新查询;若持续非 SUCCESS,检查 SSL 证书是否已绑定目标域名;`tccli ssl DescribeCertificates --region ` 确认证书状态 | +| 跨国同步不可用(实例复制) | `tccli tcr DescribeInstances --Registryids '[""]' --region ` 确认实例地域;对比目标地域 | 实例复制暂不支持跨国部署 | 改用实例同步(ManageReplication)实现跨地域镜像分发 | + +## 下一步 + +- [同实例多地域复制镜像](../../ops/image-distribution/cross-region-replication) — 高级版实例复制详细指南 +- [跨实例(账号)同步镜像](../../ops/image-distribution/cross-instance-sync) — TCR 实例间同步规则管理 +- [从自建 Harbor 同步镜像到 TCR 企业版](https://cloud.tencent.com/document/product/1141/44970) — Harbor 迁移至 TCR +- [混合云下的多平台镜像数据同步复制](../hybrid-cloud-sync) — 跨平台同步场景 +- [配置内网访问控制](../../ops/access/network/private-access) — VPC 内网链路管理 +- [配置自定义域名](../../ops/access/domain/custom-domain) — 统一域名管理 + +## 控制台替代 + +- **实例同步**:[容器镜像服务控制台 → 同步复制 → 实例同步](https://console.cloud.tencent.com/tcr/replication) — 创建、管理、触发同步规则 +- **实例复制**:[容器镜像服务控制台 → 同步复制 → 实例复制](https://console.cloud.tencent.com/tcr/replication) — 创建、管理复制实例 +- **自定义域名**:[容器镜像服务控制台 → 访问控制 → 域名管理](https://console.cloud.tencent.com/tcr) — 配置自定义域名 diff --git a/src/content/docs/cli/tcr/practices/harbor-migration-tcrctl.md b/src/content/docs/cli/tcr/practices/harbor-migration-tcrctl.md new file mode 100644 index 0000000..fcf703e --- /dev/null +++ b/src/content/docs/cli/tcr/practices/harbor-migration-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "从自建 Harbor 同步镜像到 TCR 企业版(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[从自建 Harbor 同步镜像到 TCR 企业版](https://cloud.tencent.com/document/product/1141/44970) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/practices/harbor-migration.md b/src/content/docs/cli/tcr/practices/harbor-migration.md new file mode 100644 index 0000000..12266b1 --- /dev/null +++ b/src/content/docs/cli/tcr/practices/harbor-migration.md @@ -0,0 +1,1396 @@ +--- +title: "从自建 Harbor 同步镜像到 TCR 企业版" +description: "· page_id `44970`" +--- + +> 对照官方:[从自建 Harbor 同步镜像到 TCR 企业版](https://cloud.tencent.com/document/product/1141/44970) · page_id `44970` + +## 概述 + +将自建 Harbor(v1.8.0+)中的容器镜像和 Helm Chart 同步至腾讯云容器镜像服务(TCR)企业版。同步方向为 Harbor -> TCR(Push-based),由 Harbor 侧管理复制规则,自动或手动将镜像推送到 TCR 实例。 + +整体链路分为四层:**TCR 实例就绪**(步骤 1)-> **网络接入**(步骤 2)-> **访问凭证**(步骤 3)-> **TCR 侧复制基础设施**(步骤 4-5,可选)-> **Harbor 侧配置**(步骤 6-7,Web UI)-> **触发同步与查看结果**(步骤 8)。 + +``` + ┌──────────────────────┐ ┌────────────────────────────┐ + │ 自建 Harbor │ Push │ TCR 企业版 │ + │ (IDC / 云上 CVM) │ ──────> │ 域名: xxx.tencentcloudcr.com │ + │ │ Docker │ VPC 内网 / 公网 │ + │ Harbor v1.8.0+ │ pull-> │ │ + │ │ tag-> │ 镜像仓库 + Helm Chart │ + │ ┌─────────────────┐ │ push │ ↓ 可选:实例复制 │ + │ │ 复制规则 │ │ │ 副本实例(异地只读) │ + │ │ (Tencent TCR │ │ │ ManageReplication 同步 │ + │ │ 或 Docker │ │ │ │ + │ │ Registry 提供者) │ │ │ │ + │ └─────────────────┘ │ └────────────────────────────┘ + └──────────────────────┘ +``` + +**使用限制:** + +- 仅支持 Harbor v1.8.0 及以上版本。 +- Harbor v2.1.2 及以上可选择 "Tencent TCR" 提供者(使用 CAM SecretId/SecretKey),支持 Pull-based 模式且可在 TCR 侧自动新建命名空间。 +- Harbor 版本低于 v2.1.2 时只能选择 "Docker Registry" 提供者(使用 TCR 长期访问凭证),不支持在 TCR 侧自动新建命名空间。 +- 若自建 Harbor 无法通过专线或私有网络访问 TCR,可使用公网同步(会产生公网流量费用)。 +- TCR 侧多地域复制(`CreateReplicationInstance` + `ManageReplication`)仅高级版(`premium`)实例支持。 + +## 前置条件 + +- [环境准备](../../../index.md) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域 + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tcr:DescribeInstances, tcr:ManageInternalEndpoint, tcr:ManageExternalEndpoint +# tcr:DescribeExternalEndpointStatus, tcr:CreateSecurityPolicy, tcr:DescribeSecurityPolicies +# tcr:CreateInstanceToken, tcr:DescribeInstanceToken, tcr:DeleteInstanceToken +# tcr:CreateReplicationInstance, tcr:ManageReplication +# tcr:DescribeReplicationInstances, tcr:DescribeReplicationPolicies +# tcr:DescribeReplicationInstanceCreateTasks, tcr:DeleteReplicationInstance +# tcr:DeleteSecurityPolicy, tcr:DeleteReplicationRule +# 建议授予 QcloudTCRFullAccess 预设策略 +# 验证:执行 DescribeInstances 确认权限 +tccli tcr DescribeInstances --region +# expected: exit 0,返回实例列表(可为空) +``` + +### 资源检查 + +```bash +# 4. 确认 TCR 企业版实例存在且状态正常 +tccli tcr DescribeInstances --region --Registryids '["REGISTRY_ID"]' +# expected: exit 0, Registries[0].Status == "Running" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "example-registry.tencentcloudcr.com", + "RegionName": "ap-guangzhou" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +```bash +# 5. 确认自建 Harbor 可访问 TCR 实例域名(网络连通性预检) +curl -I https://PUBLIC_DOMAIN/v2/ 2>&1 | head -1 +# expected: HTTP/1.1 401 Unauthorized(401 表示可达,只是未认证;非 401 需排查网络) +``` + +> 自建 Harbor 版本 >= v1.8.0 由 Harbor 运维侧确认,不通过 tccli 验证。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 查看实例列表及状态 | `DescribeInstances` | 是 | +| 开启内网访问链路 | `ManageInternalEndpoint --Operation Create` | 否 | +| 开启公网访问入口 | `ManageExternalEndpoint --Operation Create` | 否 | +| 查看公网访问状态 | `DescribeExternalEndpointStatus` | 是 | +| 添加公网白名单 | `CreateSecurityPolicy` | 否 | +| 查看公网白名单 | `DescribeSecurityPolicies` | 是 | +| 创建长期访问凭证 | `CreateInstanceToken` | 否 | +| 查看已有凭证列表 | `DescribeInstanceToken` | 是 | +| 创建异地复制实例 | `CreateReplicationInstance` | 否 | +| 查看复制实例创建进度 | `DescribeReplicationInstanceCreateTasks` | 是 | +| 配置同步规则 | `ManageReplication` | 是 | +| 查看同步规则 | `DescribeReplicationPolicies` | 是 | +| 查看复制实例列表 | `DescribeReplicationInstances` | 是 | +| 删除同步规则 | `DeleteReplicationRule` | 是 | +| 删除复制实例 | `DeleteReplicationInstance` | 是 | +| 删除访问凭证 | `DeleteInstanceToken` | 是 | +| 删除公网白名单 | `DeleteSecurityPolicy` | 是 | +| 关闭公网访问入口 | `ManageExternalEndpoint --Operation Delete` | 是 | +| 删除内网访问链路 | `ManageInternalEndpoint --Operation Delete` | 是 | + +## 关键字段说明 + +以下说明本操作涉及的主要参数。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | TCR 实例 ID,由 `DescribeInstances` 返回,格式 `tcr-` 前缀 | `InvalidParameter.RegistryNotFound` — 实例不存在或地域错误 | +| `Region` | String | 是 | 地域名,如 `ap-guangzhou`、`ap-shanghai` | `InvalidParameter` — 地域与服务不匹配,命令无法路由 | +| `Operation` | String | 条件必填 | `Create` 或 `Delete`,大小写敏感。`ManageInternalEndpoint` 和 `ManageExternalEndpoint` 必填 | `InvalidParameter` — 操作类型未知 | +| `VpcId` | String | 条件必填 | 内网方案必填。VPC ID,格式 `vpc-` 前缀,由 `vpc:DescribeVpcs` 返回 | `FailedOperation` — VPC 不存在或不可用 | +| `SubnetId` | String | 条件必填 | 内网方案必填。子网 ID,格式 `subnet-` 前缀,须与实例同地域 | `FailedOperation` — 子网不存在或地域不匹配 | +| `CidrBlock` | String | 条件必填 | 公网方案必填。IPv4 CIDR,如 `1.2.3.4/32`。`Description` 不可为空 | `InvalidParameter` — CIDR 格式错误或描述为空 | +| `Description` | String | 条件必填 | 白名单用途描述,不可为空字符串 | `InvalidParameter` — Description 为空 | +| `TokenType` | String | 是 | 固定值 `longterm`(长期凭证),不可用其他值 | `InvalidParameter` — 不支持的凭证类型 | +| `ReplicationRegionId` | Integer | 是 | 目标地域数字 ID,如 `1`=广州、`4`=上海 | `UnsupportedOperation` — 地域不支持复制或实例类型不支持 | +| `ReplicationRegionName` | String | 是 | 目标地域名称,须与 `ReplicationRegionId` 对应 | `InvalidParameter` — RegionId 与 RegionName 不匹配 | +| `Rule.Name` | String | 是 | 同步规则名,同实例内不可重名。同名规则 `ManageReplication` 会覆盖更新(幂等) | 同一实例重名 — 幂等覆盖,非报错 | +| `Rule.DestNamespace` | String | 是 | 目标命名空间名,须在 TCR 侧已存在 | `FailedOperation` — 命名空间不存在,同步失败 | +| `Rule.Override` | Boolean | 否 | 是否覆盖同名镜像。建议 `true` | `false` 时同名 Tag 不同步 | +| `Rule.Filters[].Type` | String | 否 | 过滤器类型,须小写:`name`(仓库名)、`tag`(标签)、`resource`(资源类型) | 大写 `Name` 不识别,Filter 不生效 | +| `PolicyIndex` | Integer | 是(删除时) | 白名单策略序号,由 `DescribeSecurityPolicies` 返回 | `InvalidParameter` — 序号不存在 | +| `PolicyVersion` | String | 是(删除时) | 白名单策略版本号,由 `DescribeSecurityPolicies` 返回 | `InvalidParameter` — 版本号不匹配 | + +## 操作步骤 + +整体分为 TCR 侧准备(步骤 1-3 必选,步骤 4-5 可选)和 Harbor 侧配置(步骤 6-7),网络与凭证就绪后再进入 Harbor Web UI。 + +### 步骤 1:确认 TCR 实例可访问 + +查询实例信息,获取访问域名与状态: + +```bash +tccli tcr DescribeInstances \ + --Registryids '["REGISTRY_ID"]' \ + --region +# expected: exit 0, Registries[0].Status == "Running" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "example-registry.tencentcloudcr.com", + "RegionName": "ap-guangzhou", + "InternalEndpoint": "10.1.65.238" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +记录 `PublicDomain`(域名须以 `.tencentcloudcr.com` 结尾)。后续 Harbor 侧配置使用完整 URL:`https://PUBLIC_DOMAIN`。 + +### 步骤 2:配置 Harbor 到 TCR 的网络接入 + +根据自建 Harbor 的网络情况,选择内网或公网方案。 + +#### 方案 A:通过腾讯云私有网络访问(推荐) + +若自建 Harbor 部署在腾讯云 VPC 内或已通过专线/VPN 打通至腾讯云 VPC,使用内网访问可提升同步速度并节省公网流量费用。 + +##### 选择依据 + +- **方案选择**:选内网而非公网,因为专线/VPN 链路带宽稳定、无公网流量费用,且同步大量镜像时公网带宽可能成为瓶颈。 +- **VpcId**:选用自建 Harbor 所在或已打通的 VPC,而非新建 VPC — 新建 VPC 需额外打通与 Harbor 的链路。 +- **SubnetId**:选用与 TCR 实例同地域的子网,跨地域子网会导致连接超时。 + +##### 最小配置(仅必填字段) + +`tcr-manage-internal-endpoint-minimal.json`: + +```json +{ + "RegistryId": "REGISTRY_ID", + "Operation": "Create", + "VpcId": "VPC_ID", + "SubnetId": "SUBNET_ID" +} +``` + +##### 增强配置(含可选地域字段) + +`tcr-manage-internal-endpoint-enhanced.json`: + +```json +{ + "RegistryId": "REGISTRY_ID", + "Operation": "Create", + "VpcId": "VPC_ID", + "SubnetId": "SUBNET_ID", + "RegionId": 1, + "RegionName": "ap-guangzhou" +} +``` + +```bash +# 执行创建(任选一个配置文件) +tccli tcr ManageInternalEndpoint \ + --cli-input-json file://tcr-manage-internal-endpoint-minimal.json \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RegistryId": "tcr-example", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +开启自动解析后,VPC 内即可通过 `INSTANCE_NAME.tencentcloudcr.com` 内网访问 TCR 实例。 + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `REGISTRY_ID` | TCR 实例 ID | 格式 `tcr-` 前缀 | `tccli tcr DescribeInstances --region ` | +| `REGION` | 地域 | 如 `ap-guangzhou` | 与 TCR 实例地域一致 | +| `VPC_ID` | VPC ID | 格式 `vpc-` 前缀,须已存在 | `tccli vpc DescribeVpcs --region ` | +| `SUBNET_ID` | 子网 ID | 格式 `subnet-` 前缀,与实例同地域 | `tccli vpc DescribeSubnets --region ` | + +#### 方案 B:通过公网访问 + +若自建 Harbor 未部署在腾讯云 VPC 内且无法通过专线打通,使用公网访问。 + +##### B.1 开启公网访问入口 + +###### 选择依据 + +- **何时用公网**:仅当内网方案不可行(Harbor 不在腾讯云,无专线/VPN)时才选择。公网会产生流量费用,且同步速度受公网带宽限制。 +- **Operation=Create**:这是开启操作(非幂等),重复执行会返回 `UnsupportedOperation: The current public network access status is Opened`。执行 Create 前先通过 `DescribeExternalEndpointStatus` 预检,仅在 `Status == "Closed"` 时执行。 + +```bash +# 预检:确认公网入口当前状态为 Closed 再执行 Create +tccli tcr DescribeExternalEndpointStatus \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, Status == "Closed" 时方可执行 Create +``` + +**预期输出**(入口已关闭时): + +```json +{ + "Status": "Closed", + "Reason": "", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +```bash +tccli tcr ManageExternalEndpoint \ + --RegistryId REGISTRY_ID \ + --Operation Create \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RegistryId": "tcr-example", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +##### B.2 轮询公网访问状态至 Opened + +公网入口开启是异步操作,需轮询 `DescribeExternalEndpointStatus` 直到 `Status` 变为 `Opened`(通常需要 1-2 分钟): + +```bash +tccli tcr DescribeExternalEndpointStatus \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, Status 终态为 "Opened" +``` + +**预期输出**(开启中): + +```json +{ + "Status": "Opening", + "Reason": "", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +| Status | 含义 | +|--------|------| +| `Closed` | 公网访问入口已关闭 | +| `Opening` | 公网访问入口开启中 | +| `Opened` | 公网访问入口已开启 | +| `Closing` | 公网访问入口关闭中 | + +重复执行上述命令,直到返回 `"Status": "Opened"`,方可进行后续操作。 + +**多维度验证**(确认公网入口完整就绪,不只看 Status): + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 状态 | `DescribeExternalEndpointStatus.Status` | `Opened` | +| 网络 | `curl -I https://PUBLIC_DOMAIN/v2/` | 返回 `401 Unauthorized`(可达但未认证) | +| 白名单 | `DescribeSecurityPolicies` 返回(若已配置) | 策略列表非空 | + +##### B.3 添加公网白名单(安全策略) + +入口开启后仍默认拒绝全部来源的公网访问,需为 Harbor 出口 IP 添加白名单。 + +###### 选择依据 + +- **CidrBlock 格式**:使用 `/32` 掩码限定单个 IP(最小权限原则),而非 `/0` 放通全网。`0.0.0.0/0` 仅用于临时测试,同步完成后必须删除。 +- **Description**:必填且不可为空字符串,用于标识白名单用途,方便后续清理时识别。 + +```bash +tccli tcr CreateSecurityPolicy \ + --RegistryId REGISTRY_ID \ + --CidrBlock "HARBOR_EXIT_IP/32" \ + --Description "自建 Harbor 公网访问" \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RegistryId": "tcr-example", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +> 如无法确认 Harbor 的公网出口 IP,可临时配置 `--CidrBlock "0.0.0.0/0"` 以放通全部来源。**完成同步后务必尽快删除该白名单**(见[清理](#清理))。 + +查看已有白名单: + +```bash +tccli tcr DescribeSecurityPolicies \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "SecurityPolicySet": [ + { + "PolicyIndex": 0, + "CidrBlock": "HARBOR_EXIT_IP/32", + "Description": "自建 Harbor 公网访问", + "PolicyVersion": "1" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +### 步骤 3:获取 TCR 访问凭证 + +Harbor 配置复制目标时需要 TCR 的访问凭证。根据 Harbor 版本选择不同凭证类型: + +#### Harbor v2.1.2 及以上 — 使用 CAM API 密钥(推荐) + +前往 [API 密钥管理](https://console.cloud.tencent.com/cam/capi) 获取 **SecretId** 和 **SecretKey**: +- Harbor 侧「访问 ID」-> SecretId +- Harbor 侧「访问密码」-> SecretKey + +此方式支持 Pull-based 同步(TCR -> Harbor),且可在 TCR 侧自动新建命名空间。 + +#### Harbor 低于 v2.1.2 — 使用 TCR 长期访问凭证 + +##### 选择依据 + +- **TokenType**:必须选 `longterm` 而非 `temp`(临时凭证有过期时间,不适合长期同步任务)。长期凭证无过期限制。 +- **Desc**:建议填写可识别的描述(如 "自建 Harbor 数据同步专用"),方便后续通过 `DescribeInstanceToken` 识别该凭证用途。 + +创建专用的长期访问凭证: + +```bash +tccli tcr CreateInstanceToken \ + --RegistryId REGISTRY_ID \ + --TokenType longterm \ + --Desc "自建 Harbor 数据同步专用" \ + --region +# expected: exit 0, 返回 Username 和 Token +``` + +**预期输出**: + +```json +{ + "Username": "100012345678", + "Token": "eyJhbGciOiJSUzI1NiIsImtpZCI6...", + "ExpTime": 2096844746789, + "TokenId": "tcr-token-example", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +> 记录 `Token` 字段,**仅此一次保存机会**。在 Harbor 中填写「访问 ID」= `Username`,「访问密码」= `Token`。 + +查看已有凭证: + +```bash +tccli tcr DescribeInstanceToken \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, TotalCount > 0 +``` + +**预期输出**: + +```json +{ + "Tokens": [ + { + "Id": "tcr-token-example", + "Desc": "自建 Harbor 数据同步专用", + "RegistryId": "tcr-example", + "Enabled": true, + "CreatedAt": "2026-01-01T00:00:00+08:00", + "ExpiredAt": 2096844746789 + } + ], + "TotalCount": 1, + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `REGISTRY_ID` | TCR 实例 ID | 格式 `tcr-` 前缀 | `DescribeInstances` | +| `REGION` | 地域 | 如 `ap-guangzhou` | 与 TCR 实例地域一致 | + +### 步骤 4:在 TCR 侧创建复制实例(异地多活,可选 — 仅高级版) + +> 本步骤为可选扩展:若需要将 Harbor 同步至 TCR 的镜像进一步分发到多地域,可在目标地域创建复制实例(只读副本)。仅高级版(`premium`)实例支持。跳过本步骤则镜像仅存在于当前 TCR 实例。 + +##### 选择依据 + +- **何时需要**:当业务要求多地域就近拉取镜像时才创建复制实例。单地域使用场景跳过此步骤可以节省目标地域的实例费用。 +- **ReplicationRegionId**:选择离目标用户最近的地域(如华东用户选 `4`=上海),而非随机选择。 +- **前置条件**:确认实例类型为 `premium`,非高级版执行 `CreateReplicationInstance` 会直接报错。 +- **升级到高级版**:若实例类型为 `basic` 或 `standard`,需先通过 `ModifyInstance` 升级至 `premium`。升级命令零预付扣费(`PayMod=0`),操作立即生效,无需提前充值。 + +创建复制实例前确认实例为高级版: + +```bash +tccli tcr DescribeInstances \ + --Registryids '["REGISTRY_ID"]' \ + --region +# expected: exit 0, Registries[0].RegistryType == "premium" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "example-registry.tencentcloudcr.com", + "RegionName": "ap-guangzhou" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +确认 `RegistryType` 为 `"premium"` 后,创建复制实例。 + +若 `RegistryType` 非 `"premium"`(如返回 `"basic"` 或 `"standard"`),先执行升级: + +```bash +tccli tcr ModifyInstance \ + --RegistryId REGISTRY_ID \ + --RegistryType premium \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +升级后重新查询确认 `RegistryType` 已变为 `"premium"`: + +```bash +tccli tcr DescribeInstances \ + --Registryids '["REGISTRY_ID"]' \ + --region +# expected: exit 0, Registries[0].RegistryType == "premium" +``` + +确认后创建复制实例: + +```bash +tccli tcr CreateReplicationInstance \ + --RegistryId REGISTRY_ID \ + --ReplicationRegionId REPLICATION_REGION_ID \ + --ReplicationRegionName REPLICATION_REGION_NAME \ + --region +# expected: exit 0, 返回 ReplicationRegistryId +``` + +**预期输出**: + +```json +{ + "ReplicationRegistryId": "tcr-example-4-ghbzyc", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +常用地域 ID 参考: + +| 地域名称 | RegionId | 地域名称 | RegionId | +|---------|----------|---------|----------| +| ap-guangzhou | 1 | ap-shanghai | 4 | +| ap-beijing | 8 | ap-nanjing | 33 | +| ap-chengdu | 16 | ap-singapore | 9 | +| na-siliconvalley | 15 | eu-frankfurt | 17 | + +记录返回的 `ReplicationRegistryId`(格式如 `tcr-example-4-xxx`)。 + +**轮询复制实例创建进度:** + +复制实例创建是异步操作,需轮询确认完成: + +```bash +tccli tcr DescribeReplicationInstanceCreateTasks \ + --ReplicationRegistryId REPLICATION_REGISTRY_ID \ + --ReplicationRegionId REPLICATION_REGION_ID \ + --region +# expected: exit 0 +``` + +**预期输出**(创建完成): + +```json +{ + "Status": "SUCCESS", + "TaskDetail": [ + { + "TaskName": "CreateReplication", + "TaskUUID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "TaskStatus": "SUCCESS", + "TaskMessage": "", + "CreatedTime": "2026-01-01T00:00:00+08:00", + "FinishedTime": "2026-01-01T00:01:00+08:00" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +`TaskStatus` 为 `SUCCESS` 表示复制实例已就绪。 + +**多维度验证**(确认复制实例完整就绪): + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 任务状态 | `DescribeReplicationInstanceCreateTasks.TaskDetail[*].TaskStatus` | 全部 `SUCCESS` | +| 实例列表 | `DescribeReplicationInstances` 含目标实例 | `ReplicationRegistryId` 存在且 `Status: "Running"` | +| 源实例 | `DescribeInstances --Registryids '["REGISTRY_ID"]'` | 源实例 `Status: "Running"` | + +```bash +# 验证复制实例已存在于列表中 +tccli tcr DescribeReplicationInstances \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, ReplicationRegistries 中包含目标实例且 Status == "Running" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "ReplicationRegistries": [ + { + "RegistryId": "tcr-example", + "ReplicationRegistryId": "tcr-example-4-ghbzyc", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai", + "Status": "Running", + "CreatedAt": "2026-01-01T00:00:00+08:00" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +### 步骤 5:在 TCR 侧配置同步规则(可选 — 仅高级版) + +> 本步骤依赖步骤 4 创建的复制实例。若跳过了步骤 4,也跳过本步骤。 + +在源实例上创建同步规则,将 Harbor 同步过来的镜像自动复制到目标地域的副本实例。 + +##### 选择依据 + +- **Rule.Name**:建议使用描述性名称(如 `harbor-sync-to-shanghai`),便于在 `DescribeReplicationPolicies` 中区分多条规则。 +- **Rule.DestNamespace**:源与目标命名空间建议同名,减少路径混淆。若目标命名空间不存在,需先在 TCR 侧创建(参考[管理命名空间](../../ops/image-creation/namespace))。 +- **Rule.Override**:建议设为 `true`(覆盖同名镜像),避免因 Tag 已存在导致同步跳过。 +- **Rule.Deletion**:建议保持 `false`(不同步删除),防止 Harbor 侧误删导致 TCR 侧数据丢失。 +- **Rule.Filters**:`resource` 选 `image` 或 `chart` 视需求而定;`name` 用 `.*` 匹配全部,或用具体仓库名限定范围。**注意**:`Filters[].Type` 必须小写(`name`、`tag`、`resource`),大写 `Name` 不识别。 +- **幂等性**:`ManageReplication` 同名规则已存在则覆盖更新,可安全重放。 +- **跨地域限制**:从源实例地域调用 `ManageReplication` 时,若目标为异地复制实例,可能返回 `InternalError: Not support slave region`。此时需切换至目标地域(副实例所在地域)执行,或在控制台配置。 + +参数 >= 4 个,使用 `--cli-input-json file://`,分最小/增强两层。 + +**最小配置**(仅必填字段): + +`sync-rule-minimal.json`: + +```json +{ + "SourceRegistryId": "REGISTRY_ID", + "DestinationRegistryId": "REPLICATION_REGISTRY_ID", + "Rule": { + "Name": "RULE_NAME", + "DestNamespace": "DEST_NAMESPACE" + } +} +``` + +**增强配置**(含可选字段): + +`sync-rule-enhanced.json`: + +```json +{ + "SourceRegistryId": "REGISTRY_ID", + "DestinationRegistryId": "REPLICATION_REGISTRY_ID", + "Rule": { + "Name": "RULE_NAME", + "DestNamespace": "DEST_NAMESPACE", + "Override": true, + "Filters": [ + { + "Type": "name", + "Value": ".*" + }, + { + "Type": "resource", + "Value": "image" + } + ], + "Deletion": false + }, + "Description": "Harbor 同步镜像自动复制至异地" +} +``` + +```bash +# 执行创建(任选一个配置文件) +tccli tcr ManageReplication \ + --cli-input-json file://sync-rule-minimal.json \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +> `ManageReplication` 跨地域执行限制:从源实例地域调用时,若目标为异地复制实例,API 可能返回 `InternalError: Not support slave region`。**解决方案**:切换至目标地域(副实例所在地域,如 `ap-shanghai`)执行 `ManageReplication`,`SourceRegistryId` 填源实例、`DestinationRegistryId` 填本地实例。此操作在同地域内幂等:同名规则会覆盖更新。复制实例创建本身(步骤 4)不受此问题影响。 + +查看同步规则已生效: + +```bash +tccli tcr DescribeReplicationPolicies \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, TotalCount > 0 +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "ReplicationPolicyInfoList": [ + { + "ID": 1, + "Name": "RULE_NAME", + "Enabled": true, + "SrcResource": "INSTANCE_NAME/.* REGION_NAME", + "DestResource": "REPLICATION_REGISTRY_ID/DEST_NAMESPACE REPLICATION_REGION_NAME", + "Filters": [ + {"Type": "name", "Value": ".*"}, + {"Type": "resource", "Value": "image"} + ], + "Override": true, + "CreationTime": "2026-01-01T00:00:00+08:00" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `REGISTRY_ID` | 源 TCR 实例 ID | 格式 `tcr-` 前缀,须为 premium | `DescribeInstances` | +| `REPLICATION_REGISTRY_ID` | 目标复制实例 ID | 由 `CreateReplicationInstance` 返回 | `DescribeReplicationInstances` | +| `REGION` | 地域 | 如 `ap-guangzhou` | 与源实例地域一致 | +| `RULE_NAME` | 同步规则名称 | 同实例内不可重名 | 自定义,如 `harbor-sync-to-shanghai` | +| `DEST_NAMESPACE` | 目标命名空间 | 须在 TCR 侧已存在 | `tccli tcr DescribeNamespaces --RegistryId REGISTRY_ID --region ` | + +### 步骤 6:在 Harbor 侧配置复制目标(Harbor Web UI,无 TCR API) + +以管理员账号登录 Harbor Web UI,进入 **系统管理 > 仓库管理 > 新建目标**。 + +**Harbor v2.1.2 及以上配置(Tencent TCR 提供者):** + +| 字段 | 值 | +|------|-----| +| 提供者 | `Tencent TCR` | +| 目标名 | 自定义,如 `tencent-tcr` | +| 目标 URL | `https://PUBLIC_DOMAIN` | +| 访问 ID | CAM SecretId | +| 访问密码 | CAM SecretKey | +| 验证远程证书 | 保持默认 | + +**Harbor 低于 v2.1.2 配置(Docker Registry 提供者):** + +| 字段 | 值 | +|------|-----| +| 提供者 | `Docker Registry` | +| 目标名 | 自定义,如 `tencent-tcr` | +| 目标 URL | `https://PUBLIC_DOMAIN` | +| 访问 ID | `CreateInstanceToken` 返回的 `Username` | +| 访问密码 | `CreateInstanceToken` 返回的 `Token` | + +单击 **测试连接** 验证连通性。若失败,检查步骤 2 网络接入配置。 + +### 步骤 7:在 Harbor 侧创建复制规则(Harbor Web UI,无 TCR API) + +进入 **系统管理 > 复制管理 > 新建规则**: + +| 字段 | 值 | +|------|-----| +| 名称 | 自定义,如 `sync-images-to-tcr` | +| 复制模式 | `Push-based`(Harbor 推送至 TCR)。使用 "Tencent TCR" 提供者且 Harbor v2.1.2+ 时也可选 `Pull-based` | +| 源资源过滤器 | 留空则同步全部镜像与 Helm Chart;支持按仓库名/Tag 过滤 | +| 目的 Registry | 选择步骤 6 创建的目标仓库 | +| 目的 Namespace | 留空则默认同名命名空间("Tencent TCR" 提供者可自动创建;"Docker Registry" 提供者需先在 TCR 侧手动创建:参考[管理命名空间](../../ops/image-creation/namespace)) | +| 触发模式 | `手动触发` 或 `事件驱动`(推送时自动同步) | +| 覆盖 | 建议勾选(覆盖同名资源) | + +### 步骤 8:触发同步并查看结果 + +以下演示手动同步流程。若复制规则为事件驱动,将镜像推送至 Harbor 后会自动触发同步。 + +**推送到 Harbor:** + +```bash +# 从 Docker Hub 拉取测试镜像 +docker pull nginx:latest + +# 打上 Harbor 项目标签 +docker tag nginx:latest HARBOR_DOMAIN/PROJECT/nginx:latest + +# 推送至自建 Harbor +docker push HARBOR_DOMAIN/PROJECT/nginx:latest +``` + +**从 TCR 侧验证镜像已到达:** + +```bash +# 登录 TCR +docker login PUBLIC_DOMAIN --username=USERNAME --password=TOKEN + +# 拉取同步后的镜像 +docker pull PUBLIC_DOMAIN/NAMESPACE/nginx:latest +``` + +**从 TCR 侧查看同步策略与复制实例:** + +查看复制策略列表(Harbor 创建的规则也会在此显示): + +```bash +tccli tcr DescribeReplicationPolicies \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, TotalCount > 0 +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "ReplicationPolicyInfoList": [ + { + "ID": 1, + "Name": "sync-images-to-tcr", + "Enabled": true, + "SrcResource": "HARBOR_DOMAIN/PROJECT/** REGION_NAME", + "DestResource": "PUBLIC_DOMAIN/NAMESPACE/** REGION_NAME", + "Filters": [ + {"Type": "name", "Value": ".*"}, + {"Type": "tag", "Value": ""}, + {"Type": "resource", "Value": "image"} + ], + "Override": true, + "CreationTime": "2026-01-01T00:00:00+08:00" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +查看复制实例列表: + +```bash +tccli tcr DescribeReplicationInstances \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "ReplicationRegistries": [ + { + "RegistryId": "tcr-example", + "ReplicationRegistryId": "REPLICATION_REGISTRY_ID", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai", + "Status": "Running", + "CreatedAt": "2026-01-01T00:00:00+08:00" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +若使用了实例复制(步骤 4-5),可查看复制实例中的同步任务进度: + +```bash +tccli tcr DescribeReplicationInstanceCreateTasks \ + --ReplicationRegistryId REPLICATION_REGISTRY_ID \ + --ReplicationRegionId REPLICATION_REGION_ID \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "Status": "SUCCESS", + "TaskDetail": [ + { + "TaskName": "SyncTask", + "TaskUUID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "TaskStatus": "SUCCESS", + "TaskMessage": "", + "CreatedTime": "2026-01-01T00:00:00+08:00", + "FinishedTime": "2026-01-01T00:01:00+08:00" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +## 验证 + +### 控制面(tccli) + +```bash +# 确认实例状态为 Running +tccli tcr DescribeInstances \ + --Registryids '["REGISTRY_ID"]' \ + --region +# expected: exit 0, Registries[0].Status == "Running" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "example-registry.tencentcloudcr.com", + "RegionName": "ap-guangzhou", + "InternalEndpoint": "10.1.65.238" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +```bash +# 确认公网入口已开启(若使用公网方案) +tccli tcr DescribeExternalEndpointStatus \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, Status == "Opened" +``` + +**预期输出**: + +```json +{ + "Status": "Opened", + "Reason": "", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +```bash +# 确认访问凭证可查询 +tccli tcr DescribeInstanceToken \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, TotalCount > 0, Token Enabled: true +``` + +**预期输出**: + +```json +{ + "Tokens": [ + { + "Id": "tcr-token-example", + "Desc": "自建 Harbor 数据同步专用", + "RegistryId": "tcr-example", + "Enabled": true, + "CreatedAt": "2026-01-01T00:00:00+08:00", + "ExpiredAt": 2096844746789 + } + ], + "TotalCount": 1, + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +```bash +# 确认 Harbor 侧的同步策略已被 TCR 识别 +tccli tcr DescribeReplicationPolicies \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, TotalCount > 0, 策略 Enabled: true +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "ReplicationPolicyInfoList": [ + { + "ID": 1, + "Name": "sync-images-to-tcr", + "Enabled": true, + "SrcResource": "HARBOR_DOMAIN/PROJECT/**", + "DestResource": "PUBLIC_DOMAIN/NAMESPACE/**", + "Override": true, + "CreationTime": "2026-01-01T00:00:00+08:00" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +```bash +# 确认复制实例存在且状态正常(若使用) +tccli tcr DescribeReplicationInstances \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, ReplicationRegistries[*].Status == "Running" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "ReplicationRegistries": [ + { + "RegistryId": "tcr-example", + "ReplicationRegistryId": "REPLICATION_REGISTRY_ID", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai", + "Status": "Running", + "CreatedAt": "2026-01-01T00:00:00+08:00" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +**多维度验证汇总**: + +| 维度 | 命令 | 预期 | +|------|------|------| +| 实例状态 | `DescribeInstances --Registryids '["REGISTRY_ID"]'` | `Status: "Running"` | +| 公网入口(若使用) | `DescribeExternalEndpointStatus` | `Status: "Opened"` | +| 访问凭证 | `DescribeInstanceToken` | `TotalCount > 0`,`Enabled: true` | +| 复制策略 | `DescribeReplicationPolicies` | `TotalCount > 0`,策略 `Enabled: true` | +| 复制实例(若使用) | `DescribeReplicationInstances` | 实例 `Status: "Running"` | + +### 数据面(docker) + +```bash +# 登录 TCR +docker login PUBLIC_DOMAIN --username=USERNAME --password=TOKEN + +# 确认镜像已同步至 TCR +docker pull PUBLIC_DOMAIN/NAMESPACE/REPOSITORY:TAG +``` + +## 清理 + +> **计费警告**:TCR 企业版实例按量计费,按实例规格和存储用量收费。若不再需要实例,请前往[销毁退还实例](../../ops/instances/delete)彻底删除以停止计费。复制实例(步骤 4 创建)也独立计费。 +> +> **副作用警告**: +> - `DeleteInstanceToken`:删除凭证后,所有依赖该凭证的 Harbor 复制目标将立即失效,同步中断。 +> - `DeleteReplicationRule`:删除同步规则后,该规则停止生效,后续 Harbor 推送的镜像不再自动复制到异地实例。 +> - `DeleteReplicationInstance`:删除复制实例后,目标地域的只读副本被销毁,已同步的镜像数据不可恢复。 +> - `DeleteSecurityPolicy`:删除白名单后,对应 IP 将无法通过公网访问 TCR 实例。 +> - `ManageInternalEndpoint --Operation Delete`:删除内网链路后,VPC 内所有服务将无法通过内网访问 TCR 实例。 +> +> 清理顺序为依赖倒序:先删规则(依赖实例),再删复制实例(依赖源实例),再删凭证和网络配置。 + +### 数据面(docker) + +```bash +# 登出 TCR +docker logout PUBLIC_DOMAIN + +# 清理本地已同步的测试镜像(可选) +docker rmi PUBLIC_DOMAIN/NAMESPACE/REPOSITORY:TAG +``` + +### 控制面(tccli) + +**1. 删除同步规则(若创建了步骤 5 的 ManageReplication 规则):** + +查询当前规则以获取 `RuleId`: + +```bash +tccli tcr DescribeReplicationPolicies \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, 记录待删规则的 ID 字段 +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "ReplicationPolicyInfoList": [ + { + "ID": 1, + "Name": "RULE_NAME", + "Enabled": true + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +```bash +tccli tcr DeleteReplicationRule \ + --RegistryId REGISTRY_ID \ + --RuleId RULE_ID \ + --region +# expected: exit 0 +``` + +验证规则已删除: + +```bash +tccli tcr DescribeReplicationPolicies \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, 列表中不再包含已删规则 +``` + +**2. 删除复制实例(若创建了步骤 4 的复制实例):** + +清理前确认目标实例存在: + +```bash +tccli tcr DescribeReplicationInstances \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, 确认 ReplicationRegistryId 为目标实例 +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "ReplicationRegistries": [ + { + "RegistryId": "tcr-example", + "ReplicationRegistryId": "REPLICATION_REGISTRY_ID", + "Status": "Running" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +```bash +tccli tcr DeleteReplicationInstance \ + --RegistryId REGISTRY_ID \ + --ReplicationRegistryId REPLICATION_REGISTRY_ID \ + --region +# expected: exit 0 +``` + +验证复制实例已删除: + +```bash +tccli tcr DescribeReplicationInstances \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, TotalCount == 0 或 ReplicationRegistries 为空 +``` + +**预期输出**: + +```json +{ + "TotalCount": 0, + "ReplicationRegistries": null, + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +**3. 删除同步专用访问凭证:** + +清理前确认凭证存在: + +```bash +tccli tcr DescribeInstanceToken \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, 记录待删凭证的 Id(即 DeleteInstanceToken 所需 --TokenId 的值) +``` + +**预期输出**: + +```json +{ + "Tokens": [ + { + "Id": "tcr-token-example", + "Desc": "自建 Harbor 数据同步专用", + "Enabled": true + } + ], + "TotalCount": 1, + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +```bash +tccli tcr DeleteInstanceToken \ + --RegistryId REGISTRY_ID \ + --TokenId TOKEN_ID \ + --region +# expected: exit 0 +``` + +验证凭证已删除: + +```bash +tccli tcr DescribeInstanceToken \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, Tokens 列表中不再包含已删凭证 +``` + +**4. 清理公网白名单和入口(若使用了公网方案):** + +清理前查询已有白名单: + +```bash +tccli tcr DescribeSecurityPolicies \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, 记录各策略的 PolicyIndex 和 PolicyVersion +``` + +**预期输出**: + +```json +{ + "SecurityPolicySet": [ + { + "PolicyIndex": 0, + "CidrBlock": "HARBOR_EXIT_IP/32", + "Description": "自建 Harbor 公网访问", + "PolicyVersion": "1" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +逐个删除白名单: + +```bash +tccli tcr DeleteSecurityPolicy \ + --RegistryId REGISTRY_ID \ + --PolicyIndex POLICY_INDEX \ + --PolicyVersion POLICY_VERSION \ + --region +# expected: exit 0 +``` + +> `PolicyIndex` 和 `PolicyVersion` 均来自 `DescribeSecurityPolicies` 返回的 `SecurityPolicySet[*]` 中各字段。 + +验证白名单已清空: + +```bash +tccli tcr DescribeSecurityPolicies \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, SecurityPolicySet 为空 +``` + +关闭公网访问入口: + +```bash +tccli tcr ManageExternalEndpoint \ + --RegistryId REGISTRY_ID \ + --Operation Delete \ + --region +# expected: exit 0 +``` + +验证公网入口已关闭: + +```bash +tccli tcr DescribeExternalEndpointStatus \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, Status == "Closed" +``` + +**预期输出**: + +```json +{ + "Status": "Closed", + "Reason": "", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +**5. 清理内网访问链路(若使用了内网方案):** + +清理前确认链路存在: + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0 +``` + +删除内网链路: + +`tcr-manage-internal-endpoint-delete.json`: + +```json +{ + "RegistryId": "REGISTRY_ID", + "Operation": "Delete", + "VpcId": "VPC_ID", + "SubnetId": "SUBNET_ID" +} +``` + +```bash +tccli tcr ManageInternalEndpoint \ + --cli-input-json file://tcr-manage-internal-endpoint-delete.json \ + --region +# expected: exit 0 +``` + +验证已删除: + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, AccessVpcSet 为 null 或 TotalCount == 0 +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ManageInternalEndpoint` 返回 `FailedOperation` | `tccli vpc DescribeVpcs --region ` 和 `tccli vpc DescribeSubnets --region ` 确认 VPC 和子网均存在且状态正常 | VPC ID 或子网 ID 不存在 / 不可用 | 修正 `VpcId` 和 `SubnetId` 为有效值,且确保子网与 TCR 实例同地域 | +| `ManageExternalEndpoint --Operation Create` 返回 `UnsupportedOperation` | `tccli tcr DescribeExternalEndpointStatus --RegistryId REGISTRY_ID --region ` 确认当前状态 | 公网入口已为 `Opened` 状态,重复执行 Create(非幂等)。错误信息含 "The current public network access status is Opened" | 仅在 `Status == "Closed"` 时执行 Create。若当前为 `Opened`,表示入口已就绪,跳过开启步骤直接使用 | +| `CreateSecurityPolicy` 返回 `InvalidParameter` | `tccli tcr DescribeSecurityPolicies --RegistryId REGISTRY_ID --region ` 确认现有策略列表 | `CidrBlock` 格式不正确(非合法 IPv4 CIDR)或 `Description` 为空字符串 | 修正为合法 IPv4 地址段(如 `1.2.3.4/32`),`Description` 不可为空 | +| `CreateReplicationInstance` 返回 `UnsupportedOperation` | `tccli tcr DescribeInstances --Registryids '["REGISTRY_ID"]' --region ` 确认 `RegistryType` | 实例非高级版(`premium`)或目标地域不支持复制 | 升级实例至高级版:`tccli tcr ModifyInstance --RegistryId REGISTRY_ID --RegistryType premium --region `,或选择支持的地域 | +| `ManageReplication` 返回 `InternalError: Not support slave region` | `tccli tcr DescribeReplicationInstances --RegistryId REGISTRY_ID --region ` 确认复制实例 `Status` 为 `"Running"`;检查当前执行地域是否为源实例地域 | 从源实例地域执行跨地域复制规则设置,API 受限 | 切换至目标地域(副实例所在地域)执行 `ManageReplication`,指定 `SourceRegistryId` 为源实例、`DestinationRegistryId` 为本地实例;或通过 [TCR 控制台](https://console.cloud.tencent.com/tcr/replication) 在目标地域配置 | +| `CreateInstanceToken` 返回 `InvalidParameter` | 检查 `TokenType` 参数值 | `TokenType` 不是 `longterm` | 修正为 `--TokenType longterm` | +| `DeleteSecurityPolicy` 返回 `InvalidParameter` | `tccli tcr DescribeSecurityPolicies --RegistryId REGISTRY_ID --region ` 确认 `PolicyIndex` 和 `PolicyVersion` | `PolicyIndex` 不存在或 `PolicyVersion` 不匹配 | 使用 `DescribeSecurityPolicies` 返回的实际 `PolicyIndex` 和 `PolicyVersion` | +| `CamNoAuth` 或权限拒绝 | `tccli tcr DescribeInstances --region ` 确认当前凭证是否有 TCR 读权限 | 子账号缺少所需 CAM 权限 | 此为环境限制,非命令错误。授予 `QcloudTCRFullAccess` 预设策略,或参考[企业版授权方案示例](https://cloud.tencent.com/document/product/1141/41417) | +| `LimitExceeded` | `tccli vpc DescribeVpcs --region ` 统计已有 VPC 数量 | 当前账号 VPC 数量已达上限 | 此为环境限制,非命令错误。清理不再使用的 VPC 后重试,或使用已有 VPC 和子网 | +| `TradeFailed` 或计费相关错误 | 检查账户余额和实例配额 | 账户欠费或实例配额不足 | 此为环境限制,非命令错误。前往控制台充值或调整配额 | + +### 创建成功但状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| Harbor 测试连接失败 | `tccli tcr DescribeExternalEndpointStatus --RegistryId REGISTRY_ID --region ` 确认状态;内网方案:`tccli tcr DescribeInternalEndpoints --RegistryId REGISTRY_ID --region ` 确认链路状态 | 网络不通(公网入口未开启/内网链路未就绪)或凭证错误 | 公网:确保 `Status=Opened`;内网:确保 `AccessVpcSet[0].Status=Enabled`;两种方案均检查凭证正确性 | +| Harbor 提供者列表中无 "Tencent TCR" | Harbor Web UI 版本号确认 | Harbor 版本低于 v2.1.2 | 选择 "Docker Registry" 提供者,使用 `CreateInstanceToken` 返回的凭证(非功能缺陷,是版本限制) | +| `CreateInstanceToken` 返回的 Token 丢失 | 无(Token 仅返回一次,不可恢复) | Token 创建时未妥善保存 | 先删除旧 Token:`tccli tcr DeleteInstanceToken --RegistryId REGISTRY_ID --TokenId TOKEN_ID --region `,再重新 `CreateInstanceToken` | +| `DescribeReplicationPolicies` 返回空 | Harbor Web UI 确认复制规则已创建;`tccli tcr DescribeReplicationInstances --RegistryId REGISTRY_ID --region ` 确认复制实例存在 | Harbor 侧尚未创建复制规则或规则未生效(非 tccli 问题) | 确认 Harbor 复制规则已创建并已触发同步 | +| 同步完成后 TCR 侧命名空间不存在 | `tccli tcr DescribeNamespaces --RegistryId REGISTRY_ID --region ` 确认命名空间是否存在 | "Docker Registry" 提供者不支持自动创建命名空间 | 先在 TCR 侧手动创建:`tccli tcr CreateNamespace --RegistryId REGISTRY_ID --NamespaceName NAMESPACE_NAME --IsPublic true --region `,然后重新触发同步 | +| `DescribeExternalEndpointStatus` 长时间为 `Opening` | 持续轮询 `tccli tcr DescribeExternalEndpointStatus --RegistryId REGISTRY_ID --region `(通常 1-2 分钟完成) | 后端资源预占延迟 | 若超过 5 分钟仍为 `Opening`,保留 RequestId 和 RegistryId,登录 [TCR 控制台](https://console.cloud.tencent.com/tcr) 查看详细状态;仍无法解决则 [提交工单](https://console.cloud.tencent.com/workorder) | +| 公网同步慢或耗时长 | `docker pull` 速度监控;`tccli tcr DescribeExternalEndpointStatus --RegistryId REGISTRY_ID --region ` 确认公网入口正常 | 公网带宽限制或镜像体积较大 | 建议改为通过专线/内网同步:使用方案 A 的 `ManageInternalEndpoint --Operation Create` | +| `CreateReplicationInstance` 成功但复制实例长时间不 Running | `tccli tcr DescribeReplicationInstanceCreateTasks --ReplicationRegistryId REPLICATION_REGISTRY_ID --ReplicationRegionId REPLICATION_REGION_ID --region ` 查看任务状态 | 后端创建缓慢(正常)或卡住(异常) | 继续等待;超过 15 分钟则保留 RegistryId、ReplicationRegistryId、RequestId -> 登录控制台查看详细状态 | +| Filter 不生效,同步了不期望的镜像 | 检查 `ManageReplication` JSON 中 `Filters[].Type` 字段 | `Type` 写成了大写 `Name` 而非小写 `name` | 修正为小写:`"Type": "name"`、`"Type": "tag"`、`"Type": "resource"`。执行 `DescribeReplicationPolicies` 可确认已生效的 Filter | + +## 下一步 + +- [跨实例(账号)同步镜像](../../ops/image-distribution/cross-instance-sync)(page_id `41945`)— TCR 实例间的镜像同步规则管理 +- [同实例多地域复制镜像](../../ops/image-distribution/cross-region-replication)(page_id `52095`)— 高级版实例多地域副本复制详解 +- [创建企业版实例](../../ops/instances/create) — 购买 TCR 企业版实例 +- [配置公网访问控制](../../ops/access/network/public-access) — 管理公网白名单(`CreateSecurityPolicy` / `DeleteSecurityPolicy`) +- [配置内网访问控制](../../ops/access/network/private-access) — VPC 内网链路管理(`ManageInternalEndpoint`) +- [管理命名空间](../../ops/image-creation/namespace) — 创建命名空间与镜像仓库 + +## 控制台替代 + +[容器镜像服务 -> 同步复制 -> 实例复制](https://console.cloud.tencent.com/tcr/replication):登录自建 Harbor -> 系统管理 -> 仓库管理 -> 新建目标(选择 "Tencent TCR" 或 "Docker Registry" 提供者)-> 复制管理 -> 新建规则 -> 触发同步。 diff --git a/src/content/docs/cli/tcr/practices/harbor-migration/harbor-migration-tcrctl.md b/src/content/docs/cli/tcr/practices/harbor-migration/harbor-migration-tcrctl.md new file mode 100644 index 0000000..fcf703e --- /dev/null +++ b/src/content/docs/cli/tcr/practices/harbor-migration/harbor-migration-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "从自建 Harbor 同步镜像到 TCR 企业版(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[从自建 Harbor 同步镜像到 TCR 企业版](https://cloud.tencent.com/document/product/1141/44970) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/practices/harbor-migration/harbor-migration.md b/src/content/docs/cli/tcr/practices/harbor-migration/harbor-migration.md new file mode 100644 index 0000000..12266b1 --- /dev/null +++ b/src/content/docs/cli/tcr/practices/harbor-migration/harbor-migration.md @@ -0,0 +1,1396 @@ +--- +title: "从自建 Harbor 同步镜像到 TCR 企业版" +description: "· page_id `44970`" +--- + +> 对照官方:[从自建 Harbor 同步镜像到 TCR 企业版](https://cloud.tencent.com/document/product/1141/44970) · page_id `44970` + +## 概述 + +将自建 Harbor(v1.8.0+)中的容器镜像和 Helm Chart 同步至腾讯云容器镜像服务(TCR)企业版。同步方向为 Harbor -> TCR(Push-based),由 Harbor 侧管理复制规则,自动或手动将镜像推送到 TCR 实例。 + +整体链路分为四层:**TCR 实例就绪**(步骤 1)-> **网络接入**(步骤 2)-> **访问凭证**(步骤 3)-> **TCR 侧复制基础设施**(步骤 4-5,可选)-> **Harbor 侧配置**(步骤 6-7,Web UI)-> **触发同步与查看结果**(步骤 8)。 + +``` + ┌──────────────────────┐ ┌────────────────────────────┐ + │ 自建 Harbor │ Push │ TCR 企业版 │ + │ (IDC / 云上 CVM) │ ──────> │ 域名: xxx.tencentcloudcr.com │ + │ │ Docker │ VPC 内网 / 公网 │ + │ Harbor v1.8.0+ │ pull-> │ │ + │ │ tag-> │ 镜像仓库 + Helm Chart │ + │ ┌─────────────────┐ │ push │ ↓ 可选:实例复制 │ + │ │ 复制规则 │ │ │ 副本实例(异地只读) │ + │ │ (Tencent TCR │ │ │ ManageReplication 同步 │ + │ │ 或 Docker │ │ │ │ + │ │ Registry 提供者) │ │ │ │ + │ └─────────────────┘ │ └────────────────────────────┘ + └──────────────────────┘ +``` + +**使用限制:** + +- 仅支持 Harbor v1.8.0 及以上版本。 +- Harbor v2.1.2 及以上可选择 "Tencent TCR" 提供者(使用 CAM SecretId/SecretKey),支持 Pull-based 模式且可在 TCR 侧自动新建命名空间。 +- Harbor 版本低于 v2.1.2 时只能选择 "Docker Registry" 提供者(使用 TCR 长期访问凭证),不支持在 TCR 侧自动新建命名空间。 +- 若自建 Harbor 无法通过专线或私有网络访问 TCR,可使用公网同步(会产生公网流量费用)。 +- TCR 侧多地域复制(`CreateReplicationInstance` + `ManageReplication`)仅高级版(`premium`)实例支持。 + +## 前置条件 + +- [环境准备](../../../index.md) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域 + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tcr:DescribeInstances, tcr:ManageInternalEndpoint, tcr:ManageExternalEndpoint +# tcr:DescribeExternalEndpointStatus, tcr:CreateSecurityPolicy, tcr:DescribeSecurityPolicies +# tcr:CreateInstanceToken, tcr:DescribeInstanceToken, tcr:DeleteInstanceToken +# tcr:CreateReplicationInstance, tcr:ManageReplication +# tcr:DescribeReplicationInstances, tcr:DescribeReplicationPolicies +# tcr:DescribeReplicationInstanceCreateTasks, tcr:DeleteReplicationInstance +# tcr:DeleteSecurityPolicy, tcr:DeleteReplicationRule +# 建议授予 QcloudTCRFullAccess 预设策略 +# 验证:执行 DescribeInstances 确认权限 +tccli tcr DescribeInstances --region +# expected: exit 0,返回实例列表(可为空) +``` + +### 资源检查 + +```bash +# 4. 确认 TCR 企业版实例存在且状态正常 +tccli tcr DescribeInstances --region --Registryids '["REGISTRY_ID"]' +# expected: exit 0, Registries[0].Status == "Running" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "example-registry.tencentcloudcr.com", + "RegionName": "ap-guangzhou" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +```bash +# 5. 确认自建 Harbor 可访问 TCR 实例域名(网络连通性预检) +curl -I https://PUBLIC_DOMAIN/v2/ 2>&1 | head -1 +# expected: HTTP/1.1 401 Unauthorized(401 表示可达,只是未认证;非 401 需排查网络) +``` + +> 自建 Harbor 版本 >= v1.8.0 由 Harbor 运维侧确认,不通过 tccli 验证。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 查看实例列表及状态 | `DescribeInstances` | 是 | +| 开启内网访问链路 | `ManageInternalEndpoint --Operation Create` | 否 | +| 开启公网访问入口 | `ManageExternalEndpoint --Operation Create` | 否 | +| 查看公网访问状态 | `DescribeExternalEndpointStatus` | 是 | +| 添加公网白名单 | `CreateSecurityPolicy` | 否 | +| 查看公网白名单 | `DescribeSecurityPolicies` | 是 | +| 创建长期访问凭证 | `CreateInstanceToken` | 否 | +| 查看已有凭证列表 | `DescribeInstanceToken` | 是 | +| 创建异地复制实例 | `CreateReplicationInstance` | 否 | +| 查看复制实例创建进度 | `DescribeReplicationInstanceCreateTasks` | 是 | +| 配置同步规则 | `ManageReplication` | 是 | +| 查看同步规则 | `DescribeReplicationPolicies` | 是 | +| 查看复制实例列表 | `DescribeReplicationInstances` | 是 | +| 删除同步规则 | `DeleteReplicationRule` | 是 | +| 删除复制实例 | `DeleteReplicationInstance` | 是 | +| 删除访问凭证 | `DeleteInstanceToken` | 是 | +| 删除公网白名单 | `DeleteSecurityPolicy` | 是 | +| 关闭公网访问入口 | `ManageExternalEndpoint --Operation Delete` | 是 | +| 删除内网访问链路 | `ManageInternalEndpoint --Operation Delete` | 是 | + +## 关键字段说明 + +以下说明本操作涉及的主要参数。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | TCR 实例 ID,由 `DescribeInstances` 返回,格式 `tcr-` 前缀 | `InvalidParameter.RegistryNotFound` — 实例不存在或地域错误 | +| `Region` | String | 是 | 地域名,如 `ap-guangzhou`、`ap-shanghai` | `InvalidParameter` — 地域与服务不匹配,命令无法路由 | +| `Operation` | String | 条件必填 | `Create` 或 `Delete`,大小写敏感。`ManageInternalEndpoint` 和 `ManageExternalEndpoint` 必填 | `InvalidParameter` — 操作类型未知 | +| `VpcId` | String | 条件必填 | 内网方案必填。VPC ID,格式 `vpc-` 前缀,由 `vpc:DescribeVpcs` 返回 | `FailedOperation` — VPC 不存在或不可用 | +| `SubnetId` | String | 条件必填 | 内网方案必填。子网 ID,格式 `subnet-` 前缀,须与实例同地域 | `FailedOperation` — 子网不存在或地域不匹配 | +| `CidrBlock` | String | 条件必填 | 公网方案必填。IPv4 CIDR,如 `1.2.3.4/32`。`Description` 不可为空 | `InvalidParameter` — CIDR 格式错误或描述为空 | +| `Description` | String | 条件必填 | 白名单用途描述,不可为空字符串 | `InvalidParameter` — Description 为空 | +| `TokenType` | String | 是 | 固定值 `longterm`(长期凭证),不可用其他值 | `InvalidParameter` — 不支持的凭证类型 | +| `ReplicationRegionId` | Integer | 是 | 目标地域数字 ID,如 `1`=广州、`4`=上海 | `UnsupportedOperation` — 地域不支持复制或实例类型不支持 | +| `ReplicationRegionName` | String | 是 | 目标地域名称,须与 `ReplicationRegionId` 对应 | `InvalidParameter` — RegionId 与 RegionName 不匹配 | +| `Rule.Name` | String | 是 | 同步规则名,同实例内不可重名。同名规则 `ManageReplication` 会覆盖更新(幂等) | 同一实例重名 — 幂等覆盖,非报错 | +| `Rule.DestNamespace` | String | 是 | 目标命名空间名,须在 TCR 侧已存在 | `FailedOperation` — 命名空间不存在,同步失败 | +| `Rule.Override` | Boolean | 否 | 是否覆盖同名镜像。建议 `true` | `false` 时同名 Tag 不同步 | +| `Rule.Filters[].Type` | String | 否 | 过滤器类型,须小写:`name`(仓库名)、`tag`(标签)、`resource`(资源类型) | 大写 `Name` 不识别,Filter 不生效 | +| `PolicyIndex` | Integer | 是(删除时) | 白名单策略序号,由 `DescribeSecurityPolicies` 返回 | `InvalidParameter` — 序号不存在 | +| `PolicyVersion` | String | 是(删除时) | 白名单策略版本号,由 `DescribeSecurityPolicies` 返回 | `InvalidParameter` — 版本号不匹配 | + +## 操作步骤 + +整体分为 TCR 侧准备(步骤 1-3 必选,步骤 4-5 可选)和 Harbor 侧配置(步骤 6-7),网络与凭证就绪后再进入 Harbor Web UI。 + +### 步骤 1:确认 TCR 实例可访问 + +查询实例信息,获取访问域名与状态: + +```bash +tccli tcr DescribeInstances \ + --Registryids '["REGISTRY_ID"]' \ + --region +# expected: exit 0, Registries[0].Status == "Running" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "example-registry.tencentcloudcr.com", + "RegionName": "ap-guangzhou", + "InternalEndpoint": "10.1.65.238" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +记录 `PublicDomain`(域名须以 `.tencentcloudcr.com` 结尾)。后续 Harbor 侧配置使用完整 URL:`https://PUBLIC_DOMAIN`。 + +### 步骤 2:配置 Harbor 到 TCR 的网络接入 + +根据自建 Harbor 的网络情况,选择内网或公网方案。 + +#### 方案 A:通过腾讯云私有网络访问(推荐) + +若自建 Harbor 部署在腾讯云 VPC 内或已通过专线/VPN 打通至腾讯云 VPC,使用内网访问可提升同步速度并节省公网流量费用。 + +##### 选择依据 + +- **方案选择**:选内网而非公网,因为专线/VPN 链路带宽稳定、无公网流量费用,且同步大量镜像时公网带宽可能成为瓶颈。 +- **VpcId**:选用自建 Harbor 所在或已打通的 VPC,而非新建 VPC — 新建 VPC 需额外打通与 Harbor 的链路。 +- **SubnetId**:选用与 TCR 实例同地域的子网,跨地域子网会导致连接超时。 + +##### 最小配置(仅必填字段) + +`tcr-manage-internal-endpoint-minimal.json`: + +```json +{ + "RegistryId": "REGISTRY_ID", + "Operation": "Create", + "VpcId": "VPC_ID", + "SubnetId": "SUBNET_ID" +} +``` + +##### 增强配置(含可选地域字段) + +`tcr-manage-internal-endpoint-enhanced.json`: + +```json +{ + "RegistryId": "REGISTRY_ID", + "Operation": "Create", + "VpcId": "VPC_ID", + "SubnetId": "SUBNET_ID", + "RegionId": 1, + "RegionName": "ap-guangzhou" +} +``` + +```bash +# 执行创建(任选一个配置文件) +tccli tcr ManageInternalEndpoint \ + --cli-input-json file://tcr-manage-internal-endpoint-minimal.json \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RegistryId": "tcr-example", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +开启自动解析后,VPC 内即可通过 `INSTANCE_NAME.tencentcloudcr.com` 内网访问 TCR 实例。 + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `REGISTRY_ID` | TCR 实例 ID | 格式 `tcr-` 前缀 | `tccli tcr DescribeInstances --region ` | +| `REGION` | 地域 | 如 `ap-guangzhou` | 与 TCR 实例地域一致 | +| `VPC_ID` | VPC ID | 格式 `vpc-` 前缀,须已存在 | `tccli vpc DescribeVpcs --region ` | +| `SUBNET_ID` | 子网 ID | 格式 `subnet-` 前缀,与实例同地域 | `tccli vpc DescribeSubnets --region ` | + +#### 方案 B:通过公网访问 + +若自建 Harbor 未部署在腾讯云 VPC 内且无法通过专线打通,使用公网访问。 + +##### B.1 开启公网访问入口 + +###### 选择依据 + +- **何时用公网**:仅当内网方案不可行(Harbor 不在腾讯云,无专线/VPN)时才选择。公网会产生流量费用,且同步速度受公网带宽限制。 +- **Operation=Create**:这是开启操作(非幂等),重复执行会返回 `UnsupportedOperation: The current public network access status is Opened`。执行 Create 前先通过 `DescribeExternalEndpointStatus` 预检,仅在 `Status == "Closed"` 时执行。 + +```bash +# 预检:确认公网入口当前状态为 Closed 再执行 Create +tccli tcr DescribeExternalEndpointStatus \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, Status == "Closed" 时方可执行 Create +``` + +**预期输出**(入口已关闭时): + +```json +{ + "Status": "Closed", + "Reason": "", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +```bash +tccli tcr ManageExternalEndpoint \ + --RegistryId REGISTRY_ID \ + --Operation Create \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RegistryId": "tcr-example", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +##### B.2 轮询公网访问状态至 Opened + +公网入口开启是异步操作,需轮询 `DescribeExternalEndpointStatus` 直到 `Status` 变为 `Opened`(通常需要 1-2 分钟): + +```bash +tccli tcr DescribeExternalEndpointStatus \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, Status 终态为 "Opened" +``` + +**预期输出**(开启中): + +```json +{ + "Status": "Opening", + "Reason": "", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +| Status | 含义 | +|--------|------| +| `Closed` | 公网访问入口已关闭 | +| `Opening` | 公网访问入口开启中 | +| `Opened` | 公网访问入口已开启 | +| `Closing` | 公网访问入口关闭中 | + +重复执行上述命令,直到返回 `"Status": "Opened"`,方可进行后续操作。 + +**多维度验证**(确认公网入口完整就绪,不只看 Status): + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 状态 | `DescribeExternalEndpointStatus.Status` | `Opened` | +| 网络 | `curl -I https://PUBLIC_DOMAIN/v2/` | 返回 `401 Unauthorized`(可达但未认证) | +| 白名单 | `DescribeSecurityPolicies` 返回(若已配置) | 策略列表非空 | + +##### B.3 添加公网白名单(安全策略) + +入口开启后仍默认拒绝全部来源的公网访问,需为 Harbor 出口 IP 添加白名单。 + +###### 选择依据 + +- **CidrBlock 格式**:使用 `/32` 掩码限定单个 IP(最小权限原则),而非 `/0` 放通全网。`0.0.0.0/0` 仅用于临时测试,同步完成后必须删除。 +- **Description**:必填且不可为空字符串,用于标识白名单用途,方便后续清理时识别。 + +```bash +tccli tcr CreateSecurityPolicy \ + --RegistryId REGISTRY_ID \ + --CidrBlock "HARBOR_EXIT_IP/32" \ + --Description "自建 Harbor 公网访问" \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RegistryId": "tcr-example", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +> 如无法确认 Harbor 的公网出口 IP,可临时配置 `--CidrBlock "0.0.0.0/0"` 以放通全部来源。**完成同步后务必尽快删除该白名单**(见[清理](#清理))。 + +查看已有白名单: + +```bash +tccli tcr DescribeSecurityPolicies \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "SecurityPolicySet": [ + { + "PolicyIndex": 0, + "CidrBlock": "HARBOR_EXIT_IP/32", + "Description": "自建 Harbor 公网访问", + "PolicyVersion": "1" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +### 步骤 3:获取 TCR 访问凭证 + +Harbor 配置复制目标时需要 TCR 的访问凭证。根据 Harbor 版本选择不同凭证类型: + +#### Harbor v2.1.2 及以上 — 使用 CAM API 密钥(推荐) + +前往 [API 密钥管理](https://console.cloud.tencent.com/cam/capi) 获取 **SecretId** 和 **SecretKey**: +- Harbor 侧「访问 ID」-> SecretId +- Harbor 侧「访问密码」-> SecretKey + +此方式支持 Pull-based 同步(TCR -> Harbor),且可在 TCR 侧自动新建命名空间。 + +#### Harbor 低于 v2.1.2 — 使用 TCR 长期访问凭证 + +##### 选择依据 + +- **TokenType**:必须选 `longterm` 而非 `temp`(临时凭证有过期时间,不适合长期同步任务)。长期凭证无过期限制。 +- **Desc**:建议填写可识别的描述(如 "自建 Harbor 数据同步专用"),方便后续通过 `DescribeInstanceToken` 识别该凭证用途。 + +创建专用的长期访问凭证: + +```bash +tccli tcr CreateInstanceToken \ + --RegistryId REGISTRY_ID \ + --TokenType longterm \ + --Desc "自建 Harbor 数据同步专用" \ + --region +# expected: exit 0, 返回 Username 和 Token +``` + +**预期输出**: + +```json +{ + "Username": "100012345678", + "Token": "eyJhbGciOiJSUzI1NiIsImtpZCI6...", + "ExpTime": 2096844746789, + "TokenId": "tcr-token-example", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +> 记录 `Token` 字段,**仅此一次保存机会**。在 Harbor 中填写「访问 ID」= `Username`,「访问密码」= `Token`。 + +查看已有凭证: + +```bash +tccli tcr DescribeInstanceToken \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, TotalCount > 0 +``` + +**预期输出**: + +```json +{ + "Tokens": [ + { + "Id": "tcr-token-example", + "Desc": "自建 Harbor 数据同步专用", + "RegistryId": "tcr-example", + "Enabled": true, + "CreatedAt": "2026-01-01T00:00:00+08:00", + "ExpiredAt": 2096844746789 + } + ], + "TotalCount": 1, + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `REGISTRY_ID` | TCR 实例 ID | 格式 `tcr-` 前缀 | `DescribeInstances` | +| `REGION` | 地域 | 如 `ap-guangzhou` | 与 TCR 实例地域一致 | + +### 步骤 4:在 TCR 侧创建复制实例(异地多活,可选 — 仅高级版) + +> 本步骤为可选扩展:若需要将 Harbor 同步至 TCR 的镜像进一步分发到多地域,可在目标地域创建复制实例(只读副本)。仅高级版(`premium`)实例支持。跳过本步骤则镜像仅存在于当前 TCR 实例。 + +##### 选择依据 + +- **何时需要**:当业务要求多地域就近拉取镜像时才创建复制实例。单地域使用场景跳过此步骤可以节省目标地域的实例费用。 +- **ReplicationRegionId**:选择离目标用户最近的地域(如华东用户选 `4`=上海),而非随机选择。 +- **前置条件**:确认实例类型为 `premium`,非高级版执行 `CreateReplicationInstance` 会直接报错。 +- **升级到高级版**:若实例类型为 `basic` 或 `standard`,需先通过 `ModifyInstance` 升级至 `premium`。升级命令零预付扣费(`PayMod=0`),操作立即生效,无需提前充值。 + +创建复制实例前确认实例为高级版: + +```bash +tccli tcr DescribeInstances \ + --Registryids '["REGISTRY_ID"]' \ + --region +# expected: exit 0, Registries[0].RegistryType == "premium" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "example-registry.tencentcloudcr.com", + "RegionName": "ap-guangzhou" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +确认 `RegistryType` 为 `"premium"` 后,创建复制实例。 + +若 `RegistryType` 非 `"premium"`(如返回 `"basic"` 或 `"standard"`),先执行升级: + +```bash +tccli tcr ModifyInstance \ + --RegistryId REGISTRY_ID \ + --RegistryType premium \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +升级后重新查询确认 `RegistryType` 已变为 `"premium"`: + +```bash +tccli tcr DescribeInstances \ + --Registryids '["REGISTRY_ID"]' \ + --region +# expected: exit 0, Registries[0].RegistryType == "premium" +``` + +确认后创建复制实例: + +```bash +tccli tcr CreateReplicationInstance \ + --RegistryId REGISTRY_ID \ + --ReplicationRegionId REPLICATION_REGION_ID \ + --ReplicationRegionName REPLICATION_REGION_NAME \ + --region +# expected: exit 0, 返回 ReplicationRegistryId +``` + +**预期输出**: + +```json +{ + "ReplicationRegistryId": "tcr-example-4-ghbzyc", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +常用地域 ID 参考: + +| 地域名称 | RegionId | 地域名称 | RegionId | +|---------|----------|---------|----------| +| ap-guangzhou | 1 | ap-shanghai | 4 | +| ap-beijing | 8 | ap-nanjing | 33 | +| ap-chengdu | 16 | ap-singapore | 9 | +| na-siliconvalley | 15 | eu-frankfurt | 17 | + +记录返回的 `ReplicationRegistryId`(格式如 `tcr-example-4-xxx`)。 + +**轮询复制实例创建进度:** + +复制实例创建是异步操作,需轮询确认完成: + +```bash +tccli tcr DescribeReplicationInstanceCreateTasks \ + --ReplicationRegistryId REPLICATION_REGISTRY_ID \ + --ReplicationRegionId REPLICATION_REGION_ID \ + --region +# expected: exit 0 +``` + +**预期输出**(创建完成): + +```json +{ + "Status": "SUCCESS", + "TaskDetail": [ + { + "TaskName": "CreateReplication", + "TaskUUID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "TaskStatus": "SUCCESS", + "TaskMessage": "", + "CreatedTime": "2026-01-01T00:00:00+08:00", + "FinishedTime": "2026-01-01T00:01:00+08:00" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +`TaskStatus` 为 `SUCCESS` 表示复制实例已就绪。 + +**多维度验证**(确认复制实例完整就绪): + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 任务状态 | `DescribeReplicationInstanceCreateTasks.TaskDetail[*].TaskStatus` | 全部 `SUCCESS` | +| 实例列表 | `DescribeReplicationInstances` 含目标实例 | `ReplicationRegistryId` 存在且 `Status: "Running"` | +| 源实例 | `DescribeInstances --Registryids '["REGISTRY_ID"]'` | 源实例 `Status: "Running"` | + +```bash +# 验证复制实例已存在于列表中 +tccli tcr DescribeReplicationInstances \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, ReplicationRegistries 中包含目标实例且 Status == "Running" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "ReplicationRegistries": [ + { + "RegistryId": "tcr-example", + "ReplicationRegistryId": "tcr-example-4-ghbzyc", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai", + "Status": "Running", + "CreatedAt": "2026-01-01T00:00:00+08:00" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +### 步骤 5:在 TCR 侧配置同步规则(可选 — 仅高级版) + +> 本步骤依赖步骤 4 创建的复制实例。若跳过了步骤 4,也跳过本步骤。 + +在源实例上创建同步规则,将 Harbor 同步过来的镜像自动复制到目标地域的副本实例。 + +##### 选择依据 + +- **Rule.Name**:建议使用描述性名称(如 `harbor-sync-to-shanghai`),便于在 `DescribeReplicationPolicies` 中区分多条规则。 +- **Rule.DestNamespace**:源与目标命名空间建议同名,减少路径混淆。若目标命名空间不存在,需先在 TCR 侧创建(参考[管理命名空间](../../ops/image-creation/namespace))。 +- **Rule.Override**:建议设为 `true`(覆盖同名镜像),避免因 Tag 已存在导致同步跳过。 +- **Rule.Deletion**:建议保持 `false`(不同步删除),防止 Harbor 侧误删导致 TCR 侧数据丢失。 +- **Rule.Filters**:`resource` 选 `image` 或 `chart` 视需求而定;`name` 用 `.*` 匹配全部,或用具体仓库名限定范围。**注意**:`Filters[].Type` 必须小写(`name`、`tag`、`resource`),大写 `Name` 不识别。 +- **幂等性**:`ManageReplication` 同名规则已存在则覆盖更新,可安全重放。 +- **跨地域限制**:从源实例地域调用 `ManageReplication` 时,若目标为异地复制实例,可能返回 `InternalError: Not support slave region`。此时需切换至目标地域(副实例所在地域)执行,或在控制台配置。 + +参数 >= 4 个,使用 `--cli-input-json file://`,分最小/增强两层。 + +**最小配置**(仅必填字段): + +`sync-rule-minimal.json`: + +```json +{ + "SourceRegistryId": "REGISTRY_ID", + "DestinationRegistryId": "REPLICATION_REGISTRY_ID", + "Rule": { + "Name": "RULE_NAME", + "DestNamespace": "DEST_NAMESPACE" + } +} +``` + +**增强配置**(含可选字段): + +`sync-rule-enhanced.json`: + +```json +{ + "SourceRegistryId": "REGISTRY_ID", + "DestinationRegistryId": "REPLICATION_REGISTRY_ID", + "Rule": { + "Name": "RULE_NAME", + "DestNamespace": "DEST_NAMESPACE", + "Override": true, + "Filters": [ + { + "Type": "name", + "Value": ".*" + }, + { + "Type": "resource", + "Value": "image" + } + ], + "Deletion": false + }, + "Description": "Harbor 同步镜像自动复制至异地" +} +``` + +```bash +# 执行创建(任选一个配置文件) +tccli tcr ManageReplication \ + --cli-input-json file://sync-rule-minimal.json \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +> `ManageReplication` 跨地域执行限制:从源实例地域调用时,若目标为异地复制实例,API 可能返回 `InternalError: Not support slave region`。**解决方案**:切换至目标地域(副实例所在地域,如 `ap-shanghai`)执行 `ManageReplication`,`SourceRegistryId` 填源实例、`DestinationRegistryId` 填本地实例。此操作在同地域内幂等:同名规则会覆盖更新。复制实例创建本身(步骤 4)不受此问题影响。 + +查看同步规则已生效: + +```bash +tccli tcr DescribeReplicationPolicies \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, TotalCount > 0 +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "ReplicationPolicyInfoList": [ + { + "ID": 1, + "Name": "RULE_NAME", + "Enabled": true, + "SrcResource": "INSTANCE_NAME/.* REGION_NAME", + "DestResource": "REPLICATION_REGISTRY_ID/DEST_NAMESPACE REPLICATION_REGION_NAME", + "Filters": [ + {"Type": "name", "Value": ".*"}, + {"Type": "resource", "Value": "image"} + ], + "Override": true, + "CreationTime": "2026-01-01T00:00:00+08:00" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `REGISTRY_ID` | 源 TCR 实例 ID | 格式 `tcr-` 前缀,须为 premium | `DescribeInstances` | +| `REPLICATION_REGISTRY_ID` | 目标复制实例 ID | 由 `CreateReplicationInstance` 返回 | `DescribeReplicationInstances` | +| `REGION` | 地域 | 如 `ap-guangzhou` | 与源实例地域一致 | +| `RULE_NAME` | 同步规则名称 | 同实例内不可重名 | 自定义,如 `harbor-sync-to-shanghai` | +| `DEST_NAMESPACE` | 目标命名空间 | 须在 TCR 侧已存在 | `tccli tcr DescribeNamespaces --RegistryId REGISTRY_ID --region ` | + +### 步骤 6:在 Harbor 侧配置复制目标(Harbor Web UI,无 TCR API) + +以管理员账号登录 Harbor Web UI,进入 **系统管理 > 仓库管理 > 新建目标**。 + +**Harbor v2.1.2 及以上配置(Tencent TCR 提供者):** + +| 字段 | 值 | +|------|-----| +| 提供者 | `Tencent TCR` | +| 目标名 | 自定义,如 `tencent-tcr` | +| 目标 URL | `https://PUBLIC_DOMAIN` | +| 访问 ID | CAM SecretId | +| 访问密码 | CAM SecretKey | +| 验证远程证书 | 保持默认 | + +**Harbor 低于 v2.1.2 配置(Docker Registry 提供者):** + +| 字段 | 值 | +|------|-----| +| 提供者 | `Docker Registry` | +| 目标名 | 自定义,如 `tencent-tcr` | +| 目标 URL | `https://PUBLIC_DOMAIN` | +| 访问 ID | `CreateInstanceToken` 返回的 `Username` | +| 访问密码 | `CreateInstanceToken` 返回的 `Token` | + +单击 **测试连接** 验证连通性。若失败,检查步骤 2 网络接入配置。 + +### 步骤 7:在 Harbor 侧创建复制规则(Harbor Web UI,无 TCR API) + +进入 **系统管理 > 复制管理 > 新建规则**: + +| 字段 | 值 | +|------|-----| +| 名称 | 自定义,如 `sync-images-to-tcr` | +| 复制模式 | `Push-based`(Harbor 推送至 TCR)。使用 "Tencent TCR" 提供者且 Harbor v2.1.2+ 时也可选 `Pull-based` | +| 源资源过滤器 | 留空则同步全部镜像与 Helm Chart;支持按仓库名/Tag 过滤 | +| 目的 Registry | 选择步骤 6 创建的目标仓库 | +| 目的 Namespace | 留空则默认同名命名空间("Tencent TCR" 提供者可自动创建;"Docker Registry" 提供者需先在 TCR 侧手动创建:参考[管理命名空间](../../ops/image-creation/namespace)) | +| 触发模式 | `手动触发` 或 `事件驱动`(推送时自动同步) | +| 覆盖 | 建议勾选(覆盖同名资源) | + +### 步骤 8:触发同步并查看结果 + +以下演示手动同步流程。若复制规则为事件驱动,将镜像推送至 Harbor 后会自动触发同步。 + +**推送到 Harbor:** + +```bash +# 从 Docker Hub 拉取测试镜像 +docker pull nginx:latest + +# 打上 Harbor 项目标签 +docker tag nginx:latest HARBOR_DOMAIN/PROJECT/nginx:latest + +# 推送至自建 Harbor +docker push HARBOR_DOMAIN/PROJECT/nginx:latest +``` + +**从 TCR 侧验证镜像已到达:** + +```bash +# 登录 TCR +docker login PUBLIC_DOMAIN --username=USERNAME --password=TOKEN + +# 拉取同步后的镜像 +docker pull PUBLIC_DOMAIN/NAMESPACE/nginx:latest +``` + +**从 TCR 侧查看同步策略与复制实例:** + +查看复制策略列表(Harbor 创建的规则也会在此显示): + +```bash +tccli tcr DescribeReplicationPolicies \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, TotalCount > 0 +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "ReplicationPolicyInfoList": [ + { + "ID": 1, + "Name": "sync-images-to-tcr", + "Enabled": true, + "SrcResource": "HARBOR_DOMAIN/PROJECT/** REGION_NAME", + "DestResource": "PUBLIC_DOMAIN/NAMESPACE/** REGION_NAME", + "Filters": [ + {"Type": "name", "Value": ".*"}, + {"Type": "tag", "Value": ""}, + {"Type": "resource", "Value": "image"} + ], + "Override": true, + "CreationTime": "2026-01-01T00:00:00+08:00" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +查看复制实例列表: + +```bash +tccli tcr DescribeReplicationInstances \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "ReplicationRegistries": [ + { + "RegistryId": "tcr-example", + "ReplicationRegistryId": "REPLICATION_REGISTRY_ID", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai", + "Status": "Running", + "CreatedAt": "2026-01-01T00:00:00+08:00" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +若使用了实例复制(步骤 4-5),可查看复制实例中的同步任务进度: + +```bash +tccli tcr DescribeReplicationInstanceCreateTasks \ + --ReplicationRegistryId REPLICATION_REGISTRY_ID \ + --ReplicationRegionId REPLICATION_REGION_ID \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "Status": "SUCCESS", + "TaskDetail": [ + { + "TaskName": "SyncTask", + "TaskUUID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "TaskStatus": "SUCCESS", + "TaskMessage": "", + "CreatedTime": "2026-01-01T00:00:00+08:00", + "FinishedTime": "2026-01-01T00:01:00+08:00" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +## 验证 + +### 控制面(tccli) + +```bash +# 确认实例状态为 Running +tccli tcr DescribeInstances \ + --Registryids '["REGISTRY_ID"]' \ + --region +# expected: exit 0, Registries[0].Status == "Running" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "example-registry.tencentcloudcr.com", + "RegionName": "ap-guangzhou", + "InternalEndpoint": "10.1.65.238" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +```bash +# 确认公网入口已开启(若使用公网方案) +tccli tcr DescribeExternalEndpointStatus \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, Status == "Opened" +``` + +**预期输出**: + +```json +{ + "Status": "Opened", + "Reason": "", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +```bash +# 确认访问凭证可查询 +tccli tcr DescribeInstanceToken \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, TotalCount > 0, Token Enabled: true +``` + +**预期输出**: + +```json +{ + "Tokens": [ + { + "Id": "tcr-token-example", + "Desc": "自建 Harbor 数据同步专用", + "RegistryId": "tcr-example", + "Enabled": true, + "CreatedAt": "2026-01-01T00:00:00+08:00", + "ExpiredAt": 2096844746789 + } + ], + "TotalCount": 1, + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +```bash +# 确认 Harbor 侧的同步策略已被 TCR 识别 +tccli tcr DescribeReplicationPolicies \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, TotalCount > 0, 策略 Enabled: true +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "ReplicationPolicyInfoList": [ + { + "ID": 1, + "Name": "sync-images-to-tcr", + "Enabled": true, + "SrcResource": "HARBOR_DOMAIN/PROJECT/**", + "DestResource": "PUBLIC_DOMAIN/NAMESPACE/**", + "Override": true, + "CreationTime": "2026-01-01T00:00:00+08:00" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +```bash +# 确认复制实例存在且状态正常(若使用) +tccli tcr DescribeReplicationInstances \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, ReplicationRegistries[*].Status == "Running" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "ReplicationRegistries": [ + { + "RegistryId": "tcr-example", + "ReplicationRegistryId": "REPLICATION_REGISTRY_ID", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai", + "Status": "Running", + "CreatedAt": "2026-01-01T00:00:00+08:00" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +**多维度验证汇总**: + +| 维度 | 命令 | 预期 | +|------|------|------| +| 实例状态 | `DescribeInstances --Registryids '["REGISTRY_ID"]'` | `Status: "Running"` | +| 公网入口(若使用) | `DescribeExternalEndpointStatus` | `Status: "Opened"` | +| 访问凭证 | `DescribeInstanceToken` | `TotalCount > 0`,`Enabled: true` | +| 复制策略 | `DescribeReplicationPolicies` | `TotalCount > 0`,策略 `Enabled: true` | +| 复制实例(若使用) | `DescribeReplicationInstances` | 实例 `Status: "Running"` | + +### 数据面(docker) + +```bash +# 登录 TCR +docker login PUBLIC_DOMAIN --username=USERNAME --password=TOKEN + +# 确认镜像已同步至 TCR +docker pull PUBLIC_DOMAIN/NAMESPACE/REPOSITORY:TAG +``` + +## 清理 + +> **计费警告**:TCR 企业版实例按量计费,按实例规格和存储用量收费。若不再需要实例,请前往[销毁退还实例](../../ops/instances/delete)彻底删除以停止计费。复制实例(步骤 4 创建)也独立计费。 +> +> **副作用警告**: +> - `DeleteInstanceToken`:删除凭证后,所有依赖该凭证的 Harbor 复制目标将立即失效,同步中断。 +> - `DeleteReplicationRule`:删除同步规则后,该规则停止生效,后续 Harbor 推送的镜像不再自动复制到异地实例。 +> - `DeleteReplicationInstance`:删除复制实例后,目标地域的只读副本被销毁,已同步的镜像数据不可恢复。 +> - `DeleteSecurityPolicy`:删除白名单后,对应 IP 将无法通过公网访问 TCR 实例。 +> - `ManageInternalEndpoint --Operation Delete`:删除内网链路后,VPC 内所有服务将无法通过内网访问 TCR 实例。 +> +> 清理顺序为依赖倒序:先删规则(依赖实例),再删复制实例(依赖源实例),再删凭证和网络配置。 + +### 数据面(docker) + +```bash +# 登出 TCR +docker logout PUBLIC_DOMAIN + +# 清理本地已同步的测试镜像(可选) +docker rmi PUBLIC_DOMAIN/NAMESPACE/REPOSITORY:TAG +``` + +### 控制面(tccli) + +**1. 删除同步规则(若创建了步骤 5 的 ManageReplication 规则):** + +查询当前规则以获取 `RuleId`: + +```bash +tccli tcr DescribeReplicationPolicies \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, 记录待删规则的 ID 字段 +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "ReplicationPolicyInfoList": [ + { + "ID": 1, + "Name": "RULE_NAME", + "Enabled": true + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +```bash +tccli tcr DeleteReplicationRule \ + --RegistryId REGISTRY_ID \ + --RuleId RULE_ID \ + --region +# expected: exit 0 +``` + +验证规则已删除: + +```bash +tccli tcr DescribeReplicationPolicies \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, 列表中不再包含已删规则 +``` + +**2. 删除复制实例(若创建了步骤 4 的复制实例):** + +清理前确认目标实例存在: + +```bash +tccli tcr DescribeReplicationInstances \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, 确认 ReplicationRegistryId 为目标实例 +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "ReplicationRegistries": [ + { + "RegistryId": "tcr-example", + "ReplicationRegistryId": "REPLICATION_REGISTRY_ID", + "Status": "Running" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +```bash +tccli tcr DeleteReplicationInstance \ + --RegistryId REGISTRY_ID \ + --ReplicationRegistryId REPLICATION_REGISTRY_ID \ + --region +# expected: exit 0 +``` + +验证复制实例已删除: + +```bash +tccli tcr DescribeReplicationInstances \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, TotalCount == 0 或 ReplicationRegistries 为空 +``` + +**预期输出**: + +```json +{ + "TotalCount": 0, + "ReplicationRegistries": null, + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +**3. 删除同步专用访问凭证:** + +清理前确认凭证存在: + +```bash +tccli tcr DescribeInstanceToken \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, 记录待删凭证的 Id(即 DeleteInstanceToken 所需 --TokenId 的值) +``` + +**预期输出**: + +```json +{ + "Tokens": [ + { + "Id": "tcr-token-example", + "Desc": "自建 Harbor 数据同步专用", + "Enabled": true + } + ], + "TotalCount": 1, + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +```bash +tccli tcr DeleteInstanceToken \ + --RegistryId REGISTRY_ID \ + --TokenId TOKEN_ID \ + --region +# expected: exit 0 +``` + +验证凭证已删除: + +```bash +tccli tcr DescribeInstanceToken \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, Tokens 列表中不再包含已删凭证 +``` + +**4. 清理公网白名单和入口(若使用了公网方案):** + +清理前查询已有白名单: + +```bash +tccli tcr DescribeSecurityPolicies \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, 记录各策略的 PolicyIndex 和 PolicyVersion +``` + +**预期输出**: + +```json +{ + "SecurityPolicySet": [ + { + "PolicyIndex": 0, + "CidrBlock": "HARBOR_EXIT_IP/32", + "Description": "自建 Harbor 公网访问", + "PolicyVersion": "1" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +逐个删除白名单: + +```bash +tccli tcr DeleteSecurityPolicy \ + --RegistryId REGISTRY_ID \ + --PolicyIndex POLICY_INDEX \ + --PolicyVersion POLICY_VERSION \ + --region +# expected: exit 0 +``` + +> `PolicyIndex` 和 `PolicyVersion` 均来自 `DescribeSecurityPolicies` 返回的 `SecurityPolicySet[*]` 中各字段。 + +验证白名单已清空: + +```bash +tccli tcr DescribeSecurityPolicies \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, SecurityPolicySet 为空 +``` + +关闭公网访问入口: + +```bash +tccli tcr ManageExternalEndpoint \ + --RegistryId REGISTRY_ID \ + --Operation Delete \ + --region +# expected: exit 0 +``` + +验证公网入口已关闭: + +```bash +tccli tcr DescribeExternalEndpointStatus \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, Status == "Closed" +``` + +**预期输出**: + +```json +{ + "Status": "Closed", + "Reason": "", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +**5. 清理内网访问链路(若使用了内网方案):** + +清理前确认链路存在: + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0 +``` + +删除内网链路: + +`tcr-manage-internal-endpoint-delete.json`: + +```json +{ + "RegistryId": "REGISTRY_ID", + "Operation": "Delete", + "VpcId": "VPC_ID", + "SubnetId": "SUBNET_ID" +} +``` + +```bash +tccli tcr ManageInternalEndpoint \ + --cli-input-json file://tcr-manage-internal-endpoint-delete.json \ + --region +# expected: exit 0 +``` + +验证已删除: + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, AccessVpcSet 为 null 或 TotalCount == 0 +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ManageInternalEndpoint` 返回 `FailedOperation` | `tccli vpc DescribeVpcs --region ` 和 `tccli vpc DescribeSubnets --region ` 确认 VPC 和子网均存在且状态正常 | VPC ID 或子网 ID 不存在 / 不可用 | 修正 `VpcId` 和 `SubnetId` 为有效值,且确保子网与 TCR 实例同地域 | +| `ManageExternalEndpoint --Operation Create` 返回 `UnsupportedOperation` | `tccli tcr DescribeExternalEndpointStatus --RegistryId REGISTRY_ID --region ` 确认当前状态 | 公网入口已为 `Opened` 状态,重复执行 Create(非幂等)。错误信息含 "The current public network access status is Opened" | 仅在 `Status == "Closed"` 时执行 Create。若当前为 `Opened`,表示入口已就绪,跳过开启步骤直接使用 | +| `CreateSecurityPolicy` 返回 `InvalidParameter` | `tccli tcr DescribeSecurityPolicies --RegistryId REGISTRY_ID --region ` 确认现有策略列表 | `CidrBlock` 格式不正确(非合法 IPv4 CIDR)或 `Description` 为空字符串 | 修正为合法 IPv4 地址段(如 `1.2.3.4/32`),`Description` 不可为空 | +| `CreateReplicationInstance` 返回 `UnsupportedOperation` | `tccli tcr DescribeInstances --Registryids '["REGISTRY_ID"]' --region ` 确认 `RegistryType` | 实例非高级版(`premium`)或目标地域不支持复制 | 升级实例至高级版:`tccli tcr ModifyInstance --RegistryId REGISTRY_ID --RegistryType premium --region `,或选择支持的地域 | +| `ManageReplication` 返回 `InternalError: Not support slave region` | `tccli tcr DescribeReplicationInstances --RegistryId REGISTRY_ID --region ` 确认复制实例 `Status` 为 `"Running"`;检查当前执行地域是否为源实例地域 | 从源实例地域执行跨地域复制规则设置,API 受限 | 切换至目标地域(副实例所在地域)执行 `ManageReplication`,指定 `SourceRegistryId` 为源实例、`DestinationRegistryId` 为本地实例;或通过 [TCR 控制台](https://console.cloud.tencent.com/tcr/replication) 在目标地域配置 | +| `CreateInstanceToken` 返回 `InvalidParameter` | 检查 `TokenType` 参数值 | `TokenType` 不是 `longterm` | 修正为 `--TokenType longterm` | +| `DeleteSecurityPolicy` 返回 `InvalidParameter` | `tccli tcr DescribeSecurityPolicies --RegistryId REGISTRY_ID --region ` 确认 `PolicyIndex` 和 `PolicyVersion` | `PolicyIndex` 不存在或 `PolicyVersion` 不匹配 | 使用 `DescribeSecurityPolicies` 返回的实际 `PolicyIndex` 和 `PolicyVersion` | +| `CamNoAuth` 或权限拒绝 | `tccli tcr DescribeInstances --region ` 确认当前凭证是否有 TCR 读权限 | 子账号缺少所需 CAM 权限 | 此为环境限制,非命令错误。授予 `QcloudTCRFullAccess` 预设策略,或参考[企业版授权方案示例](https://cloud.tencent.com/document/product/1141/41417) | +| `LimitExceeded` | `tccli vpc DescribeVpcs --region ` 统计已有 VPC 数量 | 当前账号 VPC 数量已达上限 | 此为环境限制,非命令错误。清理不再使用的 VPC 后重试,或使用已有 VPC 和子网 | +| `TradeFailed` 或计费相关错误 | 检查账户余额和实例配额 | 账户欠费或实例配额不足 | 此为环境限制,非命令错误。前往控制台充值或调整配额 | + +### 创建成功但状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| Harbor 测试连接失败 | `tccli tcr DescribeExternalEndpointStatus --RegistryId REGISTRY_ID --region ` 确认状态;内网方案:`tccli tcr DescribeInternalEndpoints --RegistryId REGISTRY_ID --region ` 确认链路状态 | 网络不通(公网入口未开启/内网链路未就绪)或凭证错误 | 公网:确保 `Status=Opened`;内网:确保 `AccessVpcSet[0].Status=Enabled`;两种方案均检查凭证正确性 | +| Harbor 提供者列表中无 "Tencent TCR" | Harbor Web UI 版本号确认 | Harbor 版本低于 v2.1.2 | 选择 "Docker Registry" 提供者,使用 `CreateInstanceToken` 返回的凭证(非功能缺陷,是版本限制) | +| `CreateInstanceToken` 返回的 Token 丢失 | 无(Token 仅返回一次,不可恢复) | Token 创建时未妥善保存 | 先删除旧 Token:`tccli tcr DeleteInstanceToken --RegistryId REGISTRY_ID --TokenId TOKEN_ID --region `,再重新 `CreateInstanceToken` | +| `DescribeReplicationPolicies` 返回空 | Harbor Web UI 确认复制规则已创建;`tccli tcr DescribeReplicationInstances --RegistryId REGISTRY_ID --region ` 确认复制实例存在 | Harbor 侧尚未创建复制规则或规则未生效(非 tccli 问题) | 确认 Harbor 复制规则已创建并已触发同步 | +| 同步完成后 TCR 侧命名空间不存在 | `tccli tcr DescribeNamespaces --RegistryId REGISTRY_ID --region ` 确认命名空间是否存在 | "Docker Registry" 提供者不支持自动创建命名空间 | 先在 TCR 侧手动创建:`tccli tcr CreateNamespace --RegistryId REGISTRY_ID --NamespaceName NAMESPACE_NAME --IsPublic true --region `,然后重新触发同步 | +| `DescribeExternalEndpointStatus` 长时间为 `Opening` | 持续轮询 `tccli tcr DescribeExternalEndpointStatus --RegistryId REGISTRY_ID --region `(通常 1-2 分钟完成) | 后端资源预占延迟 | 若超过 5 分钟仍为 `Opening`,保留 RequestId 和 RegistryId,登录 [TCR 控制台](https://console.cloud.tencent.com/tcr) 查看详细状态;仍无法解决则 [提交工单](https://console.cloud.tencent.com/workorder) | +| 公网同步慢或耗时长 | `docker pull` 速度监控;`tccli tcr DescribeExternalEndpointStatus --RegistryId REGISTRY_ID --region ` 确认公网入口正常 | 公网带宽限制或镜像体积较大 | 建议改为通过专线/内网同步:使用方案 A 的 `ManageInternalEndpoint --Operation Create` | +| `CreateReplicationInstance` 成功但复制实例长时间不 Running | `tccli tcr DescribeReplicationInstanceCreateTasks --ReplicationRegistryId REPLICATION_REGISTRY_ID --ReplicationRegionId REPLICATION_REGION_ID --region ` 查看任务状态 | 后端创建缓慢(正常)或卡住(异常) | 继续等待;超过 15 分钟则保留 RegistryId、ReplicationRegistryId、RequestId -> 登录控制台查看详细状态 | +| Filter 不生效,同步了不期望的镜像 | 检查 `ManageReplication` JSON 中 `Filters[].Type` 字段 | `Type` 写成了大写 `Name` 而非小写 `name` | 修正为小写:`"Type": "name"`、`"Type": "tag"`、`"Type": "resource"`。执行 `DescribeReplicationPolicies` 可确认已生效的 Filter | + +## 下一步 + +- [跨实例(账号)同步镜像](../../ops/image-distribution/cross-instance-sync)(page_id `41945`)— TCR 实例间的镜像同步规则管理 +- [同实例多地域复制镜像](../../ops/image-distribution/cross-region-replication)(page_id `52095`)— 高级版实例多地域副本复制详解 +- [创建企业版实例](../../ops/instances/create) — 购买 TCR 企业版实例 +- [配置公网访问控制](../../ops/access/network/public-access) — 管理公网白名单(`CreateSecurityPolicy` / `DeleteSecurityPolicy`) +- [配置内网访问控制](../../ops/access/network/private-access) — VPC 内网链路管理(`ManageInternalEndpoint`) +- [管理命名空间](../../ops/image-creation/namespace) — 创建命名空间与镜像仓库 + +## 控制台替代 + +[容器镜像服务 -> 同步复制 -> 实例复制](https://console.cloud.tencent.com/tcr/replication):登录自建 Harbor -> 系统管理 -> 仓库管理 -> 新建目标(选择 "Tencent TCR" 或 "Docker Registry" 提供者)-> 复制管理 -> 新建规则 -> 触发同步。 diff --git a/src/content/docs/cli/tcr/practices/hybrid-cloud-sync-tcrctl.md b/src/content/docs/cli/tcr/practices/hybrid-cloud-sync-tcrctl.md new file mode 100644 index 0000000..7583f3e --- /dev/null +++ b/src/content/docs/cli/tcr/practices/hybrid-cloud-sync-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "" +description: "# 产品 CLI 操作" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +# 产品 CLI 操作 + +> 页型 P1 · 使用 crctl 完成本页任务 · P0 仅提供 stub 占位,P1 阶段完成正文。 + diff --git a/src/content/docs/cli/tcr/practices/hybrid-cloud-sync.md b/src/content/docs/cli/tcr/practices/hybrid-cloud-sync.md new file mode 100644 index 0000000..b327ba4 --- /dev/null +++ b/src/content/docs/cli/tcr/practices/hybrid-cloud-sync.md @@ -0,0 +1,994 @@ +--- +title: "混合云下的多平台镜像数据同步复制" +description: "· page_id `60740`" +--- + +> 对照官方:[混合云下的多平台镜像数据同步复制](https://cloud.tencent.com/document/product/1141/60740) · page_id `60740` + +## 概述 + +混合云场景下,容器镜像需要在跨主账号、跨地域、跨国、跨平台的多个容器镜像仓库之间同步复制。TCR 提供实例复制、实例同步、镜像迁移工具及自定义域名等多项能力,覆盖以下三类典型场景。 + +| 场景 | 说明 | 核心能力 | 实例规格要求 | +|------|------|---------|------------| +| 跨地域实例复制 | 国内/跨国多地域间同步镜像,实现就近拉取 | 实例复制 + 实例同步 | 高级版 | +| 跨平台镜像迁移或同步 | 公有云/自建仓库/多家云平台间的镜像流转 | image-transfer + Harbor 同步 | 无特殊要求 | +| DevOps 镜像流转 | 开发、测试、生产环境间的镜像自动传递 | 实例同步 | 标准版或高级版 | + +> **使用限制:** +> - 实例同步功能需要实例规格为**标准版或高级版**,basic 实例不支持。 +> - 实例复制功能需要实例规格为**高级版**,basic/standard 实例不支持。 +> - 暂不支持跨国实例复制(需使用实例同步 + 本地实例复制替代)。 + +## 前置条件 + +- [环境准备](../../../index.md) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置 + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tcr:DescribeInstances, tcr:CreateReplicationInstance, tcr:DescribeReplicationInstances +# tcr:DescribeReplicationInstanceCreateTasks, tcr:ManageReplication +# tcr:DescribeReplicationPolicies, tcr:DeleteReplicationRule +# tcr:DeleteReplicationInstance, tcr:ManageInternalEndpoint +# tcr:DescribeInternalEndpoints, tcr:DescribeInstanceCustomizedDomain +# tcr:DescribeReplicationInstanceSyncStatus, tcr:ModifyInstance +# 验证:执行 DescribeInstances 确认权限 +tccli tcr DescribeInstances --region ap-guangzhou +# expected: exit 0,返回实例列表(可为空) +``` + +### 资源检查 + +```bash +# 4. 确认主实例存在且状态正常 +tccli tcr DescribeInstances --region --Registryids '["REGISTRY_ID"]' +# expected: exit 0, Status: "Running" + +# 5. 确认实例规格满足场景需求(场景一需高级版,场景三需标准版或高级版) +tccli tcr DescribeInstances --region --Registryids '["REGISTRY_ID"]' +# expected: exit 0, RegistryType 为 "premium"(场景一)或 "standard"/"premium"(场景三) + +# 6. (场景三)确认目标实例的命名空间已预先创建 +tccli tcr DescribeNamespaces --region --RegistryId DEST_REGISTRY_ID +# expected: exit 0, 目标命名空间在列表中 + +# 7. (跨主账号场景)确认目标侧提供的凭证可访问目标实例 +tccli tcr DescribeInstances --region PEER_REGION --Registryids '["PEER_REGISTRY_ID"]' +# expected: exit 0,使用目标账号凭证执行,返回目标实例信息 +``` + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 查看实例列表 | `DescribeInstances` | 是 | +| 查看已有复制实例 | `DescribeReplicationInstances` | 是 | +| 创建复制实例(新建从实例) | `CreateReplicationInstance` | 否 | +| 查询复制实例创建任务进度 | `DescribeReplicationInstanceCreateTasks` | 是 | +| 配置 VPC 内网接入 | `ManageInternalEndpoint` | 是(相同 VPC 重复接入不会报错) | +| 查看同步策略列表 | `DescribeReplicationPolicies` | 是 | +| 创建/更新同步规则 | `ManageReplication` | 否(作为创建时) | +| 查看同步状态 | `DescribeReplicationInstanceSyncStatus` | 是 | +| 查看自定义域名 | `DescribeInstanceCustomizedDomain` | 是 | +| 删除同步规则 | `DeleteReplicationRule` | 是 | +| 删除从实例 | `DeleteReplicationInstance` | 是 | +| 升级实例规格 | `ModifyInstance` | 是(已为目标规格则无操作) | + +## 关键字段说明 + +以下说明本页面涉及的主要 API 的核心参数。完整参数定义见各 API 的 `--generate-cli-skeleton` 输出。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | 格式 `tcr-` 开头,长度 22。主实例 ID | `InvalidParameter`:实例不存在或无权访问 | +| `ReplicationRegionId` | Integer | 是 | 目标地域数字 ID,如 `4`(ap-shanghai)。取值见[地域列表](https://cloud.tencent.com/document/product/1141/39265) | 写入错误地域后不可回退,需删除从实例重新创建 | +| `ReplicationRegionName` | String | 是 | 目标地域名称,如 `ap-shanghai`,须与 `ReplicationRegionId` 一致 | `InvalidParameter`:与 RegionId 不匹配 | +| `SyncTag` | Boolean | 否 | `true`:同步 Tag;`false`:不同步。默认 `false` | 省略时 Tag 不会被同步到从实例 | +| `SourceRegistryId` | String | 是 | 源实例 ID,格式 `tcr-` 开头 | `InvalidParameter`:实例不存在 | +| `DestinationRegistryId` | String | 是 | 目标实例 ID,格式 `tcr-` 开头 | `InvalidParameter`:实例不存在或跨地域不可达 | +| `Rule.Name` | String | 是 | 字母数字及 `-._`,以字母或数字开头 | `InvalidParameter`:命名不符合规范 | +| `Rule.DestNamespace` | String | 是 | 目标实例中已存在的命名空间名 | `ResourceNotFound`:命名空间不存在 | +| `Rule.Override` | Boolean | 否 | `true`:覆盖同名镜像;`false`:跳过。默认 `false` | 设为 `false` 时若镜像已存在则同步失败 | +| `Rule.Deletion` | Boolean | 否 | `true`:源删除则目标也删除;`false`:保留。默认 `false` | 设为 `true` 会级联删除目标仓库镜像 | +| `Rule.Filters` | Array | 是 | `[{"Type":"name","Value":"正则"}]`,至少一个。`Value` 为 Go 正则 | 正则错误导致零镜像匹配 | +| `DestinationRegionId` | Integer | 是 | 目标实例所在地域数字 ID | 地域错误导致同步不可达 | +| `PeerReplicationOption.PeerRegistryUin` | String | 否 | 目标主账号 UIN,跨账号必填 | 跨账号场景缺省则无权限访问 | +| `PeerReplicationOption.PeerRegistryToken` | String | 否 | 目标实例用户级长期访问密码,跨账号必填 | 误用服务级凭证则同步持续失败,且错误信息不明确 | +| `EnablePeerReplication` | Boolean | 否 | `true`:启用跨账号同步。默认为同账号 | 跨账号场景漏填则退化为同账号同步 | +| `VpcId` | String | 是 | 格式 `vpc-xxxxxxxx`,目标地域内的 VPC | `InvalidParameter`:VPC 不存在或非目标地域 | +| `SubnetId` | String | 是 | 格式 `subnet-xxxxxxxx`,属于指定 VPC | `InvalidParameter`:子网不存在或不在 VPC 下 | +| `Operation` | String | 是 | `Create`:新增接入;`Delete`:移除接入 | 填写错误导致操作语义相反 | + +## 操作步骤 + +### 场景一:跨地域实例复制 + +当业务部署在多个地域时,使用**实例复制**功能实现单地域上传、多地域高速实时同步、就近内网拉取。此场景需要**高级版**实例。 + +#### 步骤 1:确认主实例规格 + +##### 选择依据 + +- **实例规格**:实例复制仅高级版支持。若当前实例为 basic 或 standard,需先升级至 premium。 +- **升级方式**:basic 到 premium 为在线升级,不中断服务,零预付扣费。 + +```bash +tccli tcr DescribeInstances \ + --region \ + --Registryids '["REGISTRY_ID"]' +# expected: exit 0, RegistryType: "premium", Status: "Running" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "example-registry.tencentcloudcr.com", + "RegionName": "ap-guangzhou", + "InternalEndpoint": "10.0.0.1" + } + ] +} +``` + +若 `RegistryType` 不是 `premium`,执行升级: + +```bash +tccli tcr ModifyInstance \ + --RegistryId REGISTRY_ID \ + --RegistryType premium \ + --region +# expected: exit 0 +``` + +升级为异步操作,轮询确认变更生效: + +```bash +tccli tcr DescribeInstances \ + --region \ + --Registryids '["REGISTRY_ID"]' +# expected: exit 0, RegistryType: "premium", Status: "Running" +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `REGISTRY_ID` | 主实例 ID | 格式 `tcr-xxxxxxxxxxxx` | `tccli tcr DescribeInstances` | +| `REGION` | 主实例所在地域 | 如 `ap-guangzhou` | `tccli tcr DescribeInstances` 返回的 `RegionName` | + +#### 步骤 2:查看当前复制实例关系 + +```bash +tccli tcr DescribeReplicationInstances \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0 +``` + +**预期输出**(首次创建时为空): + +```json +{ + "TotalCount": 0, + "ReplicationRegistries": null +} +``` + +确认当前未配置复制实例,避免在同一地域重复创建。 + +#### 步骤 3:创建从实例 + +##### 选择依据 + +- **ReplicationRegionId**:选择目标地域的数字 ID。业务就近访问优先选择用户集中的地域(如华东选 `4`/ap-shanghai),降低镜像拉取延迟。 +- **SyncTag**:选 `true` 以保持 Tag 一致性。若 Tag 量大且仅需部分 Tag,可选 `false` 以减少同步开销。 +- **`--region` 参数**:始终指向主实例所在地域,即使创建的是其他地域的从实例。 + +##### 最小配置(只含必填字段) + +`replication_instance-minimal.json`: + +```json +{ + "RegistryId": "", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai" +} +``` + +```bash +tccli tcr CreateReplicationInstance \ + --cli-input-json file://replication_instance-minimal.json \ + --region ap-guangzhou +# expected: exit 0, 返回 ReplicationRegistryId +``` + +##### 增强配置(含可选字段) + +`replication_instance-enhanced.json`: + +```json +{ + "RegistryId": "", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai", + "SyncTag": true +} +``` + +```bash +tccli tcr CreateReplicationInstance \ + --cli-input-json file://replication_instance-enhanced.json \ + --region ap-guangzhou +# expected: exit 0, 返回 ReplicationRegistryId +``` + +**预期输出**: + +```json +{ + "ReplicationRegistryId": "tcr-example-4-example", + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | 主实例 ID | 格式 `tcr-xxxxxxxxxxxx` | `tccli tcr DescribeInstances` | +| `ReplicationRegionId` | 目标地域数字 ID | 如 `4`(ap-shanghai) | 见[地域列表](https://cloud.tencent.com/document/product/1141/39265) | +| `ReplicationRegionName` | 目标地域名称 | 如 `ap-shanghai` | 同上 | +| `SyncTag` | 是否同步 Tag | `true` 或 `false` | 自定义 | + +记录返回的 `ReplicationRegistryId`(格式为 `--xxxxx`)。 + +#### 步骤 4:轮询等待复制实例就绪 + +`CreateReplicationInstance` 是异步操作,需轮询检查创建任务进度。 + +```bash +tccli tcr DescribeReplicationInstanceCreateTasks \ + --ReplicationRegistryId REPLICATION_REGISTRY_ID \ + --ReplicationRegionId 4 \ + --region +# expected: exit 0, 所有子任务 TaskStatus 均为 "SUCCESS" +``` + +**预期输出**(所有任务完成时): + +```json +{ + "TaskDetail": [ + { + "TaskName": "SyncDBTask", + "TaskUUID": "tcr-example-4-example-db", + "TaskStatus": "SUCCESS", + "TaskMessage": "success" + }, + { + "TaskName": "CreateTcrCrdTask", + "TaskUUID": "tcr-example-4-example-crd", + "TaskStatus": "SUCCESS", + "TaskMessage": "success" + } + ] +} +``` + +| 占位符 | 说明 | 获取方式 | +|--------|------|---------| +| `REPLICATION_REGISTRY_ID` | 从实例 ID | 步骤 3 `CreateReplicationInstance` 返回值 | + +若子任务 `TaskStatus` 仍为 `IN_PROGRESS` 或 `PENDING`,等待 10-30 秒后重试。直到 `TaskDetail` 中所有子任务均为 `SUCCESS`。 + +##### 多维度验证 + +轮询结束后,从以下维度交叉验证复制实例是否真正可用: + +| 维度 | 检查内容 | 命令 | 预期 | +|------|---------|------|------| +| 任务完成 | 所有子任务 TaskStatus | `DescribeReplicationInstanceCreateTasks` | 均为 `SUCCESS` | +| 实例状态 | 从实例 Status | `DescribeReplicationInstances` | `Running` | +| 实例标识 | ReplicationRegistryId | `DescribeReplicationInstances` 返回 | 格式 `tcr-*-X-*`,非空 | +| 创建时间 | CreatedAt | `DescribeReplicationInstances` 返回 | 为当前时间前后 5 分钟内 | + +#### 步骤 5:确认从实例状态 + +```bash +tccli tcr DescribeReplicationInstances \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, Status: "Running" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "ReplicationRegistries": [ + { + "ReplicationRegistryId": "tcr-example-4-example", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai", + "Status": "Running", + "CreatedAt": "2026-01-01T00:00:00+08:00" + } + ] +} +``` + +确认 `Status` 为 `Running` 且 `ReplicationRegistryId` 与创建时返回的一致。 + +#### 步骤 6:将复制地域内的 VPC 接入复制实例 + +复制实例创建后,需将目标地域内需要拉取镜像的 VPC 接入该实例,实现就近内网拉取。详见[配置内网访问控制](../../ops/access/network/private-access)。 + +##### 选择依据 + +- **Operation**:选 `Create` 新增接入。若 VPC 已接入则使用已有链路。 +- **VpcId / SubnetId**:选择属于复制实例所在地域(`ReplicationRegionId` 对应地域)的 VPC 和子网。跨地域 VPC 不可用。 +- **RegionId / RegionName**:须与复制实例所在地域一致,否则接入链路不可达。 + +`internal_endpoint-minimal.json`: + +```json +{ + "RegistryId": "", + "Operation": "Create", + "VpcId": "", + "SubnetId": "" +} +``` + +```bash +tccli tcr ManageInternalEndpoint \ + --cli-input-json file://internal_endpoint-minimal.json \ + --region +# expected: exit 0 +``` + +`internal_endpoint-enhanced.json`(含地域参数): + +```json +{ + "RegistryId": "", + "Operation": "Create", + "VpcId": "", + "SubnetId": "", + "RegionId": 4, + "RegionName": "ap-shanghai" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | 主实例 ID | 格式 `tcr-xxxxxxxxxxxx` | `tccli tcr DescribeInstances` | +| `` | VPC 实例 ID | 格式 `vpc-xxxxxxxx`,属于复制实例所在地域 | `tccli vpc DescribeVpcs --region REPLICATION_REGION` | +| `` | 子网 ID | 格式 `subnet-xxxxxxxx`,属于指定 VPC | `tccli vpc DescribeSubnets --region REPLICATION_REGION` | + +> **注意:** `ManageInternalEndpoint` 可能因 VPC 接入配额已满(`LimitExceeded`)而失败。若遇此情况,清理不再使用的 VPC 接入后重试。 + +#### 跨国场景的折中方案 + +跨国场景下暂不支持直接实例复制。可采用组合方案: +1. 在国内和国外分别创建高级版实例 +2. 配置实例同步规则(`ManageReplication`)实现跨国按需同步(参见[场景三](#场景三devops-镜像流水线)中的同步规则配置) +3. 在上述国内外实例上分别执行 `CreateReplicationInstance`,在各自区域内创建从实例 + +最终效果:国内单点推送,实例复制到各国内地域;国外通过同步规则拉取,实例复制到各国外地域。 + +### 场景二:跨平台镜像迁移 + +多平台间(TCR、Docker Hub、Harbor、阿里云 ACR 等)的镜像迁移或同步,主要使用 image-transfer 工具和 Harbor 自建中转方案。此场景**不受实例规格限制**,basic 实例即可。 + +#### 方式一:使用 image-transfer 工具 + +[image-transfer](https://github.com/tkestack/image-transfer) 是腾讯云开源镜像迁移工具,支持 Docker Registry V2 标准的所有镜像仓库(TCR、Docker Hub、Quay、ACR、Harbor 等)间的批量迁移。 + +操作流程: +1. 编写认证鉴权文件 `auth.json`,配置源和目标仓库的访问凭证 +2. 编写迁移规则文件 `transfer.json`,定义镜像的源/目标映射关系 +3. 运行 image-transfer 容器 + +`auth.json` 示例: + +```json +{ + "src-registry.example.com": { + "username": "SRC_USERNAME", + "password": "SRC_PASSWORD", + "insecure": false + }, + "example-registry.tencentcloudcr.com": { + "username": "TCR_USERNAME", + "password": "TCR_PASSWORD", + "insecure": false + } +} +``` + +`transfer.json` 示例: + +```json +{ + "src-registry.example.com": { + "source-ns/source-repo:tag": "example-registry.tencentcloudcr.com/dest-ns/dest-repo:tag" + } +} +``` + +执行迁移: + +```bash +docker run --rm \ + -v "$(pwd)/auth.json:/app/auth.json" \ + -v "$(pwd)/transfer.json:/app/transfer.json" \ + tkestack/image-transfer +# expected: 镜像迁移成功日志,exit 0 +``` + +> **说明:** +> - image-transfer 运行在本地终端或服务器上,不属于 tccli 边界,但属于场景二的核心操作工具。 +> - `insecure: true` 表示跳过 TLS 验证,仅适用于非生产环境的自建仓库。 +> - 详细用法参见 [image-transfer README](https:/github.com/tkestack/image-transfer/blob/main/readme.md)。 + +**一键迁移模式(CCR 个人版到 TCR 企业版):** 参见[个人版迁移至企业版完全指南](https://cloud.tencent.com/document/product/1141/52292),该模式封装了认证和迁移规则,简化操作流程。 + +#### 方式二:通过自建 Harbor 实现跨平台实时同步 + +若需在第三方平台间实时同步镜像,可使用自建 Harbor 作为中转枢纽: + +1. 在 Harbor 配置 Pull-based 复制策略,将源平台的镜像定期拉取至 Harbor +2. 在 Harbor 配置 Push-based 复制策略,将 Harbor 内的镜像推送至目标平台 + +> **支持的镜像仓库服务:** Docker Hub、Docker Registry、AWS ECR、Azure ACR、阿里云 ACR、GCR、华为 SWR、Artifact Hub、GitLab、Quay、JFrog Artifactory、TCR。 + +此方式无需 TCR tccli 操作。详见[从自建 Harbor 同步镜像到 TCR 企业版](https://cloud.tencent.com/document/product/1141/44970)。 + +#### 使用自定义域名保证服务连续性 + +从其他镜像仓库迁移至 TCR 时,可为 TCR 实例配置自定义域名,沿用原有仓库域名,保持发布配置和 CI/CD 流程不变。 + +查看已有自定义域名: + +```bash +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "DomainInfoList": [ + { + "RegistryId": "tcr-example", + "CertId": "example-cert-id", + "DomainName": "test.example.com", + "Status": "SUCCESS" + } + ], + "TotalCount": 1 +} +``` + +`Status` 枚举:`SUCCESS`(绑定成功)、`FAILED`(绑定失败)、`PROCESSING`(处理中)。 + +配置自定义域名操作参见[配置自定义域名](../../ops/access/domain/custom-domain),涉及 SSL 证书上传(`tccli ssl UploadCertificate`)和 DNS CNAME 解析。 + +### 场景三:DevOps 镜像流水线 + +利用 TCR 实例同步功能,实现开发、测试、预发布、生产环境之间的镜像自动流转。此场景需要**标准版或高级版**实例。 + +#### 步骤 1:查看当前同步策略列表 + +```bash +tccli tcr DescribeReplicationPolicies \ + --RegistryId DEV_REGISTRY_ID \ + --region +# expected: exit 0 +``` + +**预期输出**(首次时为空): + +```json +{ + "TotalCount": 0, + "ReplicationPolicyInfoList": null +} +``` + +确认当前无已配置的同步规则。跨账号场景请同时参见[跨实例(账号)同步镜像](../../ops/image-distribution/cross-instance-sync)。 + +#### 步骤 2:配置同步规则 + +在开发环境实例上创建同步规则,将镜像自动推送到测试环境实例。 + +##### 选择依据 + +- **Rule.Override**:选 `true` 以确保每次推送都用最新镜像覆盖目标。若需保留历史版本则选 `false`,但需确保 Tag 唯一避免冲突。 +- **Rule.Deletion**:选 `false` 以保留目标仓库镜像,防止源端误删导致目标也丢失。仅当需要严格镜像生命周期同步时才选 `true`。 +- **Rule.Filters**:使用 `".*"` 匹配所有镜像。生产环境建议限制范围(如 `"release-.*"`),避免调试镜像污染生产环境。 +- **DestinationRegionId**:选目标实例所在地域的数字 ID,跨地域同步需确保网络可达。 + +##### 最小配置(只含必填字段) + +`sync_dev_to_test-minimal.json`: + +```json +{ + "SourceRegistryId": "", + "DestinationRegistryId": "", + "Rule": { + "Name": "dev-to-test", + "DestNamespace": "", + "Filters": [ + { + "Type": "name", + "Value": ".*" + } + ] + }, + "DestinationRegionId": 1 +} +``` + +```bash +tccli tcr ManageReplication \ + --cli-input-json file://sync_dev_to_test-minimal.json \ + --region +# expected: exit 0 +``` + +##### 增强配置(含可选字段) + +`sync_dev_to_test-enhanced.json`: + +```json +{ + "SourceRegistryId": "", + "DestinationRegistryId": "", + "Rule": { + "Name": "dev-to-test", + "DestNamespace": "", + "Override": true, + "Filters": [ + { + "Type": "name", + "Value": ".*" + } + ], + "Deletion": false + }, + "Description": "开发环境到测试环境自动同步", + "DestinationRegionId": 1 +} +``` + +```bash +tccli tcr ManageReplication \ + --cli-input-json file://sync_dev_to_test-enhanced.json \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | 源(开发)实例 ID | 格式 `tcr-xxxxxxxxxxxx` | `tccli tcr DescribeInstances` | +| `` | 目标(测试)实例 ID | 格式 `tcr-xxxxxxxxxxxx` | 同上 | +| `` | 目标实例命名空间 | 需在目标实例中预先创建 | `tccli tcr DescribeNamespaces --region --RegistryId TEST_REGISTRY_ID` | +| `DestinationRegionId` | 目标实例所在地域数字 ID | 如 `1`(ap-guangzhou) | 见[地域列表](https://cloud.tencent.com/document/product/1141/39265) | + +同一主账号内,按同样模式配置后续环节(测试到预发布、预发布到生产): + +- `test-to-staging`:`DestNamespace: "staging"` +- `staging-to-prod`:`DestNamespace: "production"` + +#### 步骤 3:验证同步规则已生效 + +```bash +tccli tcr DescribeReplicationPolicies \ + --RegistryId DEV_REGISTRY_ID \ + --region +# expected: exit 0, Enabled: true +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "ReplicationPolicyInfoList": [ + { + "ID": 2, + "Name": "dev-to-test", + "Enabled": true, + "SrcResource": "example-registry/.* ap-guangzhou", + "DestResource": "tcr-example-1/example-ns ap-guangzhou", + "Filters": [ + {"Type": "name", "Value": ".*"} + ], + "Override": true + } + ] +} +``` + +确认 `Enabled` 为 `true` 且 `Filters` 与预期一致。 + +#### 步骤 4:查看同步状态 + +```bash +tccli tcr DescribeReplicationInstanceSyncStatus \ + --RegistryId DEV_REGISTRY_ID \ + --ReplicationRegistryId REPLICATION_REGISTRY_ID \ + --ShowReplicationLog true \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "ReplicationStatus": "Succeed", + "ReplicationTime": "2026-01-01T00:05:00+08:00", + "ReplicationLog": { + "ReplicationTime": "2026-01-01T00:05:00+08:00", + "TaskId": 42, + "Status": "Succeed", + "Percentage": 100, + "Total": 3 + } +} +``` + +`ReplicationStatus` 枚举:`Succeed`(同步成功)、`InProgress`(同步中)、`Failed`(同步失败)、`Cancel`(已取消)。 + +当 `Status` 为 `Failed` 时,通过 `ShowReplicationLog` 查看具体失败原因。 + +| 占位符 | 说明 | 获取方式 | +|--------|------|---------| +| `DEV_REGISTRY_ID` | 源实例 ID | `tccli tcr DescribeInstances` | +| `REPLICATION_REGISTRY_ID` | 目标实例 ID | 同上 | + +#### 步骤 5(可选):跨主账号同步 + +若不同环境分属不同的腾讯云主账号,在步骤 2 的 `ManageReplication` 请求中增加 `PeerReplicationOption`。 + +##### 选择依据 + +- **PeerRegistryToken**:必须使用**用户级账号**长期访问密码,服务级账号凭证不支持跨主账号同步。选取长期凭证以避免同步因凭证过期中断。 +- **EnablePeerReplication**:设为 `true`。此字段是跨账号开关,漏填则退化为同账号同步。 + +##### 最小配置(只含必填字段) + +`sync_cross_account-minimal.json`: + +```json +{ + "SourceRegistryId": "", + "DestinationRegistryId": "", + "Rule": { + "Name": "dev-to-test-cross-account", + "DestNamespace": "", + "Filters": [ + { + "Type": "name", + "Value": ".*" + } + ] + }, + "DestinationRegionId": 1, + "PeerReplicationOption": { + "PeerRegistryUin": "", + "PeerRegistryToken": "", + "EnablePeerReplication": true + } +} +``` + +```bash +tccli tcr ManageReplication \ + --cli-input-json file://sync_cross_account-minimal.json \ + --region +# expected: exit 0 +``` + +##### 增强配置(含可选字段) + +`sync_cross_account-enhanced.json`: + +```json +{ + "SourceRegistryId": "", + "DestinationRegistryId": "", + "Rule": { + "Name": "dev-to-test-cross-account", + "DestNamespace": "", + "Override": true, + "Filters": [ + { + "Type": "name", + "Value": ".*" + } + ], + "Deletion": false + }, + "Description": "跨主账号同步:开发到测试", + "DestinationRegionId": 1, + "PeerReplicationOption": { + "PeerRegistryUin": "", + "PeerRegistryToken": "", + "EnablePeerReplication": true + } +} +``` + +| 占位符 | 说明 | 获取方式 | +|--------|------|---------| +| `` | 目标主账号 UIN | 目标账号基本信息页 | +| `` | 目标实例长期访问凭证 | 目标实例 [用户级账号](https://cloud.tencent.com/document/product/1141/41829) 密码 | +| `` | 目标实例 ID | 目标侧 `tccli tcr DescribeInstances` | + +> **注意:** +> - 仅支持[用户级账号](https://cloud.tencent.com/document/product/1141/41829),不支持[服务级账号](https://cloud.tencent.com/document/product/1141/89137)。 +> - 同步规则的生命周期与目标账号最新添加规则的访问凭证生命周期保持一致,请使用长期访问凭证。 + +#### 交付流水线(CODING DevOps) + +对于更复杂的 DevOps 需求,可使用腾讯云 [CODING DevOps](https://console.cloud.tencent.com/coding) 一站式平台。TCR 的交付流水线功能依赖于 CODING DevOps 的 CI/CD 能力,支持推送代码自动触发镜像构建和部署、本地推送镜像后自动触发部署。 + +> 交付流水线非 tccli 范围,详见[使用交付流水线实现容器 DevOps](https://cloud.tencent.com/document/product/1141/48186)。 + +## 验证 + +### 控制面(tccli) + +**实例复制验证:** + +```bash +# 1. 确认从实例状态 +tccli tcr DescribeReplicationInstances \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, Status: "Running", ReplicationRegistryId 非空 + +# 2. 确认复制任务全部完成 +tccli tcr DescribeReplicationInstanceCreateTasks \ + --ReplicationRegistryId REPLICATION_REGISTRY_ID \ + --ReplicationRegionId REPLICATION_REGION_ID \ + --region +# expected: exit 0, 所有 TaskDetail[].TaskStatus 均为 "SUCCESS" +``` + +**实例同步验证:** + +```bash +# 3. 确认同步规则已启用 +tccli tcr DescribeReplicationPolicies \ + --RegistryId DEV_REGISTRY_ID \ + --region +# expected: exit 0, Enabled: true + +# 4. 确认同步状态为成功 +tccli tcr DescribeReplicationInstanceSyncStatus \ + --RegistryId DEV_REGISTRY_ID \ + --ReplicationRegistryId REPLICATION_REGISTRY_ID \ + --ShowReplicationLog true \ + --region +# expected: exit 0, ReplicationStatus: "Succeed" +``` + +**自定义域名验证:** + +```bash +# 5. 确认域名绑定状态 +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, Status: "SUCCESS" +``` + +### 数据面 + +**镜像迁移验证(TCR 侧):** + +```bash +tccli tcr DescribeImages \ + --RegistryId REGISTRY_ID \ + --NamespaceName NAMESPACE \ + --RepositoryName REPO \ + --region +# expected: 目标镜像 Tag 在列表中 +``` + +**自定义域名 DNS 解析验证:** + +```bash +nslookup CUSTOM_DOMAIN +# expected: 解析结果为 CNAME 指向 TCR 实例域名 +``` + +**镜像拉取验证(从复制实例):** + +```bash +docker pull REPLICATION_REGISTRY_DOMAIN/NAMESPACE/REPO:TAG +# expected: 镜像拉取成功 +``` + +## 清理 + +> **计费提醒:** TCR 企业版实例按规格和时长计费。实例复制产生的从实例按主实例规格计费,删除从实例或同步规则不会自动退还已产生的费用。请确认无需继续使用后再执行清理操作。 + +### 数据面 + +清理 image-transfer 产生的本地凭证文件(含敏感信息): + +```bash +rm auth.json transfer.json +# expected: 文件已删除 +``` + +若在 Harbor 配置了中转复制策略,迁移完成后在 Harbor UI 停用并删除对应规则。 + +若为 TCR 实例配置了自定义域名且不再需要,删除 SSL 证书绑定和 DNS CNAME 记录(参见[配置自定义域名](../../ops/access/domain/custom-domain))。 + +### 控制面(tccli) + +按依赖倒序依次清理:同步规则 -> VPC 内网接入链路 -> 从实例。 + +> **副作用警告:** `DeleteReplicationInstance` 会删除从实例及其所有关联数据,从实例上的镜像和配置将永久丢失。`DeleteReplicationRule` 仅删除同步规则本身,不会删除已同步的镜像数据。 + +#### 步骤 1:清理前状态检查 + +先确认待清理的资源列表: + +```bash +# 查看同步规则 +tccli tcr DescribeReplicationPolicies \ + --RegistryId REGISTRY_ID \ + --region +# expected: 记录 Rule ID,确认是待删除的规则 + +# 查看复制实例 +tccli tcr DescribeReplicationInstances \ + --RegistryId REGISTRY_ID \ + --region +# expected: 记录 ReplicationRegistryId,确认是待删除的从实例 + +# 查看 VPC 内网接入(如有) +tccli tcr DescribeInternalEndpoints \ + --RegistryId REGISTRY_ID \ + --region +# expected: 记录 VpcId,确认接入链路 +``` + +#### 步骤 2:删除同步规则 + +```bash +tccli tcr DeleteReplicationRule \ + --RegistryId REGISTRY_ID \ + --RuleId RULE_ID \ + --region +# expected: exit 0 +``` + +验证已删除: + +```bash +tccli tcr DescribeReplicationPolicies \ + --RegistryId REGISTRY_ID \ + --region +# expected: TotalCount: 0 +``` + +#### 步骤 3:删除 VPC 内网接入链路(如有) + +`delete_endpoint.json`: + +```json +{ + "RegistryId": "", + "Operation": "Delete", + "VpcId": "" +} +``` + +```bash +tccli tcr ManageInternalEndpoint \ + --cli-input-json file://delete_endpoint.json \ + --region +# expected: exit 0 +``` + +验证已删除: + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId REGISTRY_ID \ + --region +# expected: TotalCount: 0 +``` + +#### 步骤 4:删除从实例 + +```bash +tccli tcr DeleteReplicationInstance \ + --RegistryId REGISTRY_ID \ + --ReplicationRegistryId REPLICATION_REGISTRY_ID \ + --ReplicationRegionId REPLICATION_REGION_ID \ + --region +# expected: exit 0 +``` + +验证已删除: + +```bash +tccli tcr DescribeReplicationInstances \ + --RegistryId REGISTRY_ID \ + --region +# expected: TotalCount: 0 +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `CreateReplicationInstance` 返回 `UnsupportedOperation` | `tccli tcr DescribeInstances --region --Registryids '["REGISTRY_ID"]'` 查看 `RegistryType` | 实例非高级版,basic/standard 实例不支持实例复制 | 升级实例规格:`tccli tcr ModifyInstance --RegistryId REGISTRY_ID --RegistryType premium --region `;或改用实例同步(`ManageReplication`)替代 | +| `ManageReplication` 返回 `UnsupportedOperation` | `tccli tcr DescribeInstances --region --Registryids '["REGISTRY_ID"]'` 查看 `RegistryType` | 实例为 basic 版,不支持实例同步 | 升级实例规格:`tccli tcr ModifyInstance --RegistryId REGISTRY_ID --RegistryType standard --region `;若无法升级,使用 image-transfer 工具手动同步 | +| `ManageReplication` 返回 `InvalidParameter` | 检查 `Rule.Name` 的组成字符和长度 | 规则名称不符合命名规范(仅支持字母数字及 `-._`,以字母或数字开头) | 修正 `Rule.Name`,确保符合规范,如 `"dev-to-test"` | +| `ManageReplication` 返回 `InternalError`,错误信息:"Not support slave region" | `tccli tcr DescribeReplicationInstances --RegistryId REGISTRY_ID --region ` 确认当前实例是否为复制从实例 | 当前实例是通过 `CreateReplicationInstance` 创建的从实例(slave),从实例不支持作为 `ManageReplication` 的 `SourceRegistryId` 发起同步规则 | 在源主实例(非 slave 的主实例)上执行 `ManageReplication`。从实例仅用于接收镜像复制,不支持创建同步规则作为同步源 | +| 跨主账号同步返回权限错误 | `tccli tcr DescribeInstances --region PEER_REGION --Registryids '["PEER_REGISTRY_ID"]'` 使用目标账号凭证确认目标实例存在且可访问 | `PeerRegistryToken` 使用了服务级账号凭证或不正确的密码 | 改用目标实例的[用户级账号](https://cloud.tencent.com/document/product/1141/41829)长期访问密码,重新执行 `ManageReplication` | +| 删除实例时返回 "please delete the replication rule first" | `tccli tcr DescribeReplicationInstances --RegistryId REGISTRY_ID --region ` 查看关联的从实例;`tccli tcr DescribeReplicationPolicies --RegistryId REGISTRY_ID --region ` 查看关联的同步规则 | 实例关联了活动的从实例或同步规则,不允许直接删除主实例 | 先执行 `DeleteReplicationInstance` 删除所有从实例,再执行 `DeleteReplicationRule` 删除所有同步规则,最后再删除主实例 | +| `ManageInternalEndpoint` 返回 `LimitExceeded` | `tccli tcr DescribeInternalEndpoints --RegistryId REGISTRY_ID --region ` 查看当前接入的 VPC 数量 | 当前实例 VPC 接入数量已达上限(此为环境限制,非命令错误) | 清理不再使用的 VPC 接入链路:`tccli tcr ManageInternalEndpoint --cli-input-json file://delete_endpoint.json --region `(Operation: "Delete"),或使用已有 VPC 和子网 | +| `CreateReplicationInstance` 返回 `FailedOperation.TradeFailed` | 检查账户余额 | 账户余额不足,创建高级版从实例需预付费用(此为环境限制,非命令错误) | 先创建 basic 实例(后付费无预付):`tccli tcr CreateInstance --RegistryType basic --region REPLICATION_REGION`,再执行 `tccli tcr ModifyInstance --RegistryType premium --region REPLICATION_REGION` 升级;若升级也触发余额限制,需充值 | + +### 创建成功但状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 实例同步长时间处于 `InProgress` | `tccli tcr DescribeReplicationInstanceSyncStatus --RegistryId REGISTRY_ID --ReplicationRegistryId REPLICATION_REGISTRY_ID --ShowReplicationLog true --region ` 查看 `Percentage` | 待同步镜像数量多或跨地域网络延迟 | 等待自动完成,通过 `Percentage` 观察进度。若超过预期时间仍无进展,检查源和目标实例的网络连通性 | +| 复制实例创建后 `Status` 长时间非 `Running` | `tccli tcr DescribeReplicationInstanceCreateTasks --ReplicationRegistryId REPLICATION_REGISTRY_ID --ReplicationRegionId REPLICATION_REGION_ID --region ` 查看各子任务状态 | 后端任务(SyncDBTask / CreateTcrCrdTask)执行超时,可能因地域资源紧张 | 若某子任务 `TaskStatus` 为 `FAILED`,记录 `TaskMessage` 和 `RequestId` 后 [提交工单](https://console.cloud.tencent.com/workorder);若均为 `IN_PROGRESS`,继续轮询(间隔 30 秒),最长等待约 15 分钟 | +| `DescribeReplicationInstanceSyncStatus` 返回 `ReplicationStatus: "Failed"` | `tccli tcr DescribeReplicationInstanceSyncStatus --RegistryId REGISTRY_ID --ReplicationRegistryId REPLICATION_REGISTRY_ID --ShowReplicationLog true --region ` 查看 `ReplicationLog` 中具体错误 | 同步过程中镜像拉取或推送失败(源/目标仓库不可达、认证过期、镜像损坏等) | 根据 `ReplicationLog` 中的错误信息定位:认证问题则更新 `PeerRegistryToken`;网络问题则检查目标实例内网/公网可达性;镜像问题则重新推送源镜像。修复后重新触发同步 | +| `DescribeInstanceCustomizedDomain` 返回 `Status: "FAILED"` | 检查 `DomainName` 对应的 DNS CNAME 记录和 SSL 证书 | CNAME 未正确配置或 SSL 证书与域名不匹配 | 确认 DNS CNAME 指向 TCR 实例域名,确认 SSL 证书包含该自定义域名 | + +## 下一步 + +- [全球多地域间同步镜像实现就近访问](../global-replication) -- 实例复制与同步实战 +- [跨实例(账号)同步镜像](../../ops/image-distribution/cross-instance-sync) -- 跨主账号同步规则详解 +- [同实例多地域复制镜像](../../ops/image-distribution/cross-region-replication) -- 实例复制详细操作 +- [配置内网访问控制](../../ops/access/network/private-access) -- VPC 内网链路管理 +- [配置自定义域名](../../ops/access/domain/custom-domain) -- 统一域名管理 + +## 控制台替代 + +- [容器镜像服务控制台 -- 同步复制](https://console.cloud.tencent.com/tcr/replication) diff --git a/src/content/docs/cli/tcr/practices/hybrid-cloud-sync/hybrid-cloud-sync-tcrctl.md b/src/content/docs/cli/tcr/practices/hybrid-cloud-sync/hybrid-cloud-sync-tcrctl.md new file mode 100644 index 0000000..7583f3e --- /dev/null +++ b/src/content/docs/cli/tcr/practices/hybrid-cloud-sync/hybrid-cloud-sync-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "" +description: "# 产品 CLI 操作" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +# 产品 CLI 操作 + +> 页型 P1 · 使用 crctl 完成本页任务 · P0 仅提供 stub 占位,P1 阶段完成正文。 + diff --git a/src/content/docs/cli/tcr/practices/hybrid-cloud-sync/hybrid-cloud-sync.md b/src/content/docs/cli/tcr/practices/hybrid-cloud-sync/hybrid-cloud-sync.md new file mode 100644 index 0000000..b327ba4 --- /dev/null +++ b/src/content/docs/cli/tcr/practices/hybrid-cloud-sync/hybrid-cloud-sync.md @@ -0,0 +1,994 @@ +--- +title: "混合云下的多平台镜像数据同步复制" +description: "· page_id `60740`" +--- + +> 对照官方:[混合云下的多平台镜像数据同步复制](https://cloud.tencent.com/document/product/1141/60740) · page_id `60740` + +## 概述 + +混合云场景下,容器镜像需要在跨主账号、跨地域、跨国、跨平台的多个容器镜像仓库之间同步复制。TCR 提供实例复制、实例同步、镜像迁移工具及自定义域名等多项能力,覆盖以下三类典型场景。 + +| 场景 | 说明 | 核心能力 | 实例规格要求 | +|------|------|---------|------------| +| 跨地域实例复制 | 国内/跨国多地域间同步镜像,实现就近拉取 | 实例复制 + 实例同步 | 高级版 | +| 跨平台镜像迁移或同步 | 公有云/自建仓库/多家云平台间的镜像流转 | image-transfer + Harbor 同步 | 无特殊要求 | +| DevOps 镜像流转 | 开发、测试、生产环境间的镜像自动传递 | 实例同步 | 标准版或高级版 | + +> **使用限制:** +> - 实例同步功能需要实例规格为**标准版或高级版**,basic 实例不支持。 +> - 实例复制功能需要实例规格为**高级版**,basic/standard 实例不支持。 +> - 暂不支持跨国实例复制(需使用实例同步 + 本地实例复制替代)。 + +## 前置条件 + +- [环境准备](../../../index.md) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置 + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tcr:DescribeInstances, tcr:CreateReplicationInstance, tcr:DescribeReplicationInstances +# tcr:DescribeReplicationInstanceCreateTasks, tcr:ManageReplication +# tcr:DescribeReplicationPolicies, tcr:DeleteReplicationRule +# tcr:DeleteReplicationInstance, tcr:ManageInternalEndpoint +# tcr:DescribeInternalEndpoints, tcr:DescribeInstanceCustomizedDomain +# tcr:DescribeReplicationInstanceSyncStatus, tcr:ModifyInstance +# 验证:执行 DescribeInstances 确认权限 +tccli tcr DescribeInstances --region ap-guangzhou +# expected: exit 0,返回实例列表(可为空) +``` + +### 资源检查 + +```bash +# 4. 确认主实例存在且状态正常 +tccli tcr DescribeInstances --region --Registryids '["REGISTRY_ID"]' +# expected: exit 0, Status: "Running" + +# 5. 确认实例规格满足场景需求(场景一需高级版,场景三需标准版或高级版) +tccli tcr DescribeInstances --region --Registryids '["REGISTRY_ID"]' +# expected: exit 0, RegistryType 为 "premium"(场景一)或 "standard"/"premium"(场景三) + +# 6. (场景三)确认目标实例的命名空间已预先创建 +tccli tcr DescribeNamespaces --region --RegistryId DEST_REGISTRY_ID +# expected: exit 0, 目标命名空间在列表中 + +# 7. (跨主账号场景)确认目标侧提供的凭证可访问目标实例 +tccli tcr DescribeInstances --region PEER_REGION --Registryids '["PEER_REGISTRY_ID"]' +# expected: exit 0,使用目标账号凭证执行,返回目标实例信息 +``` + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 查看实例列表 | `DescribeInstances` | 是 | +| 查看已有复制实例 | `DescribeReplicationInstances` | 是 | +| 创建复制实例(新建从实例) | `CreateReplicationInstance` | 否 | +| 查询复制实例创建任务进度 | `DescribeReplicationInstanceCreateTasks` | 是 | +| 配置 VPC 内网接入 | `ManageInternalEndpoint` | 是(相同 VPC 重复接入不会报错) | +| 查看同步策略列表 | `DescribeReplicationPolicies` | 是 | +| 创建/更新同步规则 | `ManageReplication` | 否(作为创建时) | +| 查看同步状态 | `DescribeReplicationInstanceSyncStatus` | 是 | +| 查看自定义域名 | `DescribeInstanceCustomizedDomain` | 是 | +| 删除同步规则 | `DeleteReplicationRule` | 是 | +| 删除从实例 | `DeleteReplicationInstance` | 是 | +| 升级实例规格 | `ModifyInstance` | 是(已为目标规格则无操作) | + +## 关键字段说明 + +以下说明本页面涉及的主要 API 的核心参数。完整参数定义见各 API 的 `--generate-cli-skeleton` 输出。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | 格式 `tcr-` 开头,长度 22。主实例 ID | `InvalidParameter`:实例不存在或无权访问 | +| `ReplicationRegionId` | Integer | 是 | 目标地域数字 ID,如 `4`(ap-shanghai)。取值见[地域列表](https://cloud.tencent.com/document/product/1141/39265) | 写入错误地域后不可回退,需删除从实例重新创建 | +| `ReplicationRegionName` | String | 是 | 目标地域名称,如 `ap-shanghai`,须与 `ReplicationRegionId` 一致 | `InvalidParameter`:与 RegionId 不匹配 | +| `SyncTag` | Boolean | 否 | `true`:同步 Tag;`false`:不同步。默认 `false` | 省略时 Tag 不会被同步到从实例 | +| `SourceRegistryId` | String | 是 | 源实例 ID,格式 `tcr-` 开头 | `InvalidParameter`:实例不存在 | +| `DestinationRegistryId` | String | 是 | 目标实例 ID,格式 `tcr-` 开头 | `InvalidParameter`:实例不存在或跨地域不可达 | +| `Rule.Name` | String | 是 | 字母数字及 `-._`,以字母或数字开头 | `InvalidParameter`:命名不符合规范 | +| `Rule.DestNamespace` | String | 是 | 目标实例中已存在的命名空间名 | `ResourceNotFound`:命名空间不存在 | +| `Rule.Override` | Boolean | 否 | `true`:覆盖同名镜像;`false`:跳过。默认 `false` | 设为 `false` 时若镜像已存在则同步失败 | +| `Rule.Deletion` | Boolean | 否 | `true`:源删除则目标也删除;`false`:保留。默认 `false` | 设为 `true` 会级联删除目标仓库镜像 | +| `Rule.Filters` | Array | 是 | `[{"Type":"name","Value":"正则"}]`,至少一个。`Value` 为 Go 正则 | 正则错误导致零镜像匹配 | +| `DestinationRegionId` | Integer | 是 | 目标实例所在地域数字 ID | 地域错误导致同步不可达 | +| `PeerReplicationOption.PeerRegistryUin` | String | 否 | 目标主账号 UIN,跨账号必填 | 跨账号场景缺省则无权限访问 | +| `PeerReplicationOption.PeerRegistryToken` | String | 否 | 目标实例用户级长期访问密码,跨账号必填 | 误用服务级凭证则同步持续失败,且错误信息不明确 | +| `EnablePeerReplication` | Boolean | 否 | `true`:启用跨账号同步。默认为同账号 | 跨账号场景漏填则退化为同账号同步 | +| `VpcId` | String | 是 | 格式 `vpc-xxxxxxxx`,目标地域内的 VPC | `InvalidParameter`:VPC 不存在或非目标地域 | +| `SubnetId` | String | 是 | 格式 `subnet-xxxxxxxx`,属于指定 VPC | `InvalidParameter`:子网不存在或不在 VPC 下 | +| `Operation` | String | 是 | `Create`:新增接入;`Delete`:移除接入 | 填写错误导致操作语义相反 | + +## 操作步骤 + +### 场景一:跨地域实例复制 + +当业务部署在多个地域时,使用**实例复制**功能实现单地域上传、多地域高速实时同步、就近内网拉取。此场景需要**高级版**实例。 + +#### 步骤 1:确认主实例规格 + +##### 选择依据 + +- **实例规格**:实例复制仅高级版支持。若当前实例为 basic 或 standard,需先升级至 premium。 +- **升级方式**:basic 到 premium 为在线升级,不中断服务,零预付扣费。 + +```bash +tccli tcr DescribeInstances \ + --region \ + --Registryids '["REGISTRY_ID"]' +# expected: exit 0, RegistryType: "premium", Status: "Running" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "example-registry.tencentcloudcr.com", + "RegionName": "ap-guangzhou", + "InternalEndpoint": "10.0.0.1" + } + ] +} +``` + +若 `RegistryType` 不是 `premium`,执行升级: + +```bash +tccli tcr ModifyInstance \ + --RegistryId REGISTRY_ID \ + --RegistryType premium \ + --region +# expected: exit 0 +``` + +升级为异步操作,轮询确认变更生效: + +```bash +tccli tcr DescribeInstances \ + --region \ + --Registryids '["REGISTRY_ID"]' +# expected: exit 0, RegistryType: "premium", Status: "Running" +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `REGISTRY_ID` | 主实例 ID | 格式 `tcr-xxxxxxxxxxxx` | `tccli tcr DescribeInstances` | +| `REGION` | 主实例所在地域 | 如 `ap-guangzhou` | `tccli tcr DescribeInstances` 返回的 `RegionName` | + +#### 步骤 2:查看当前复制实例关系 + +```bash +tccli tcr DescribeReplicationInstances \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0 +``` + +**预期输出**(首次创建时为空): + +```json +{ + "TotalCount": 0, + "ReplicationRegistries": null +} +``` + +确认当前未配置复制实例,避免在同一地域重复创建。 + +#### 步骤 3:创建从实例 + +##### 选择依据 + +- **ReplicationRegionId**:选择目标地域的数字 ID。业务就近访问优先选择用户集中的地域(如华东选 `4`/ap-shanghai),降低镜像拉取延迟。 +- **SyncTag**:选 `true` 以保持 Tag 一致性。若 Tag 量大且仅需部分 Tag,可选 `false` 以减少同步开销。 +- **`--region` 参数**:始终指向主实例所在地域,即使创建的是其他地域的从实例。 + +##### 最小配置(只含必填字段) + +`replication_instance-minimal.json`: + +```json +{ + "RegistryId": "", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai" +} +``` + +```bash +tccli tcr CreateReplicationInstance \ + --cli-input-json file://replication_instance-minimal.json \ + --region ap-guangzhou +# expected: exit 0, 返回 ReplicationRegistryId +``` + +##### 增强配置(含可选字段) + +`replication_instance-enhanced.json`: + +```json +{ + "RegistryId": "", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai", + "SyncTag": true +} +``` + +```bash +tccli tcr CreateReplicationInstance \ + --cli-input-json file://replication_instance-enhanced.json \ + --region ap-guangzhou +# expected: exit 0, 返回 ReplicationRegistryId +``` + +**预期输出**: + +```json +{ + "ReplicationRegistryId": "tcr-example-4-example", + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | 主实例 ID | 格式 `tcr-xxxxxxxxxxxx` | `tccli tcr DescribeInstances` | +| `ReplicationRegionId` | 目标地域数字 ID | 如 `4`(ap-shanghai) | 见[地域列表](https://cloud.tencent.com/document/product/1141/39265) | +| `ReplicationRegionName` | 目标地域名称 | 如 `ap-shanghai` | 同上 | +| `SyncTag` | 是否同步 Tag | `true` 或 `false` | 自定义 | + +记录返回的 `ReplicationRegistryId`(格式为 `--xxxxx`)。 + +#### 步骤 4:轮询等待复制实例就绪 + +`CreateReplicationInstance` 是异步操作,需轮询检查创建任务进度。 + +```bash +tccli tcr DescribeReplicationInstanceCreateTasks \ + --ReplicationRegistryId REPLICATION_REGISTRY_ID \ + --ReplicationRegionId 4 \ + --region +# expected: exit 0, 所有子任务 TaskStatus 均为 "SUCCESS" +``` + +**预期输出**(所有任务完成时): + +```json +{ + "TaskDetail": [ + { + "TaskName": "SyncDBTask", + "TaskUUID": "tcr-example-4-example-db", + "TaskStatus": "SUCCESS", + "TaskMessage": "success" + }, + { + "TaskName": "CreateTcrCrdTask", + "TaskUUID": "tcr-example-4-example-crd", + "TaskStatus": "SUCCESS", + "TaskMessage": "success" + } + ] +} +``` + +| 占位符 | 说明 | 获取方式 | +|--------|------|---------| +| `REPLICATION_REGISTRY_ID` | 从实例 ID | 步骤 3 `CreateReplicationInstance` 返回值 | + +若子任务 `TaskStatus` 仍为 `IN_PROGRESS` 或 `PENDING`,等待 10-30 秒后重试。直到 `TaskDetail` 中所有子任务均为 `SUCCESS`。 + +##### 多维度验证 + +轮询结束后,从以下维度交叉验证复制实例是否真正可用: + +| 维度 | 检查内容 | 命令 | 预期 | +|------|---------|------|------| +| 任务完成 | 所有子任务 TaskStatus | `DescribeReplicationInstanceCreateTasks` | 均为 `SUCCESS` | +| 实例状态 | 从实例 Status | `DescribeReplicationInstances` | `Running` | +| 实例标识 | ReplicationRegistryId | `DescribeReplicationInstances` 返回 | 格式 `tcr-*-X-*`,非空 | +| 创建时间 | CreatedAt | `DescribeReplicationInstances` 返回 | 为当前时间前后 5 分钟内 | + +#### 步骤 5:确认从实例状态 + +```bash +tccli tcr DescribeReplicationInstances \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, Status: "Running" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "ReplicationRegistries": [ + { + "ReplicationRegistryId": "tcr-example-4-example", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai", + "Status": "Running", + "CreatedAt": "2026-01-01T00:00:00+08:00" + } + ] +} +``` + +确认 `Status` 为 `Running` 且 `ReplicationRegistryId` 与创建时返回的一致。 + +#### 步骤 6:将复制地域内的 VPC 接入复制实例 + +复制实例创建后,需将目标地域内需要拉取镜像的 VPC 接入该实例,实现就近内网拉取。详见[配置内网访问控制](../../ops/access/network/private-access)。 + +##### 选择依据 + +- **Operation**:选 `Create` 新增接入。若 VPC 已接入则使用已有链路。 +- **VpcId / SubnetId**:选择属于复制实例所在地域(`ReplicationRegionId` 对应地域)的 VPC 和子网。跨地域 VPC 不可用。 +- **RegionId / RegionName**:须与复制实例所在地域一致,否则接入链路不可达。 + +`internal_endpoint-minimal.json`: + +```json +{ + "RegistryId": "", + "Operation": "Create", + "VpcId": "", + "SubnetId": "" +} +``` + +```bash +tccli tcr ManageInternalEndpoint \ + --cli-input-json file://internal_endpoint-minimal.json \ + --region +# expected: exit 0 +``` + +`internal_endpoint-enhanced.json`(含地域参数): + +```json +{ + "RegistryId": "", + "Operation": "Create", + "VpcId": "", + "SubnetId": "", + "RegionId": 4, + "RegionName": "ap-shanghai" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | 主实例 ID | 格式 `tcr-xxxxxxxxxxxx` | `tccli tcr DescribeInstances` | +| `` | VPC 实例 ID | 格式 `vpc-xxxxxxxx`,属于复制实例所在地域 | `tccli vpc DescribeVpcs --region REPLICATION_REGION` | +| `` | 子网 ID | 格式 `subnet-xxxxxxxx`,属于指定 VPC | `tccli vpc DescribeSubnets --region REPLICATION_REGION` | + +> **注意:** `ManageInternalEndpoint` 可能因 VPC 接入配额已满(`LimitExceeded`)而失败。若遇此情况,清理不再使用的 VPC 接入后重试。 + +#### 跨国场景的折中方案 + +跨国场景下暂不支持直接实例复制。可采用组合方案: +1. 在国内和国外分别创建高级版实例 +2. 配置实例同步规则(`ManageReplication`)实现跨国按需同步(参见[场景三](#场景三devops-镜像流水线)中的同步规则配置) +3. 在上述国内外实例上分别执行 `CreateReplicationInstance`,在各自区域内创建从实例 + +最终效果:国内单点推送,实例复制到各国内地域;国外通过同步规则拉取,实例复制到各国外地域。 + +### 场景二:跨平台镜像迁移 + +多平台间(TCR、Docker Hub、Harbor、阿里云 ACR 等)的镜像迁移或同步,主要使用 image-transfer 工具和 Harbor 自建中转方案。此场景**不受实例规格限制**,basic 实例即可。 + +#### 方式一:使用 image-transfer 工具 + +[image-transfer](https://github.com/tkestack/image-transfer) 是腾讯云开源镜像迁移工具,支持 Docker Registry V2 标准的所有镜像仓库(TCR、Docker Hub、Quay、ACR、Harbor 等)间的批量迁移。 + +操作流程: +1. 编写认证鉴权文件 `auth.json`,配置源和目标仓库的访问凭证 +2. 编写迁移规则文件 `transfer.json`,定义镜像的源/目标映射关系 +3. 运行 image-transfer 容器 + +`auth.json` 示例: + +```json +{ + "src-registry.example.com": { + "username": "SRC_USERNAME", + "password": "SRC_PASSWORD", + "insecure": false + }, + "example-registry.tencentcloudcr.com": { + "username": "TCR_USERNAME", + "password": "TCR_PASSWORD", + "insecure": false + } +} +``` + +`transfer.json` 示例: + +```json +{ + "src-registry.example.com": { + "source-ns/source-repo:tag": "example-registry.tencentcloudcr.com/dest-ns/dest-repo:tag" + } +} +``` + +执行迁移: + +```bash +docker run --rm \ + -v "$(pwd)/auth.json:/app/auth.json" \ + -v "$(pwd)/transfer.json:/app/transfer.json" \ + tkestack/image-transfer +# expected: 镜像迁移成功日志,exit 0 +``` + +> **说明:** +> - image-transfer 运行在本地终端或服务器上,不属于 tccli 边界,但属于场景二的核心操作工具。 +> - `insecure: true` 表示跳过 TLS 验证,仅适用于非生产环境的自建仓库。 +> - 详细用法参见 [image-transfer README](https:/github.com/tkestack/image-transfer/blob/main/readme.md)。 + +**一键迁移模式(CCR 个人版到 TCR 企业版):** 参见[个人版迁移至企业版完全指南](https://cloud.tencent.com/document/product/1141/52292),该模式封装了认证和迁移规则,简化操作流程。 + +#### 方式二:通过自建 Harbor 实现跨平台实时同步 + +若需在第三方平台间实时同步镜像,可使用自建 Harbor 作为中转枢纽: + +1. 在 Harbor 配置 Pull-based 复制策略,将源平台的镜像定期拉取至 Harbor +2. 在 Harbor 配置 Push-based 复制策略,将 Harbor 内的镜像推送至目标平台 + +> **支持的镜像仓库服务:** Docker Hub、Docker Registry、AWS ECR、Azure ACR、阿里云 ACR、GCR、华为 SWR、Artifact Hub、GitLab、Quay、JFrog Artifactory、TCR。 + +此方式无需 TCR tccli 操作。详见[从自建 Harbor 同步镜像到 TCR 企业版](https://cloud.tencent.com/document/product/1141/44970)。 + +#### 使用自定义域名保证服务连续性 + +从其他镜像仓库迁移至 TCR 时,可为 TCR 实例配置自定义域名,沿用原有仓库域名,保持发布配置和 CI/CD 流程不变。 + +查看已有自定义域名: + +```bash +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "DomainInfoList": [ + { + "RegistryId": "tcr-example", + "CertId": "example-cert-id", + "DomainName": "test.example.com", + "Status": "SUCCESS" + } + ], + "TotalCount": 1 +} +``` + +`Status` 枚举:`SUCCESS`(绑定成功)、`FAILED`(绑定失败)、`PROCESSING`(处理中)。 + +配置自定义域名操作参见[配置自定义域名](../../ops/access/domain/custom-domain),涉及 SSL 证书上传(`tccli ssl UploadCertificate`)和 DNS CNAME 解析。 + +### 场景三:DevOps 镜像流水线 + +利用 TCR 实例同步功能,实现开发、测试、预发布、生产环境之间的镜像自动流转。此场景需要**标准版或高级版**实例。 + +#### 步骤 1:查看当前同步策略列表 + +```bash +tccli tcr DescribeReplicationPolicies \ + --RegistryId DEV_REGISTRY_ID \ + --region +# expected: exit 0 +``` + +**预期输出**(首次时为空): + +```json +{ + "TotalCount": 0, + "ReplicationPolicyInfoList": null +} +``` + +确认当前无已配置的同步规则。跨账号场景请同时参见[跨实例(账号)同步镜像](../../ops/image-distribution/cross-instance-sync)。 + +#### 步骤 2:配置同步规则 + +在开发环境实例上创建同步规则,将镜像自动推送到测试环境实例。 + +##### 选择依据 + +- **Rule.Override**:选 `true` 以确保每次推送都用最新镜像覆盖目标。若需保留历史版本则选 `false`,但需确保 Tag 唯一避免冲突。 +- **Rule.Deletion**:选 `false` 以保留目标仓库镜像,防止源端误删导致目标也丢失。仅当需要严格镜像生命周期同步时才选 `true`。 +- **Rule.Filters**:使用 `".*"` 匹配所有镜像。生产环境建议限制范围(如 `"release-.*"`),避免调试镜像污染生产环境。 +- **DestinationRegionId**:选目标实例所在地域的数字 ID,跨地域同步需确保网络可达。 + +##### 最小配置(只含必填字段) + +`sync_dev_to_test-minimal.json`: + +```json +{ + "SourceRegistryId": "", + "DestinationRegistryId": "", + "Rule": { + "Name": "dev-to-test", + "DestNamespace": "", + "Filters": [ + { + "Type": "name", + "Value": ".*" + } + ] + }, + "DestinationRegionId": 1 +} +``` + +```bash +tccli tcr ManageReplication \ + --cli-input-json file://sync_dev_to_test-minimal.json \ + --region +# expected: exit 0 +``` + +##### 增强配置(含可选字段) + +`sync_dev_to_test-enhanced.json`: + +```json +{ + "SourceRegistryId": "", + "DestinationRegistryId": "", + "Rule": { + "Name": "dev-to-test", + "DestNamespace": "", + "Override": true, + "Filters": [ + { + "Type": "name", + "Value": ".*" + } + ], + "Deletion": false + }, + "Description": "开发环境到测试环境自动同步", + "DestinationRegionId": 1 +} +``` + +```bash +tccli tcr ManageReplication \ + --cli-input-json file://sync_dev_to_test-enhanced.json \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | 源(开发)实例 ID | 格式 `tcr-xxxxxxxxxxxx` | `tccli tcr DescribeInstances` | +| `` | 目标(测试)实例 ID | 格式 `tcr-xxxxxxxxxxxx` | 同上 | +| `` | 目标实例命名空间 | 需在目标实例中预先创建 | `tccli tcr DescribeNamespaces --region --RegistryId TEST_REGISTRY_ID` | +| `DestinationRegionId` | 目标实例所在地域数字 ID | 如 `1`(ap-guangzhou) | 见[地域列表](https://cloud.tencent.com/document/product/1141/39265) | + +同一主账号内,按同样模式配置后续环节(测试到预发布、预发布到生产): + +- `test-to-staging`:`DestNamespace: "staging"` +- `staging-to-prod`:`DestNamespace: "production"` + +#### 步骤 3:验证同步规则已生效 + +```bash +tccli tcr DescribeReplicationPolicies \ + --RegistryId DEV_REGISTRY_ID \ + --region +# expected: exit 0, Enabled: true +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "ReplicationPolicyInfoList": [ + { + "ID": 2, + "Name": "dev-to-test", + "Enabled": true, + "SrcResource": "example-registry/.* ap-guangzhou", + "DestResource": "tcr-example-1/example-ns ap-guangzhou", + "Filters": [ + {"Type": "name", "Value": ".*"} + ], + "Override": true + } + ] +} +``` + +确认 `Enabled` 为 `true` 且 `Filters` 与预期一致。 + +#### 步骤 4:查看同步状态 + +```bash +tccli tcr DescribeReplicationInstanceSyncStatus \ + --RegistryId DEV_REGISTRY_ID \ + --ReplicationRegistryId REPLICATION_REGISTRY_ID \ + --ShowReplicationLog true \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "ReplicationStatus": "Succeed", + "ReplicationTime": "2026-01-01T00:05:00+08:00", + "ReplicationLog": { + "ReplicationTime": "2026-01-01T00:05:00+08:00", + "TaskId": 42, + "Status": "Succeed", + "Percentage": 100, + "Total": 3 + } +} +``` + +`ReplicationStatus` 枚举:`Succeed`(同步成功)、`InProgress`(同步中)、`Failed`(同步失败)、`Cancel`(已取消)。 + +当 `Status` 为 `Failed` 时,通过 `ShowReplicationLog` 查看具体失败原因。 + +| 占位符 | 说明 | 获取方式 | +|--------|------|---------| +| `DEV_REGISTRY_ID` | 源实例 ID | `tccli tcr DescribeInstances` | +| `REPLICATION_REGISTRY_ID` | 目标实例 ID | 同上 | + +#### 步骤 5(可选):跨主账号同步 + +若不同环境分属不同的腾讯云主账号,在步骤 2 的 `ManageReplication` 请求中增加 `PeerReplicationOption`。 + +##### 选择依据 + +- **PeerRegistryToken**:必须使用**用户级账号**长期访问密码,服务级账号凭证不支持跨主账号同步。选取长期凭证以避免同步因凭证过期中断。 +- **EnablePeerReplication**:设为 `true`。此字段是跨账号开关,漏填则退化为同账号同步。 + +##### 最小配置(只含必填字段) + +`sync_cross_account-minimal.json`: + +```json +{ + "SourceRegistryId": "", + "DestinationRegistryId": "", + "Rule": { + "Name": "dev-to-test-cross-account", + "DestNamespace": "", + "Filters": [ + { + "Type": "name", + "Value": ".*" + } + ] + }, + "DestinationRegionId": 1, + "PeerReplicationOption": { + "PeerRegistryUin": "", + "PeerRegistryToken": "", + "EnablePeerReplication": true + } +} +``` + +```bash +tccli tcr ManageReplication \ + --cli-input-json file://sync_cross_account-minimal.json \ + --region +# expected: exit 0 +``` + +##### 增强配置(含可选字段) + +`sync_cross_account-enhanced.json`: + +```json +{ + "SourceRegistryId": "", + "DestinationRegistryId": "", + "Rule": { + "Name": "dev-to-test-cross-account", + "DestNamespace": "", + "Override": true, + "Filters": [ + { + "Type": "name", + "Value": ".*" + } + ], + "Deletion": false + }, + "Description": "跨主账号同步:开发到测试", + "DestinationRegionId": 1, + "PeerReplicationOption": { + "PeerRegistryUin": "", + "PeerRegistryToken": "", + "EnablePeerReplication": true + } +} +``` + +| 占位符 | 说明 | 获取方式 | +|--------|------|---------| +| `` | 目标主账号 UIN | 目标账号基本信息页 | +| `` | 目标实例长期访问凭证 | 目标实例 [用户级账号](https://cloud.tencent.com/document/product/1141/41829) 密码 | +| `` | 目标实例 ID | 目标侧 `tccli tcr DescribeInstances` | + +> **注意:** +> - 仅支持[用户级账号](https://cloud.tencent.com/document/product/1141/41829),不支持[服务级账号](https://cloud.tencent.com/document/product/1141/89137)。 +> - 同步规则的生命周期与目标账号最新添加规则的访问凭证生命周期保持一致,请使用长期访问凭证。 + +#### 交付流水线(CODING DevOps) + +对于更复杂的 DevOps 需求,可使用腾讯云 [CODING DevOps](https://console.cloud.tencent.com/coding) 一站式平台。TCR 的交付流水线功能依赖于 CODING DevOps 的 CI/CD 能力,支持推送代码自动触发镜像构建和部署、本地推送镜像后自动触发部署。 + +> 交付流水线非 tccli 范围,详见[使用交付流水线实现容器 DevOps](https://cloud.tencent.com/document/product/1141/48186)。 + +## 验证 + +### 控制面(tccli) + +**实例复制验证:** + +```bash +# 1. 确认从实例状态 +tccli tcr DescribeReplicationInstances \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, Status: "Running", ReplicationRegistryId 非空 + +# 2. 确认复制任务全部完成 +tccli tcr DescribeReplicationInstanceCreateTasks \ + --ReplicationRegistryId REPLICATION_REGISTRY_ID \ + --ReplicationRegionId REPLICATION_REGION_ID \ + --region +# expected: exit 0, 所有 TaskDetail[].TaskStatus 均为 "SUCCESS" +``` + +**实例同步验证:** + +```bash +# 3. 确认同步规则已启用 +tccli tcr DescribeReplicationPolicies \ + --RegistryId DEV_REGISTRY_ID \ + --region +# expected: exit 0, Enabled: true + +# 4. 确认同步状态为成功 +tccli tcr DescribeReplicationInstanceSyncStatus \ + --RegistryId DEV_REGISTRY_ID \ + --ReplicationRegistryId REPLICATION_REGISTRY_ID \ + --ShowReplicationLog true \ + --region +# expected: exit 0, ReplicationStatus: "Succeed" +``` + +**自定义域名验证:** + +```bash +# 5. 确认域名绑定状态 +tccli tcr DescribeInstanceCustomizedDomain \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, Status: "SUCCESS" +``` + +### 数据面 + +**镜像迁移验证(TCR 侧):** + +```bash +tccli tcr DescribeImages \ + --RegistryId REGISTRY_ID \ + --NamespaceName NAMESPACE \ + --RepositoryName REPO \ + --region +# expected: 目标镜像 Tag 在列表中 +``` + +**自定义域名 DNS 解析验证:** + +```bash +nslookup CUSTOM_DOMAIN +# expected: 解析结果为 CNAME 指向 TCR 实例域名 +``` + +**镜像拉取验证(从复制实例):** + +```bash +docker pull REPLICATION_REGISTRY_DOMAIN/NAMESPACE/REPO:TAG +# expected: 镜像拉取成功 +``` + +## 清理 + +> **计费提醒:** TCR 企业版实例按规格和时长计费。实例复制产生的从实例按主实例规格计费,删除从实例或同步规则不会自动退还已产生的费用。请确认无需继续使用后再执行清理操作。 + +### 数据面 + +清理 image-transfer 产生的本地凭证文件(含敏感信息): + +```bash +rm auth.json transfer.json +# expected: 文件已删除 +``` + +若在 Harbor 配置了中转复制策略,迁移完成后在 Harbor UI 停用并删除对应规则。 + +若为 TCR 实例配置了自定义域名且不再需要,删除 SSL 证书绑定和 DNS CNAME 记录(参见[配置自定义域名](../../ops/access/domain/custom-domain))。 + +### 控制面(tccli) + +按依赖倒序依次清理:同步规则 -> VPC 内网接入链路 -> 从实例。 + +> **副作用警告:** `DeleteReplicationInstance` 会删除从实例及其所有关联数据,从实例上的镜像和配置将永久丢失。`DeleteReplicationRule` 仅删除同步规则本身,不会删除已同步的镜像数据。 + +#### 步骤 1:清理前状态检查 + +先确认待清理的资源列表: + +```bash +# 查看同步规则 +tccli tcr DescribeReplicationPolicies \ + --RegistryId REGISTRY_ID \ + --region +# expected: 记录 Rule ID,确认是待删除的规则 + +# 查看复制实例 +tccli tcr DescribeReplicationInstances \ + --RegistryId REGISTRY_ID \ + --region +# expected: 记录 ReplicationRegistryId,确认是待删除的从实例 + +# 查看 VPC 内网接入(如有) +tccli tcr DescribeInternalEndpoints \ + --RegistryId REGISTRY_ID \ + --region +# expected: 记录 VpcId,确认接入链路 +``` + +#### 步骤 2:删除同步规则 + +```bash +tccli tcr DeleteReplicationRule \ + --RegistryId REGISTRY_ID \ + --RuleId RULE_ID \ + --region +# expected: exit 0 +``` + +验证已删除: + +```bash +tccli tcr DescribeReplicationPolicies \ + --RegistryId REGISTRY_ID \ + --region +# expected: TotalCount: 0 +``` + +#### 步骤 3:删除 VPC 内网接入链路(如有) + +`delete_endpoint.json`: + +```json +{ + "RegistryId": "", + "Operation": "Delete", + "VpcId": "" +} +``` + +```bash +tccli tcr ManageInternalEndpoint \ + --cli-input-json file://delete_endpoint.json \ + --region +# expected: exit 0 +``` + +验证已删除: + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId REGISTRY_ID \ + --region +# expected: TotalCount: 0 +``` + +#### 步骤 4:删除从实例 + +```bash +tccli tcr DeleteReplicationInstance \ + --RegistryId REGISTRY_ID \ + --ReplicationRegistryId REPLICATION_REGISTRY_ID \ + --ReplicationRegionId REPLICATION_REGION_ID \ + --region +# expected: exit 0 +``` + +验证已删除: + +```bash +tccli tcr DescribeReplicationInstances \ + --RegistryId REGISTRY_ID \ + --region +# expected: TotalCount: 0 +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `CreateReplicationInstance` 返回 `UnsupportedOperation` | `tccli tcr DescribeInstances --region --Registryids '["REGISTRY_ID"]'` 查看 `RegistryType` | 实例非高级版,basic/standard 实例不支持实例复制 | 升级实例规格:`tccli tcr ModifyInstance --RegistryId REGISTRY_ID --RegistryType premium --region `;或改用实例同步(`ManageReplication`)替代 | +| `ManageReplication` 返回 `UnsupportedOperation` | `tccli tcr DescribeInstances --region --Registryids '["REGISTRY_ID"]'` 查看 `RegistryType` | 实例为 basic 版,不支持实例同步 | 升级实例规格:`tccli tcr ModifyInstance --RegistryId REGISTRY_ID --RegistryType standard --region `;若无法升级,使用 image-transfer 工具手动同步 | +| `ManageReplication` 返回 `InvalidParameter` | 检查 `Rule.Name` 的组成字符和长度 | 规则名称不符合命名规范(仅支持字母数字及 `-._`,以字母或数字开头) | 修正 `Rule.Name`,确保符合规范,如 `"dev-to-test"` | +| `ManageReplication` 返回 `InternalError`,错误信息:"Not support slave region" | `tccli tcr DescribeReplicationInstances --RegistryId REGISTRY_ID --region ` 确认当前实例是否为复制从实例 | 当前实例是通过 `CreateReplicationInstance` 创建的从实例(slave),从实例不支持作为 `ManageReplication` 的 `SourceRegistryId` 发起同步规则 | 在源主实例(非 slave 的主实例)上执行 `ManageReplication`。从实例仅用于接收镜像复制,不支持创建同步规则作为同步源 | +| 跨主账号同步返回权限错误 | `tccli tcr DescribeInstances --region PEER_REGION --Registryids '["PEER_REGISTRY_ID"]'` 使用目标账号凭证确认目标实例存在且可访问 | `PeerRegistryToken` 使用了服务级账号凭证或不正确的密码 | 改用目标实例的[用户级账号](https://cloud.tencent.com/document/product/1141/41829)长期访问密码,重新执行 `ManageReplication` | +| 删除实例时返回 "please delete the replication rule first" | `tccli tcr DescribeReplicationInstances --RegistryId REGISTRY_ID --region ` 查看关联的从实例;`tccli tcr DescribeReplicationPolicies --RegistryId REGISTRY_ID --region ` 查看关联的同步规则 | 实例关联了活动的从实例或同步规则,不允许直接删除主实例 | 先执行 `DeleteReplicationInstance` 删除所有从实例,再执行 `DeleteReplicationRule` 删除所有同步规则,最后再删除主实例 | +| `ManageInternalEndpoint` 返回 `LimitExceeded` | `tccli tcr DescribeInternalEndpoints --RegistryId REGISTRY_ID --region ` 查看当前接入的 VPC 数量 | 当前实例 VPC 接入数量已达上限(此为环境限制,非命令错误) | 清理不再使用的 VPC 接入链路:`tccli tcr ManageInternalEndpoint --cli-input-json file://delete_endpoint.json --region `(Operation: "Delete"),或使用已有 VPC 和子网 | +| `CreateReplicationInstance` 返回 `FailedOperation.TradeFailed` | 检查账户余额 | 账户余额不足,创建高级版从实例需预付费用(此为环境限制,非命令错误) | 先创建 basic 实例(后付费无预付):`tccli tcr CreateInstance --RegistryType basic --region REPLICATION_REGION`,再执行 `tccli tcr ModifyInstance --RegistryType premium --region REPLICATION_REGION` 升级;若升级也触发余额限制,需充值 | + +### 创建成功但状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 实例同步长时间处于 `InProgress` | `tccli tcr DescribeReplicationInstanceSyncStatus --RegistryId REGISTRY_ID --ReplicationRegistryId REPLICATION_REGISTRY_ID --ShowReplicationLog true --region ` 查看 `Percentage` | 待同步镜像数量多或跨地域网络延迟 | 等待自动完成,通过 `Percentage` 观察进度。若超过预期时间仍无进展,检查源和目标实例的网络连通性 | +| 复制实例创建后 `Status` 长时间非 `Running` | `tccli tcr DescribeReplicationInstanceCreateTasks --ReplicationRegistryId REPLICATION_REGISTRY_ID --ReplicationRegionId REPLICATION_REGION_ID --region ` 查看各子任务状态 | 后端任务(SyncDBTask / CreateTcrCrdTask)执行超时,可能因地域资源紧张 | 若某子任务 `TaskStatus` 为 `FAILED`,记录 `TaskMessage` 和 `RequestId` 后 [提交工单](https://console.cloud.tencent.com/workorder);若均为 `IN_PROGRESS`,继续轮询(间隔 30 秒),最长等待约 15 分钟 | +| `DescribeReplicationInstanceSyncStatus` 返回 `ReplicationStatus: "Failed"` | `tccli tcr DescribeReplicationInstanceSyncStatus --RegistryId REGISTRY_ID --ReplicationRegistryId REPLICATION_REGISTRY_ID --ShowReplicationLog true --region ` 查看 `ReplicationLog` 中具体错误 | 同步过程中镜像拉取或推送失败(源/目标仓库不可达、认证过期、镜像损坏等) | 根据 `ReplicationLog` 中的错误信息定位:认证问题则更新 `PeerRegistryToken`;网络问题则检查目标实例内网/公网可达性;镜像问题则重新推送源镜像。修复后重新触发同步 | +| `DescribeInstanceCustomizedDomain` 返回 `Status: "FAILED"` | 检查 `DomainName` 对应的 DNS CNAME 记录和 SSL 证书 | CNAME 未正确配置或 SSL 证书与域名不匹配 | 确认 DNS CNAME 指向 TCR 实例域名,确认 SSL 证书包含该自定义域名 | + +## 下一步 + +- [全球多地域间同步镜像实现就近访问](../global-replication) -- 实例复制与同步实战 +- [跨实例(账号)同步镜像](../../ops/image-distribution/cross-instance-sync) -- 跨主账号同步规则详解 +- [同实例多地域复制镜像](../../ops/image-distribution/cross-region-replication) -- 实例复制详细操作 +- [配置内网访问控制](../../ops/access/network/private-access) -- VPC 内网链路管理 +- [配置自定义域名](../../ops/access/domain/custom-domain) -- 统一域名管理 + +## 控制台替代 + +- [容器镜像服务控制台 -- 同步复制](https://console.cloud.tencent.com/tcr/replication) diff --git a/src/content/docs/cli/tcr/practices/personal-domain-access-tcrctl.md b/src/content/docs/cli/tcr/practices/personal-domain-access-tcrctl.md new file mode 100644 index 0000000..7583f3e --- /dev/null +++ b/src/content/docs/cli/tcr/practices/personal-domain-access-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "" +description: "# 产品 CLI 操作" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +# 产品 CLI 操作 + +> 页型 P1 · 使用 crctl 完成本页任务 · P0 仅提供 stub 占位,P1 阶段完成正文。 + diff --git a/src/content/docs/cli/tcr/practices/personal-domain-access.md b/src/content/docs/cli/tcr/practices/personal-domain-access.md new file mode 100644 index 0000000..2526bde --- /dev/null +++ b/src/content/docs/cli/tcr/practices/personal-domain-access.md @@ -0,0 +1,546 @@ +--- +title: "使用个人版域名访问企业版实例" +description: "· page_id `82855`" +--- + +> 对照官方:[使用个人版域名访问企业版实例](https://cloud.tencent.com/document/product/1141/82855) · page_id `82855` + +## 概述 + +将个人版服务域名 `ccr.ccs.tencentyun.com` 的流量引导至 TCR 企业版实例,无需变更 TKE 集群、CI/CD 平台内已有镜像仓库地址及访问凭证配置。客户端继续使用个人版域名 + 个人版凭证即可拉取或推送企业版实例内镜像。 + +整套方案通过两层 DNS 劫持实现透明转发: + +1. **PrivateDNS 层**:在私有网络中创建 `tencentyun.com` 私有域,将 `ccr.ccs` 主机记录 CNAME 解析至企业版实例公网域名。 +2. **TCR 内网 DNS 层**:在企业版侧开启 VPC 内网自动解析(`CreateInternalEndpointDns`),使 VPC 内 DNS 请求将企业版公网域名解析至企业版内网 IP。 + +**支持智能回源**:若企业版实例内无对应命名空间和仓库,自动回源至个人版服务拉取对应镜像(需同命名空间、同仓库名且在个人版中存在该镜像)。若企业版仓库已存在但具体 Tag 不存在,**不会**触发回源。 + +> **关键提醒**:企业版实例内请勿使用 `library`、`tke`、`public` 等系统保留命名空间,否则会导致 TKE 集群无法拉取产品官方镜像。 + +## 前置条件 + +> 前置依赖:需已完成[个人版迁移至企业版完全指南](../personal-migration)中的镜像迁移步骤。企业版实例新建时默认下发支持个人版域名的 SSL 证书。 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域 + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tcr:DescribeInstances, tcr:DescribeInternalEndpoints +# tcr:ManageInternalEndpoint, tcr:CreateInternalEndpointDns +# tcr:DescribeInternalEndpointDnsStatus, tcr:CreateInstanceToken +# vpc:DescribeVpcs, vpc:DescribeSubnets +# 验证 TCR 实例查询权限: +tccli tcr DescribeInstances --region +# expected: exit 0,返回实例列表(可为空) + +# 验证 VPC 权限: +tccli vpc DescribeVpcs --region +# expected: exit 0,返回 VPC 列表(可为空) + +# 4. 检查 Docker 客户端(用于数据面验证) +docker --version +# expected: Docker version >= 20.10 +``` + +### 资源检查 + +```bash +# 5. 确认 TCR 企业版实例存在且 Running +tccli tcr DescribeInstances --region --Registryids '["REGISTRY_ID"]' +# expected: exit 0, Status: "Running", RegistryType: "premium" 或 "standard" + +# 6. 确认 PrivateDNS 服务已开通 +tccli privatedns DescribePrivateZoneList --region +# expected: exit 0(非 AuthFailure 即表示已开通) + +# 7. 确认目标 VPC 存在 +tccli vpc DescribeVpcs --region --VpcIds '["VPC_ID"]' +# expected: exit 0, TotalCount >= 1 + +# 8. 确认目标子网存在且 IP 充足 +tccli vpc DescribeSubnets --region --SubnetIds '["SUBNET_ID"]' +# expected: exit 0, AvailableIpAddressCount >= 1 +``` + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 查看企业版实例及域名 | `DescribeInstances` | 是 | +| 查看内网访问链路及 IP | `DescribeInternalEndpoints` | 是 | +| 关联 VPC 至 TCR 实例 | `ManageInternalEndpoint --Operation Create` | 否 | +| 开启内网自动解析 | `CreateInternalEndpointDns` | 否(重复创建报错) | +| 查看内网 DNS 解析状态 | `DescribeInternalEndpointDnsStatus` | 是 | +| 获取临时/长期访问凭证 | `CreateInstanceToken` | 否 | +| 创建私有域(PrivateDNS) | PrivateDNS 控制台操作 | — | +| 配置解析记录 | PrivateDNS 控制台操作 | — | +| 拉取/推送镜像(数据面) | `docker pull/push ccr.ccs.tencentyun.com/...` | — | + +## 关键字段说明 + +以下说明本页涉及的 API 主要参数。完整参数定义见 `tccli tcr --help`。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | TCR 企业版实例 ID,格式 `tcr-xxxxxxxx`。`DescribeInstances` 获取 | 实例不存在 → `ResourceNotFound` | +| `VpcId` | String | 是 | TKE 集群所在 VPC ID,格式 `vpc-xxxxxxxx`。`vpc:DescribeVpcs` 获取 | VPC 不存在 → `FailedOperation` | +| `SubnetId` | String | 是 | VPC 下子网 ID,格式 `subnet-xxxxxxxx`。`vpc:DescribeSubnets` 获取 | 子网不存在或 IP 耗尽 → `FailedOperation` | +| `EniLBIp` | String | 是 | ENI LB IP,来自 `DescribeInternalEndpoints` 返回的 `EniLBIp`。不可自行编造 | IP 不匹配 → DNS 解析指向错误目标 | +| `UsePublicDomain` | Boolean | 否 | `true`(使用公网域名解析,推荐)/ `false`(使用内网域名)。默认 `true` | `false` 时 VPC 内使用内网域名,PrivateDNS 记录值需同步修改 | +| `TokenType` | String | 是 | `longterm`(长期有效)或 `temp`(临时,约 1 小时) | 选 `temp` 凭证过期后需重建 | +| `Operation` | String | 是 | `Create`(关联 VPC)或 `Delete`(解除关联) | 填错 → 意图相反,可能误删链路 | + +## 操作步骤 + +### 步骤 1:确认实例及内网访问链路 + +```bash +tccli tcr DescribeInstances --region \ + --Registryids '["REGISTRY_ID"]' +# expected: exit 0, Status: "Running", RegistryType: "premium" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-xxxxxxxx", + "RegistryName": "tcr-example", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "tcr-example.tencentcloudcr.com" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +查询内网访问链路当前状态: + +```bash +tccli tcr DescribeInternalEndpoints --region \ + --RegistryId REGISTRY_ID +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "AccessVpcSet": [ + { + "VpcId": "vpc-xxxxxxxx", + "SubnetId": "subnet-xxxxxxxx", + "Status": "Running", + "AccessIp": "10.0.0.5", + "EniLBIp": "10.0.0.5" + } + ], + "TotalCount": 1, + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +若 `TotalCount` 为 0 或目标 VPC 未关联,执行步骤 2。 + +### 步骤 2:关联 VPC 至 TCR 实例 + +#### 选择依据 + +- **VpcId**:选择 TKE 集群所在 VPC。从 `tccli tke DescribeClusters --region --ClusterIds '["CLUSTER_ID"]'` 返回的 `ClusterNetworkSettings.VpcId` 获取。 +- **SubnetId**:推荐选择集群节点所在子网,减少跨子网路由。同一 VPC 即可连通,不需特定子网。 +- **Operation**:首次配置选 `Create`。若 VPC 已关联则跳过此步(`DescribeInternalEndpoints` 中 `VpcId` 已存在即表示已关联)。 + +#### 最小配置 + +`manage-internal-endpoint.json`: + +```json +{ + "RegistryId": "REGISTRY_ID", + "Operation": "Create", + "VpcId": "VPC_ID", + "SubnetId": "SUBNET_ID" +} +``` + +```bash +tccli tcr ManageInternalEndpoint \ + --region \ + --cli-input-json file://manage-internal-endpoint.json +# expected: exit 0, 返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +轮询确认链路就绪: + +```bash +tccli tcr DescribeInternalEndpoints --region \ + --RegistryId REGISTRY_ID +# expected: exit 0, AccessVpcSet[*].Status == "Running" +``` + +**预期输出**: + +```json +{ + "AccessVpcSet": [ + { + "VpcId": "vpc-xxxxxxxx", + "SubnetId": "subnet-xxxxxxxx", + "Status": "Running", + "AccessIp": "10.0.0.5", + "EniLBIp": "10.0.0.5" + } + ], + "TotalCount": 1, + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +记录 `EniLBIp`(步骤 3 必需)。`EniLBIp` 为企业版实例在 VPC 内的 ENI LB VIP,后续 DNS 解析将指向此 IP。 + +### 步骤 3:开启 TCR 侧内网 DNS 自动解析 + +#### 选择依据 + +- **EniLBIp**:必须使用步骤 2 `DescribeInternalEndpoints` 返回的 `EniLBIp`,不可自行编造。 +- **UsePublicDomain**:选 `true`,使 VPC 内将企业版公网域名(`.tencentcloudcr.com`)解析至内网 IP。与 PrivateDNS 侧 CNAME 记录值一致。 + +#### 最小配置 + +`create-dns.json`: + +```json +{ + "InstanceId": "REGISTRY_ID", + "VpcId": "VPC_ID", + "EniLBIp": "ENI_LB_IP", + "UsePublicDomain": true +} +``` + +```bash +tccli tcr CreateInternalEndpointDns \ + --region \ + --cli-input-json file://create-dns.json +# expected: exit 0, 返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +轮询确认 DNS 生效: + +```bash +tccli tcr DescribeInternalEndpointDnsStatus --region \ + --VpcSet '[{"InstanceId":"REGISTRY_ID","VpcId":"VPC_ID","EniLBIp":"ENI_LB_IP","UsePublicDomain":true}]' +# expected: exit 0, VpcSet[*].Status == "ENABLED" +``` + +**预期输出**: + +```json +{ + "VpcSet": [ + { + "Status": "ENABLED" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +确认各 `VpcSet[*].Status` 均为 `ENABLED` 后继续。 + +### 步骤 4:配置 PrivateDNS 私有域解析 + +> PrivateDNS 私有域及解析记录的创建与管理需通过 [PrivateDNS 控制台](https://console.cloud.tencent.com/privatedns) 操作,无对应 tccli 等价接口。 + +#### 4.1 创建私有域 + +1. 前往 [私有域解析 PrivateDNS](https://console.cloud.tencent.com/privatedns) 控制台。 +2. 单击**新建私有域**: + - **域名**:`tencentyun.com` + - **关联 VPC**:选择步骤 2 已关联至 TCR 实例的 VPC + - **子域名递归解析**:保持**开启**(确保未配置的 `*.tencentyun.com` 子域名仍走公网 DNS) + +#### 4.2 配置解析记录 + +进入私有域 `tencentyun.com` 详情页,添加解析记录: + +| 字段 | 值 | +|------|-----| +| 主机记录 | `ccr.ccs` | +| 记录类型 | `CNAME` | +| 记录值 | `.tencentcloudcr.com`(企业版实例公网域名,从步骤 1 `DescribeInstances` 的 `PublicDomain` 获取) | + +解析链路最终效果: + +``` +ccr.ccs.tencentyun.com + → (PrivateDNS CNAME) → .tencentcloudcr.com + → (TCR VPC 内网 DNS) → (企业版内网 IP) +``` + +### 步骤 5:获取访问凭证 + +#### 选择依据 + +- **TokenType**:`longterm`(长期有效),适合写在 TKE `imagePullSecret` 或 CI/CD 配置中。`temp`(临时,约 1 小时)仅在一次性调试场景使用,过期需重建。 +- **Desc**:填写描述性文字(如 `个人版域名兼容专用`),便于在凭证列表中区分用途。 + +#### 最小创建 + +```bash +tccli tcr CreateInstanceToken --region \ + --RegistryId REGISTRY_ID \ + --TokenType longterm +# expected: exit 0, 返回 Username 和 Token +``` + +**预期输出**: + +```json +{ + "Username": "100012345678", + "Token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IlBBR...", + "ExpTime": 2096844746789, + "TokenId": "tcr-token-xxxxxxxx", + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +#### 增强配置(带描述) + +```bash +tccli tcr CreateInstanceToken --region \ + --RegistryId REGISTRY_ID \ + --TokenType longterm \ + --Desc "个人版域名兼容专用" +# expected: exit 0, 返回 Username 和 Token(含 Desc) +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `REGISTRY_ID` | TCR 企业版实例 ID | 格式 `tcr-xxxxxxxx` | `tccli tcr DescribeInstances` | +| `REGION` | 地域 | 如 `ap-guangzhou` | `tccli configure list` 或 `tccli tcr DescribeInstances` 返回的地域 | +| `VPC_ID` | VPC 实例 ID | 格式 `vpc-xxxxxxxx` | `tccli vpc DescribeVpcs` | +| `ENI_LB_IP` | ENI LB 内网 IP | 点分十进制 IP,来自 `DescribeInternalEndpoints` | `tccli tcr DescribeInternalEndpoints --RegistryId REGISTRY_ID` | + +### 步骤 6:验证透明访问 + +#### 6.1 VPC 内 DNS 解析验证 + +在 VPC 内任意 CVM 或 Pod 中执行: + +```bash +nslookup ccr.ccs.tencentyun.com +# expected: 返回企业版实例内网 IP(ENI_LB_IP) +``` + +#### 6.2 Docker 登录与拉取验证 + +```bash +docker login ccr.ccs.tencentyun.com --username= --password= +# expected: Login Succeeded + +docker pull ccr.ccs.tencentyun.com//: +# expected: 从企业版实例拉取成功(实际请求已路由至企业版内网 IP) +``` + +#### 6.3 智能回源验证(企业版无此仓库时回源个人版) + +```bash +# 假设个人版存在 team-b/apache:latest,但企业版无此仓库 +docker pull ccr.ccs.tencentyun.com/team-b/apache:latest +# expected: 自动回源至个人版拉取成功 +``` + +> 回源仅在仓库名完全不存在于企业版实例时生效。若企业版有同名仓库但具体 Tag 不存在,**不会**回源。 + +#### 6.4 TKE 集群中验证 + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-test +spec: + replicas: 1 + selector: + matchLabels: + app: nginx-test + template: + metadata: + labels: + app: nginx-test + spec: + imagePullSecrets: + - name: qcloudregistrykey + containers: + - name: nginx + image: ccr.ccs.tencentyun.com//: +``` + +```bash +kubectl apply -f deployment.yaml +kubectl get pods -l app=nginx-test +# expected: STATUS Running, READY 1/1 +``` + +## 验证 + +### 控制面(tccli) + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 实例状态 | `tccli tcr DescribeInstances --region --Registryids '["REGISTRY_ID"]'` | `Status: "Running"` | +| 内网链路 | `tccli tcr DescribeInternalEndpoints --region --RegistryId REGISTRY_ID` | `AccessVpcSet[*].Status == "Running"` 且含目标 VPC | +| DNS 解析状态 | `tccli tcr DescribeInternalEndpointDnsStatus --region --VpcSet '[{"InstanceId":"REGISTRY_ID","VpcId":"VPC_ID","EniLBIp":"ENI_LB_IP","UsePublicDomain":true}]'` | `VpcSet[*].Status == "ENABLED"` | +| 访问凭证 | `tccli tcr CreateInstanceToken --region --RegistryId REGISTRY_ID --TokenType longterm` 保存 `Username` 和 `Token` | 返回有效凭证 | + +### 数据面 + +```bash +# DNS 解析验证(VPC 内执行) +nslookup ccr.ccs.tencentyun.com +# expected: 返回企业版实例内网 IP + +# Docker 登录验证 +docker login ccr.ccs.tencentyun.com --username= --password= +# expected: Login Succeeded + +# 拉取镜像验证 +docker pull ccr.ccs.tencentyun.com//: +# expected: 成功拉取 + +# TKE Pod 验证 +kubectl get pods -l app=nginx-test +# expected: STATUS Running, READY 1/1 +``` + +## 清理 + +> **副作用警告**: +> - `DeleteInternalEndpointDns` 仅移除 TCR 侧自动 DNS 解析记录,不影响 PrivateDNS 手动配置的记录。 +> - `ManageInternalEndpoint --Operation Delete` 会移除 VPC 内网访问链路,该 VPC 内所有客户端无法内网访问企业版实例。 +> - 删除 PrivateDNS 解析记录或私有域后,VPC 内 `ccr.ccs.tencentyun.com` 恢复公网 DNS 解析,指向个人版服务。客户端将**回退至直接访问个人版**,而非透明路由至企业版。 + +### 清理前状态检查 + +```bash +# 确认当前内网链路状态 +tccli tcr DescribeInternalEndpoints --region \ + --RegistryId REGISTRY_ID +# 确认当前 DNS 解析状态 +tccli tcr DescribeInternalEndpointDnsStatus --region \ + --VpcSet '[{"InstanceId":"REGISTRY_ID","VpcId":"VPC_ID","EniLBIp":"ENI_LB_IP","UsePublicDomain":true}]' +``` + +### 数据面 + +```bash +# 删除测试 Deployment +kubectl delete deployment nginx-test --ignore-not-found +# 删除测试镜像 +docker rmi ccr.ccs.tencentyun.com//: +``` + +### 控制面(tccli) + +```bash +# 1. 删除 TCR 侧内网 DNS 解析 +tccli tcr DeleteInternalEndpointDns --region \ + --InstanceId REGISTRY_ID \ + --VpcId VPC_ID \ + --EniLBIp ENI_LB_IP +# expected: exit 0 + +# 2. 解除 VPC 内网访问链路 +tccli tcr ManageInternalEndpoint --region \ + --RegistryId REGISTRY_ID \ + --Operation Delete \ + --VpcId VPC_ID \ + --SubnetId SUBNET_ID +# expected: exit 0 +``` + +### PrivateDNS 控制台清理 + +在 [PrivateDNS 控制台](https://console.cloud.tencent.com/privatedns) 中删除 `ccr.ccs` 解析记录,或删除整个私有域 `tencentyun.com`。 + +### 验证已删除 + +```bash +# 确认内网链路已删除 +tccli tcr DescribeInternalEndpoints --region \ + --RegistryId REGISTRY_ID +# expected: TotalCount: 0 或目标 VPC 不在 AccessVpcSet 中 + +# 确认 DNS 解析已删除 +tccli tcr DescribeInternalEndpointDnsStatus --region \ + --VpcSet '[{"InstanceId":"REGISTRY_ID","VpcId":"VPC_ID","EniLBIp":"ENI_LB_IP","UsePublicDomain":true}]' +# expected: VpcSet 为空或状态非 ENABLED +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ManageInternalEndpoint` 返回 `FailedOperation` | `tccli vpc DescribeVpcs --region --VpcIds '["VPC_ID"]'` 确认 VPC 存在 | VPC 不存在或 VPC ID 错误 | 使用 `tccli vpc DescribeVpcs --region ` 查询正确 VPC ID | +| `ManageInternalEndpoint` 返回 `FailedOperation`(子网不可用) | `tccli vpc DescribeSubnets --region --SubnetIds '["SUBNET_ID"]'` 检查 `AvailableIpAddressCount` | 子网不存在或 IP 耗尽 | 换用有可用 IP 的子网,或在该 VPC 下新建子网 | +| `ManageInternalEndpoint` 返回 `LimitExceeded` | `tccli tcr DescribeInternalEndpoints --region --RegistryId REGISTRY_ID` 查看已有 VPC 关联数 | VPC 关联数量达上限(此为环境限制,非命令错误) | 删除不再使用的 VPC 关联后重试 | +| `CreateInternalEndpointDns` 返回 `UnsupportedOperation` | `tccli tcr DescribeInternalEndpoints --region --RegistryId REGISTRY_ID` 确认 VPC 是否已关联 | VPC 未通过 `ManageInternalEndpoint` 关联至 TCR 实例 | 先执行步骤 2 关联 VPC,再创建 DNS 解析 | +| `CreateInternalEndpointDns` 返回 `InvalidParameterValue` | `tccli tcr DescribeInternalEndpoints --region --RegistryId REGISTRY_ID` 对比 `EniLBIp` | 传入的 `EniLBIp` 与返回值不匹配 | 使用 `DescribeInternalEndpoints` 返回的精确 `EniLBIp` | +| `CreateInstanceToken` 返回 `AuthFailure` | `tccli configure list` 检查凭证 + `tccli tcr DescribeInstances --region ` 验证权限 | CAM 权限不足,缺少 `tcr:CreateInstanceToken` | 联系 CAM 管理员添加 `tcr:CreateInstanceToken` 权限 | + +### 创建成功但状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `docker pull` 超时或无响应 | `nslookup ccr.ccs.tencentyun.com`(在 VPC 内 CVM/Pod 执行)验证 DNS | PrivateDNS 未配置或 VPC 关联错误 | 在 PrivateDNS 控制台确认私有域已关联正确 VPC,解析记录已保存且生效 | +| `docker pull` 返回个人版镜像而非企业版 | `tccli tcr DescribeRepositories --region --RegistryId REGISTRY_ID --NamespaceName ` 确认仓库是否存在 | 企业版实例内无对应仓库,触发了智能回源(预期行为) | 如不期望回源,在企业版实例内创建对应命名空间和仓库并迁移镜像 | +| Push 报 `denied` / `unauthorized` | `tccli tcr DescribeNamespaces --region --RegistryId REGISTRY_ID` 确认命名空间存在 | 企业版实例内无对应命名空间,或登录凭证错误 | 先创建命名空间;确认 `docker login ccr.ccs.tencentyun.com` 使用步骤 5 生成的凭证 | +| 使用 `library`/`tke`/`public` 导致 TKE 官方镜像拉取异常 | `tccli tcr DescribeNamespaces --region --RegistryId REGISTRY_ID` 查看是否含保留命名空间 | 系统保留命名空间在个人版域名模式下与 TKE 官方镜像路径冲突 | 删除保留命名空间,改用自定义名称 | +| DNS 解析生效延迟 | `nslookup ccr.ccs.tencentyun.com` 指定 DNS 服务器查询 | DNS 传播通常需 1-2 分钟 | 等待后重试;检查 PrivateDNS 解析记录是否保存成功 | +| 同一地域多个企业版实例开启域名兼容导致解析冲突 | `tccli tcr DescribeInstances --region ` 列出所有实例及 `DescribeInternalEndpointDnsStatus` 状态 | 同地域仅允许一个实例开启个人版域名兼容功能 | 仅保留一个实例开启 `CreateInternalEndpointDns`,其余实例在 PrivateDNS 侧移除解析记录 | + +> 遇到上述排查无法解决的问题,保留 `RequestId`(每次 API 调用返回的 `RequestId` 字段)、实例 ID、地域信息,登录 [TCR 控制台](https://console.cloud.tencent.com/tcr) 查看详细状态,或 [提交工单](https://console.cloud.tencent.com/workorder)。 + +## 下一步 + +- [个人版迁移至企业版完全指南](../personal-migration) — 镜像数据迁移(前置步骤) +- [配置内网访问控制](../../../ops/access/network/private-access) — VPC 内网链路管理 +- [TKE 集群使用 TCR 插件内网免密拉取容器镜像](../../tke-plugin-pull) — 免密拉取配置 +- [配置自定义域名](../../../ops/access/domain/custom-domain) — 通用自定义域名管理 + +## 控制台替代 + +[容器镜像服务控制台](https://console.cloud.tencent.com/tcr) 完成实例创建与镜像迁移,前往 [PrivateDNS 控制台](https://console.cloud.tencent.com/privatedns) 创建 `tencentyun.com` 私有域并添加 `ccr.ccs` CNAME 记录指向企业版实例域名。在 TKE 集群内保持已有 `ccr.ccs.tencentyun.com` 镜像地址与 `qcloudregistrykey` 凭证即可透明访问企业版实例。 diff --git a/src/content/docs/cli/tcr/practices/personal-domain-access/personal-domain-access-tcrctl.md b/src/content/docs/cli/tcr/practices/personal-domain-access/personal-domain-access-tcrctl.md new file mode 100644 index 0000000..7583f3e --- /dev/null +++ b/src/content/docs/cli/tcr/practices/personal-domain-access/personal-domain-access-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "" +description: "# 产品 CLI 操作" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +# 产品 CLI 操作 + +> 页型 P1 · 使用 crctl 完成本页任务 · P0 仅提供 stub 占位,P1 阶段完成正文。 + diff --git a/src/content/docs/cli/tcr/practices/personal-domain-access/personal-domain-access.md b/src/content/docs/cli/tcr/practices/personal-domain-access/personal-domain-access.md new file mode 100644 index 0000000..2526bde --- /dev/null +++ b/src/content/docs/cli/tcr/practices/personal-domain-access/personal-domain-access.md @@ -0,0 +1,546 @@ +--- +title: "使用个人版域名访问企业版实例" +description: "· page_id `82855`" +--- + +> 对照官方:[使用个人版域名访问企业版实例](https://cloud.tencent.com/document/product/1141/82855) · page_id `82855` + +## 概述 + +将个人版服务域名 `ccr.ccs.tencentyun.com` 的流量引导至 TCR 企业版实例,无需变更 TKE 集群、CI/CD 平台内已有镜像仓库地址及访问凭证配置。客户端继续使用个人版域名 + 个人版凭证即可拉取或推送企业版实例内镜像。 + +整套方案通过两层 DNS 劫持实现透明转发: + +1. **PrivateDNS 层**:在私有网络中创建 `tencentyun.com` 私有域,将 `ccr.ccs` 主机记录 CNAME 解析至企业版实例公网域名。 +2. **TCR 内网 DNS 层**:在企业版侧开启 VPC 内网自动解析(`CreateInternalEndpointDns`),使 VPC 内 DNS 请求将企业版公网域名解析至企业版内网 IP。 + +**支持智能回源**:若企业版实例内无对应命名空间和仓库,自动回源至个人版服务拉取对应镜像(需同命名空间、同仓库名且在个人版中存在该镜像)。若企业版仓库已存在但具体 Tag 不存在,**不会**触发回源。 + +> **关键提醒**:企业版实例内请勿使用 `library`、`tke`、`public` 等系统保留命名空间,否则会导致 TKE 集群无法拉取产品官方镜像。 + +## 前置条件 + +> 前置依赖:需已完成[个人版迁移至企业版完全指南](../personal-migration)中的镜像迁移步骤。企业版实例新建时默认下发支持个人版域名的 SSL 证书。 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域 + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tcr:DescribeInstances, tcr:DescribeInternalEndpoints +# tcr:ManageInternalEndpoint, tcr:CreateInternalEndpointDns +# tcr:DescribeInternalEndpointDnsStatus, tcr:CreateInstanceToken +# vpc:DescribeVpcs, vpc:DescribeSubnets +# 验证 TCR 实例查询权限: +tccli tcr DescribeInstances --region +# expected: exit 0,返回实例列表(可为空) + +# 验证 VPC 权限: +tccli vpc DescribeVpcs --region +# expected: exit 0,返回 VPC 列表(可为空) + +# 4. 检查 Docker 客户端(用于数据面验证) +docker --version +# expected: Docker version >= 20.10 +``` + +### 资源检查 + +```bash +# 5. 确认 TCR 企业版实例存在且 Running +tccli tcr DescribeInstances --region --Registryids '["REGISTRY_ID"]' +# expected: exit 0, Status: "Running", RegistryType: "premium" 或 "standard" + +# 6. 确认 PrivateDNS 服务已开通 +tccli privatedns DescribePrivateZoneList --region +# expected: exit 0(非 AuthFailure 即表示已开通) + +# 7. 确认目标 VPC 存在 +tccli vpc DescribeVpcs --region --VpcIds '["VPC_ID"]' +# expected: exit 0, TotalCount >= 1 + +# 8. 确认目标子网存在且 IP 充足 +tccli vpc DescribeSubnets --region --SubnetIds '["SUBNET_ID"]' +# expected: exit 0, AvailableIpAddressCount >= 1 +``` + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 查看企业版实例及域名 | `DescribeInstances` | 是 | +| 查看内网访问链路及 IP | `DescribeInternalEndpoints` | 是 | +| 关联 VPC 至 TCR 实例 | `ManageInternalEndpoint --Operation Create` | 否 | +| 开启内网自动解析 | `CreateInternalEndpointDns` | 否(重复创建报错) | +| 查看内网 DNS 解析状态 | `DescribeInternalEndpointDnsStatus` | 是 | +| 获取临时/长期访问凭证 | `CreateInstanceToken` | 否 | +| 创建私有域(PrivateDNS) | PrivateDNS 控制台操作 | — | +| 配置解析记录 | PrivateDNS 控制台操作 | — | +| 拉取/推送镜像(数据面) | `docker pull/push ccr.ccs.tencentyun.com/...` | — | + +## 关键字段说明 + +以下说明本页涉及的 API 主要参数。完整参数定义见 `tccli tcr --help`。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | TCR 企业版实例 ID,格式 `tcr-xxxxxxxx`。`DescribeInstances` 获取 | 实例不存在 → `ResourceNotFound` | +| `VpcId` | String | 是 | TKE 集群所在 VPC ID,格式 `vpc-xxxxxxxx`。`vpc:DescribeVpcs` 获取 | VPC 不存在 → `FailedOperation` | +| `SubnetId` | String | 是 | VPC 下子网 ID,格式 `subnet-xxxxxxxx`。`vpc:DescribeSubnets` 获取 | 子网不存在或 IP 耗尽 → `FailedOperation` | +| `EniLBIp` | String | 是 | ENI LB IP,来自 `DescribeInternalEndpoints` 返回的 `EniLBIp`。不可自行编造 | IP 不匹配 → DNS 解析指向错误目标 | +| `UsePublicDomain` | Boolean | 否 | `true`(使用公网域名解析,推荐)/ `false`(使用内网域名)。默认 `true` | `false` 时 VPC 内使用内网域名,PrivateDNS 记录值需同步修改 | +| `TokenType` | String | 是 | `longterm`(长期有效)或 `temp`(临时,约 1 小时) | 选 `temp` 凭证过期后需重建 | +| `Operation` | String | 是 | `Create`(关联 VPC)或 `Delete`(解除关联) | 填错 → 意图相反,可能误删链路 | + +## 操作步骤 + +### 步骤 1:确认实例及内网访问链路 + +```bash +tccli tcr DescribeInstances --region \ + --Registryids '["REGISTRY_ID"]' +# expected: exit 0, Status: "Running", RegistryType: "premium" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-xxxxxxxx", + "RegistryName": "tcr-example", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "tcr-example.tencentcloudcr.com" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +查询内网访问链路当前状态: + +```bash +tccli tcr DescribeInternalEndpoints --region \ + --RegistryId REGISTRY_ID +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "AccessVpcSet": [ + { + "VpcId": "vpc-xxxxxxxx", + "SubnetId": "subnet-xxxxxxxx", + "Status": "Running", + "AccessIp": "10.0.0.5", + "EniLBIp": "10.0.0.5" + } + ], + "TotalCount": 1, + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +若 `TotalCount` 为 0 或目标 VPC 未关联,执行步骤 2。 + +### 步骤 2:关联 VPC 至 TCR 实例 + +#### 选择依据 + +- **VpcId**:选择 TKE 集群所在 VPC。从 `tccli tke DescribeClusters --region --ClusterIds '["CLUSTER_ID"]'` 返回的 `ClusterNetworkSettings.VpcId` 获取。 +- **SubnetId**:推荐选择集群节点所在子网,减少跨子网路由。同一 VPC 即可连通,不需特定子网。 +- **Operation**:首次配置选 `Create`。若 VPC 已关联则跳过此步(`DescribeInternalEndpoints` 中 `VpcId` 已存在即表示已关联)。 + +#### 最小配置 + +`manage-internal-endpoint.json`: + +```json +{ + "RegistryId": "REGISTRY_ID", + "Operation": "Create", + "VpcId": "VPC_ID", + "SubnetId": "SUBNET_ID" +} +``` + +```bash +tccli tcr ManageInternalEndpoint \ + --region \ + --cli-input-json file://manage-internal-endpoint.json +# expected: exit 0, 返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +轮询确认链路就绪: + +```bash +tccli tcr DescribeInternalEndpoints --region \ + --RegistryId REGISTRY_ID +# expected: exit 0, AccessVpcSet[*].Status == "Running" +``` + +**预期输出**: + +```json +{ + "AccessVpcSet": [ + { + "VpcId": "vpc-xxxxxxxx", + "SubnetId": "subnet-xxxxxxxx", + "Status": "Running", + "AccessIp": "10.0.0.5", + "EniLBIp": "10.0.0.5" + } + ], + "TotalCount": 1, + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +记录 `EniLBIp`(步骤 3 必需)。`EniLBIp` 为企业版实例在 VPC 内的 ENI LB VIP,后续 DNS 解析将指向此 IP。 + +### 步骤 3:开启 TCR 侧内网 DNS 自动解析 + +#### 选择依据 + +- **EniLBIp**:必须使用步骤 2 `DescribeInternalEndpoints` 返回的 `EniLBIp`,不可自行编造。 +- **UsePublicDomain**:选 `true`,使 VPC 内将企业版公网域名(`.tencentcloudcr.com`)解析至内网 IP。与 PrivateDNS 侧 CNAME 记录值一致。 + +#### 最小配置 + +`create-dns.json`: + +```json +{ + "InstanceId": "REGISTRY_ID", + "VpcId": "VPC_ID", + "EniLBIp": "ENI_LB_IP", + "UsePublicDomain": true +} +``` + +```bash +tccli tcr CreateInternalEndpointDns \ + --region \ + --cli-input-json file://create-dns.json +# expected: exit 0, 返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +轮询确认 DNS 生效: + +```bash +tccli tcr DescribeInternalEndpointDnsStatus --region \ + --VpcSet '[{"InstanceId":"REGISTRY_ID","VpcId":"VPC_ID","EniLBIp":"ENI_LB_IP","UsePublicDomain":true}]' +# expected: exit 0, VpcSet[*].Status == "ENABLED" +``` + +**预期输出**: + +```json +{ + "VpcSet": [ + { + "Status": "ENABLED" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +确认各 `VpcSet[*].Status` 均为 `ENABLED` 后继续。 + +### 步骤 4:配置 PrivateDNS 私有域解析 + +> PrivateDNS 私有域及解析记录的创建与管理需通过 [PrivateDNS 控制台](https://console.cloud.tencent.com/privatedns) 操作,无对应 tccli 等价接口。 + +#### 4.1 创建私有域 + +1. 前往 [私有域解析 PrivateDNS](https://console.cloud.tencent.com/privatedns) 控制台。 +2. 单击**新建私有域**: + - **域名**:`tencentyun.com` + - **关联 VPC**:选择步骤 2 已关联至 TCR 实例的 VPC + - **子域名递归解析**:保持**开启**(确保未配置的 `*.tencentyun.com` 子域名仍走公网 DNS) + +#### 4.2 配置解析记录 + +进入私有域 `tencentyun.com` 详情页,添加解析记录: + +| 字段 | 值 | +|------|-----| +| 主机记录 | `ccr.ccs` | +| 记录类型 | `CNAME` | +| 记录值 | `.tencentcloudcr.com`(企业版实例公网域名,从步骤 1 `DescribeInstances` 的 `PublicDomain` 获取) | + +解析链路最终效果: + +``` +ccr.ccs.tencentyun.com + → (PrivateDNS CNAME) → .tencentcloudcr.com + → (TCR VPC 内网 DNS) → (企业版内网 IP) +``` + +### 步骤 5:获取访问凭证 + +#### 选择依据 + +- **TokenType**:`longterm`(长期有效),适合写在 TKE `imagePullSecret` 或 CI/CD 配置中。`temp`(临时,约 1 小时)仅在一次性调试场景使用,过期需重建。 +- **Desc**:填写描述性文字(如 `个人版域名兼容专用`),便于在凭证列表中区分用途。 + +#### 最小创建 + +```bash +tccli tcr CreateInstanceToken --region \ + --RegistryId REGISTRY_ID \ + --TokenType longterm +# expected: exit 0, 返回 Username 和 Token +``` + +**预期输出**: + +```json +{ + "Username": "100012345678", + "Token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IlBBR...", + "ExpTime": 2096844746789, + "TokenId": "tcr-token-xxxxxxxx", + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +#### 增强配置(带描述) + +```bash +tccli tcr CreateInstanceToken --region \ + --RegistryId REGISTRY_ID \ + --TokenType longterm \ + --Desc "个人版域名兼容专用" +# expected: exit 0, 返回 Username 和 Token(含 Desc) +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `REGISTRY_ID` | TCR 企业版实例 ID | 格式 `tcr-xxxxxxxx` | `tccli tcr DescribeInstances` | +| `REGION` | 地域 | 如 `ap-guangzhou` | `tccli configure list` 或 `tccli tcr DescribeInstances` 返回的地域 | +| `VPC_ID` | VPC 实例 ID | 格式 `vpc-xxxxxxxx` | `tccli vpc DescribeVpcs` | +| `ENI_LB_IP` | ENI LB 内网 IP | 点分十进制 IP,来自 `DescribeInternalEndpoints` | `tccli tcr DescribeInternalEndpoints --RegistryId REGISTRY_ID` | + +### 步骤 6:验证透明访问 + +#### 6.1 VPC 内 DNS 解析验证 + +在 VPC 内任意 CVM 或 Pod 中执行: + +```bash +nslookup ccr.ccs.tencentyun.com +# expected: 返回企业版实例内网 IP(ENI_LB_IP) +``` + +#### 6.2 Docker 登录与拉取验证 + +```bash +docker login ccr.ccs.tencentyun.com --username= --password= +# expected: Login Succeeded + +docker pull ccr.ccs.tencentyun.com//: +# expected: 从企业版实例拉取成功(实际请求已路由至企业版内网 IP) +``` + +#### 6.3 智能回源验证(企业版无此仓库时回源个人版) + +```bash +# 假设个人版存在 team-b/apache:latest,但企业版无此仓库 +docker pull ccr.ccs.tencentyun.com/team-b/apache:latest +# expected: 自动回源至个人版拉取成功 +``` + +> 回源仅在仓库名完全不存在于企业版实例时生效。若企业版有同名仓库但具体 Tag 不存在,**不会**回源。 + +#### 6.4 TKE 集群中验证 + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-test +spec: + replicas: 1 + selector: + matchLabels: + app: nginx-test + template: + metadata: + labels: + app: nginx-test + spec: + imagePullSecrets: + - name: qcloudregistrykey + containers: + - name: nginx + image: ccr.ccs.tencentyun.com//: +``` + +```bash +kubectl apply -f deployment.yaml +kubectl get pods -l app=nginx-test +# expected: STATUS Running, READY 1/1 +``` + +## 验证 + +### 控制面(tccli) + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 实例状态 | `tccli tcr DescribeInstances --region --Registryids '["REGISTRY_ID"]'` | `Status: "Running"` | +| 内网链路 | `tccli tcr DescribeInternalEndpoints --region --RegistryId REGISTRY_ID` | `AccessVpcSet[*].Status == "Running"` 且含目标 VPC | +| DNS 解析状态 | `tccli tcr DescribeInternalEndpointDnsStatus --region --VpcSet '[{"InstanceId":"REGISTRY_ID","VpcId":"VPC_ID","EniLBIp":"ENI_LB_IP","UsePublicDomain":true}]'` | `VpcSet[*].Status == "ENABLED"` | +| 访问凭证 | `tccli tcr CreateInstanceToken --region --RegistryId REGISTRY_ID --TokenType longterm` 保存 `Username` 和 `Token` | 返回有效凭证 | + +### 数据面 + +```bash +# DNS 解析验证(VPC 内执行) +nslookup ccr.ccs.tencentyun.com +# expected: 返回企业版实例内网 IP + +# Docker 登录验证 +docker login ccr.ccs.tencentyun.com --username= --password= +# expected: Login Succeeded + +# 拉取镜像验证 +docker pull ccr.ccs.tencentyun.com//: +# expected: 成功拉取 + +# TKE Pod 验证 +kubectl get pods -l app=nginx-test +# expected: STATUS Running, READY 1/1 +``` + +## 清理 + +> **副作用警告**: +> - `DeleteInternalEndpointDns` 仅移除 TCR 侧自动 DNS 解析记录,不影响 PrivateDNS 手动配置的记录。 +> - `ManageInternalEndpoint --Operation Delete` 会移除 VPC 内网访问链路,该 VPC 内所有客户端无法内网访问企业版实例。 +> - 删除 PrivateDNS 解析记录或私有域后,VPC 内 `ccr.ccs.tencentyun.com` 恢复公网 DNS 解析,指向个人版服务。客户端将**回退至直接访问个人版**,而非透明路由至企业版。 + +### 清理前状态检查 + +```bash +# 确认当前内网链路状态 +tccli tcr DescribeInternalEndpoints --region \ + --RegistryId REGISTRY_ID +# 确认当前 DNS 解析状态 +tccli tcr DescribeInternalEndpointDnsStatus --region \ + --VpcSet '[{"InstanceId":"REGISTRY_ID","VpcId":"VPC_ID","EniLBIp":"ENI_LB_IP","UsePublicDomain":true}]' +``` + +### 数据面 + +```bash +# 删除测试 Deployment +kubectl delete deployment nginx-test --ignore-not-found +# 删除测试镜像 +docker rmi ccr.ccs.tencentyun.com//: +``` + +### 控制面(tccli) + +```bash +# 1. 删除 TCR 侧内网 DNS 解析 +tccli tcr DeleteInternalEndpointDns --region \ + --InstanceId REGISTRY_ID \ + --VpcId VPC_ID \ + --EniLBIp ENI_LB_IP +# expected: exit 0 + +# 2. 解除 VPC 内网访问链路 +tccli tcr ManageInternalEndpoint --region \ + --RegistryId REGISTRY_ID \ + --Operation Delete \ + --VpcId VPC_ID \ + --SubnetId SUBNET_ID +# expected: exit 0 +``` + +### PrivateDNS 控制台清理 + +在 [PrivateDNS 控制台](https://console.cloud.tencent.com/privatedns) 中删除 `ccr.ccs` 解析记录,或删除整个私有域 `tencentyun.com`。 + +### 验证已删除 + +```bash +# 确认内网链路已删除 +tccli tcr DescribeInternalEndpoints --region \ + --RegistryId REGISTRY_ID +# expected: TotalCount: 0 或目标 VPC 不在 AccessVpcSet 中 + +# 确认 DNS 解析已删除 +tccli tcr DescribeInternalEndpointDnsStatus --region \ + --VpcSet '[{"InstanceId":"REGISTRY_ID","VpcId":"VPC_ID","EniLBIp":"ENI_LB_IP","UsePublicDomain":true}]' +# expected: VpcSet 为空或状态非 ENABLED +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ManageInternalEndpoint` 返回 `FailedOperation` | `tccli vpc DescribeVpcs --region --VpcIds '["VPC_ID"]'` 确认 VPC 存在 | VPC 不存在或 VPC ID 错误 | 使用 `tccli vpc DescribeVpcs --region ` 查询正确 VPC ID | +| `ManageInternalEndpoint` 返回 `FailedOperation`(子网不可用) | `tccli vpc DescribeSubnets --region --SubnetIds '["SUBNET_ID"]'` 检查 `AvailableIpAddressCount` | 子网不存在或 IP 耗尽 | 换用有可用 IP 的子网,或在该 VPC 下新建子网 | +| `ManageInternalEndpoint` 返回 `LimitExceeded` | `tccli tcr DescribeInternalEndpoints --region --RegistryId REGISTRY_ID` 查看已有 VPC 关联数 | VPC 关联数量达上限(此为环境限制,非命令错误) | 删除不再使用的 VPC 关联后重试 | +| `CreateInternalEndpointDns` 返回 `UnsupportedOperation` | `tccli tcr DescribeInternalEndpoints --region --RegistryId REGISTRY_ID` 确认 VPC 是否已关联 | VPC 未通过 `ManageInternalEndpoint` 关联至 TCR 实例 | 先执行步骤 2 关联 VPC,再创建 DNS 解析 | +| `CreateInternalEndpointDns` 返回 `InvalidParameterValue` | `tccli tcr DescribeInternalEndpoints --region --RegistryId REGISTRY_ID` 对比 `EniLBIp` | 传入的 `EniLBIp` 与返回值不匹配 | 使用 `DescribeInternalEndpoints` 返回的精确 `EniLBIp` | +| `CreateInstanceToken` 返回 `AuthFailure` | `tccli configure list` 检查凭证 + `tccli tcr DescribeInstances --region ` 验证权限 | CAM 权限不足,缺少 `tcr:CreateInstanceToken` | 联系 CAM 管理员添加 `tcr:CreateInstanceToken` 权限 | + +### 创建成功但状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `docker pull` 超时或无响应 | `nslookup ccr.ccs.tencentyun.com`(在 VPC 内 CVM/Pod 执行)验证 DNS | PrivateDNS 未配置或 VPC 关联错误 | 在 PrivateDNS 控制台确认私有域已关联正确 VPC,解析记录已保存且生效 | +| `docker pull` 返回个人版镜像而非企业版 | `tccli tcr DescribeRepositories --region --RegistryId REGISTRY_ID --NamespaceName ` 确认仓库是否存在 | 企业版实例内无对应仓库,触发了智能回源(预期行为) | 如不期望回源,在企业版实例内创建对应命名空间和仓库并迁移镜像 | +| Push 报 `denied` / `unauthorized` | `tccli tcr DescribeNamespaces --region --RegistryId REGISTRY_ID` 确认命名空间存在 | 企业版实例内无对应命名空间,或登录凭证错误 | 先创建命名空间;确认 `docker login ccr.ccs.tencentyun.com` 使用步骤 5 生成的凭证 | +| 使用 `library`/`tke`/`public` 导致 TKE 官方镜像拉取异常 | `tccli tcr DescribeNamespaces --region --RegistryId REGISTRY_ID` 查看是否含保留命名空间 | 系统保留命名空间在个人版域名模式下与 TKE 官方镜像路径冲突 | 删除保留命名空间,改用自定义名称 | +| DNS 解析生效延迟 | `nslookup ccr.ccs.tencentyun.com` 指定 DNS 服务器查询 | DNS 传播通常需 1-2 分钟 | 等待后重试;检查 PrivateDNS 解析记录是否保存成功 | +| 同一地域多个企业版实例开启域名兼容导致解析冲突 | `tccli tcr DescribeInstances --region ` 列出所有实例及 `DescribeInternalEndpointDnsStatus` 状态 | 同地域仅允许一个实例开启个人版域名兼容功能 | 仅保留一个实例开启 `CreateInternalEndpointDns`,其余实例在 PrivateDNS 侧移除解析记录 | + +> 遇到上述排查无法解决的问题,保留 `RequestId`(每次 API 调用返回的 `RequestId` 字段)、实例 ID、地域信息,登录 [TCR 控制台](https://console.cloud.tencent.com/tcr) 查看详细状态,或 [提交工单](https://console.cloud.tencent.com/workorder)。 + +## 下一步 + +- [个人版迁移至企业版完全指南](../personal-migration) — 镜像数据迁移(前置步骤) +- [配置内网访问控制](../../../ops/access/network/private-access) — VPC 内网链路管理 +- [TKE 集群使用 TCR 插件内网免密拉取容器镜像](../../tke-plugin-pull) — 免密拉取配置 +- [配置自定义域名](../../../ops/access/domain/custom-domain) — 通用自定义域名管理 + +## 控制台替代 + +[容器镜像服务控制台](https://console.cloud.tencent.com/tcr) 完成实例创建与镜像迁移,前往 [PrivateDNS 控制台](https://console.cloud.tencent.com/privatedns) 创建 `tencentyun.com` 私有域并添加 `ccr.ccs` CNAME 记录指向企业版实例域名。在 TKE 集群内保持已有 `ccr.ccs.tencentyun.com` 镜像地址与 `qcloudregistrykey` 凭证即可透明访问企业版实例。 diff --git a/src/content/docs/cli/tcr/practices/personal-migration-tcrctl.md b/src/content/docs/cli/tcr/practices/personal-migration-tcrctl.md new file mode 100644 index 0000000..7583f3e --- /dev/null +++ b/src/content/docs/cli/tcr/practices/personal-migration-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "" +description: "# 产品 CLI 操作" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +# 产品 CLI 操作 + +> 页型 P1 · 使用 crctl 完成本页任务 · P0 仅提供 stub 占位,P1 阶段完成正文。 + diff --git a/src/content/docs/cli/tcr/practices/personal-migration.md b/src/content/docs/cli/tcr/practices/personal-migration.md new file mode 100644 index 0000000..8b3ee5f --- /dev/null +++ b/src/content/docs/cli/tcr/practices/personal-migration.md @@ -0,0 +1,774 @@ +--- +title: "个人版迁移至企业版完全指南(tccli)" +description: "· page_id `52292`" +--- + +> 对照官方:[个人版迁移至企业版完全指南](https://cloud.tencent.com/document/product/1141/52292) · page_id `52292` + +## 概述 + +将腾讯云容器镜像服务(TCR)个人版中的容器镜像与 Helm Chart 迁移至 TCR 企业版实例。个人版为免费共享服务,企业版为独享实例,提供更稳定的服务质量和内网访问、镜像安全扫描、实例同步等功能。 + +| 维度 | 方案A:DuplicateImage(逐 Tag) | 方案B:ccr2tcr 工具(全量自动) | +|------|-------------------------------|--------------------------------| +| 适用规模 | 少量镜像(<50 Tag) | 大量镜像(≥50 Tag) | +| 迁移粒度 | 逐 Tag 精确控制,支持跨命名空间重命名 | 全量自动迁移,保留原有组织结构 | +| 操作方式 | tccli 命令行 + 可选 Shell 批处理脚本 | Docker 容器运行,一条命令完成 | +| 前置依赖 | tccli + CAM 权限 | CVM + VPC 内网链路 + API 密钥对 | +| 幂等性 | 是(同一 Tag 重复迁移覆盖写入) | 是(检测已存在则跳过) | +| 推荐场景 | 精确控制、少量仓库、跨命名空间重组 | 全量搬迁、多仓库、追求效率 | + +核心流程:(1)在企业版创建目标命名空间与仓库;(2)方案 A 用 `DuplicateImage` 逐 Tag 迁移,方案 B 用 `ccr2tcr` 工具全量自动迁移;(3)配置内网访问链路使业务集群可拉取;(4)通过个人版域名兼容实现零配置切换或更新镜像地址。 + +## 前置条件 + +- [环境准备](../../../index.md) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域 + +# 3. 检查 TCR 企业版实例查询权限 +tccli tcr DescribeInstances --region +# expected: exit 0,返回实例列表(可为空) + +# 4. 检查个人版镜像查询权限 +tccli tcr DescribeImagePersonal --region --RepoName "namespace/repo" +# expected: exit 0(仓库存在返回 Tag 列表,不存在返回 ResourceNotFound 也说明权限 OK) + +# 5. 检查 VPC 查询权限 +tccli vpc DescribeVpcs --region +# expected: exit 0,返回 VPC 列表(可为空) +``` + +> CAM 所需权限(精确 Action 名):`tcr:DescribeInstances`、`tcr:DescribeImagePersonal`、`tcr:DuplicateImage`、`tcr:CreateNamespace`、`tcr:DescribeNamespaces`、`tcr:CreateRepository`、`tcr:DescribeRepositories`、`tcr:ManageInternalEndpoint`、`tcr:DescribeInternalEndpoints`、`tcr:CreateInternalEndpointDns`、`tcr:DescribeInternalEndpointDnsStatus`、`tcr:CreateInstanceToken`、`tcr:DescribeInstanceToken`、`tcr:DeleteInstanceToken`。建议授予 `QcloudTCRFullAccess` 预设策略。方案 B 额外需要 `cvm:DescribeInstances`。 + +### 资源检查 + +```bash +# 6. 确认 TCR 企业版实例已创建且状态为 Running +tccli tcr DescribeInstances --region --Registryids '["REGISTRY_ID"]' +# expected: exit 0, Status: "Running" + +# 7. 确认个人版命名空间中存在待迁移镜像 +tccli tcr DescribeImagePersonal --region --RepoName "SRC_NS/SRC_REPO" +# expected: exit 0, Data.TagInfo 非空,TotalCount > 0 + +# 8. 查询可用 VPC 和子网(方案 B 配置内网访问链路需要) +tccli vpc DescribeVpcs --region --Filters '[{"Name":"vpc-id","Values":["VPC_ID"]}]' +# expected: 至少返回 1 个 VPC,状态 Available + +tccli vpc DescribeSubnets --region --Filters '[{"Name":"vpc-id","Values":["VPC_ID"]}]' +# expected: 至少返回 1 个子网,AvailableIpCount ≥ 1 + +# 9.(方案 B)确认 CVM 可用 +tccli cvm DescribeInstances --region --Filters '[{"Name":"instance-id","Values":["INS_ID"]}]' +# expected: 至少返回 1 台 CVM,InstanceState: "RUNNING" +``` + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 查看个人版镜像列表 | `DescribeImagePersonal` | 是 | +| 查看企业版实例列表 | `DescribeInstances` | 是 | +| 创建企业版命名空间 | `CreateNamespace` | 否(同名存在则报错) | +| 创建企业版镜像仓库 | `CreateRepository` | 否(同名存在则报错) | +| 迁移镜像(逐 Tag) | `DuplicateImage` | 是(幂等覆盖写入) | +| 关联 VPC 至 TCR 实例 | `ManageInternalEndpoint` | 否 | +| 查看内网访问链路 | `DescribeInternalEndpoints` | 是 | +| 开启内网 DNS 解析 | `CreateInternalEndpointDns` | 否 | +| 查询 DNS 解析状态 | `DescribeInternalEndpointDnsStatus` | 是 | +| 创建长期访问凭证 | `CreateInstanceToken` | 否 | +| 全量自动迁移 | `docker run ccr2tcr` | 是(检测已存在则跳过) | + +## 关键字段说明 + +以下列出主要操作的参数约束。完整参数定义见 `tccli tcr --generate-cli-skeleton`。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|-----------|---------| +| `RegistryId` | String | 是 | 目标企业版实例 ID,格式 `tcr-xxxxxxxx`。`DescribeInstances` 返回 | `ResourceNotFound` — 实例不存在 | +| `NamespaceName` | String | 是 | 命名空间名,1-63 字符,以小写字母或数字开头和结尾 | `InvalidParameter` — 命名空间名格式不合法 | +| `RepositoryName` | String | 是 | 镜像仓库名,1-255 字符。同命名空间下不可重名 | `InvalidParameter.RepositoryName` — 仓库名冲突 | +| `SourceNamespaceName` | String | 是 | 源命名空间名(个人版命名空间) | `ResourceNotFound` — 源命名空间不存在 | +| `SourceRepositoryName` | String | 是 | 源仓库名(个人版仓库) | `ResourceNotFound` — 源仓库不存在 | +| `ImageVersion` | String | 是 | 镜像 Tag 名称,如 `latest`、`v1.0.0` | `ResourceNotFound` — Tag 不存在 | +| `RepoName` | String | 是 | 个人版仓库全名,格式 `<命名空间>/<仓库名>`,如 `myns/myapp` | `ResourceNotFound` — 仓库拼写错误导致查询失败 | +| `TokenType` | String | 是 | `longterm`(长期有效,推荐生产)或 `temp`(临时,数小时后过期) | 选 `temp` → 凭证过期后拉取失败;未存储 Token 值无法找回 | +| `VpcId` | String | 方案 B | 已有 VPC ID,格式 `vpc-xxxxxxxx`。`DescribeInternalEndpoints` 获取已关联列表 | `FailedOperation` — VPC 不存在或不属于当前账号 | +| `SubnetId` | String | 方案 B | VPC 下子网 ID,格式 `subnet-xxxxxxxx` | `FailedOperation` — 子网不存在或可用 IP 不足 | +| `EniLBIp` | String | 方案 B | 内网访问 IP,在 `DescribeInternalEndpoints` 返回的 `AccessIp` 字段获取 | — IP 不正确 → DNS 解析关联错误 | +| `UsePublicDomain` | Boolean | 方案 B | `true`:使用默认域名解析到内网 IP | `false` → 内网 DNS 不会自动解析默认域名 | + +## 操作步骤 + +### 方案A:逐 Tag 精确迁移(DuplicateImage) + +--- + +适用场景:仅需迁移特定命名空间/仓库的特定 Tag,或需在迁移过程中重命名命名空间/仓库。 + +#### 步骤 A1:获取个人版镜像列表 + +```bash +tccli tcr DescribeImagePersonal \ + --region \ + --RepoName "SRC_NS/SRC_REPO" \ + --Limit 100 \ + --output json +# expected: exit 0,返回 Tag 列表 +``` + +**预期输出**: + +```json +{ + "Data": { + "TagInfo": [ + { + "TagName": "latest", + "TagId": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "ImageId": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "Size": 123456789, + "CreationTime": "2025-12-01T09:00:00+08:00", + "PushTime": "2026-01-15T10:30:00+08:00" + } + ], + "TotalCount": 1 + }, + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +记录需要迁移的 Tag 名称列表,以及源命名空间名(`SRC_NS`)和源仓库名(`SRC_REPO`)。 + +#### 步骤 A2:在企业版创建目标命名空间 + +##### 选择依据 + +- **NamespaceName**:建议与个人版命名空间名保持一致,减少 CI/CD 配置变更和路径混淆。如需重组组织结构,可与源命名空间不同名。 +- **IsPublic**:设为 `false`(私有)。迁移过程仅涉及数据搬运,访问权限在迁移完成后按需配置。 + +##### 最小创建(只含必填字段) + +```bash +tccli tcr CreateNamespace \ + --region \ + --RegistryId REGISTRY_ID \ + --NamespaceName NAMESPACE_NAME \ + --output json +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +##### 增强配置(指定访问级别) + +```bash +tccli tcr CreateNamespace \ + --region \ + --RegistryId REGISTRY_ID \ + --NamespaceName NAMESPACE_NAME \ + --IsPublic false \ + --output json +# expected: exit 0,返回 RequestId +``` + +#### 步骤 A3:在企业版创建目标仓库 + +##### 选择依据 + +- **RepositoryName**:建议与个人版仓库名保持一致。如需重命名,可在企业版使用新名称,迁移时通过 `SourceRepositoryName` 和 `RepositoryName` 分别指定源和目标。 +- **BriefDescription**:可选字段,建议添加描述标记迁移来源,便于后续管理。 + +##### 最小创建(只含必填字段) + +```bash +tccli tcr CreateRepository \ + --region \ + --RegistryId REGISTRY_ID \ + --NamespaceName NAMESPACE_NAME \ + --RepositoryName REPO_NAME \ + --output json +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +##### 增强配置(添加描述信息) + +```bash +tccli tcr CreateRepository \ + --region \ + --RegistryId REGISTRY_ID \ + --NamespaceName NAMESPACE_NAME \ + --RepositoryName REPO_NAME \ + --BriefDescription "从个人版迁移" \ + --output json +# expected: exit 0,返回 RequestId +``` + +#### 步骤 A4:执行镜像迁移 + +##### 选择依据 + +- **NamespaceName / RepositoryName**:目标企业版命名空间和仓库(步骤 A2/A3 创建的)。可与源命名空间/仓库不同名,支持迁移过程中重组组织结构。 +- **ImageVersion(Tag)**:每次迁移一个 Tag,允许选择性迁移。跳过不需要的 Tag(如临时构建版本)。 +- **幂等性**:`DuplicateImage` 对同一 Tag 重复执行会覆盖写入(幂等),可安全重放。迁移中断后重新执行同一命令即可续传。 + +##### 单 Tag 迁移 + +```bash +cat > duplicate-image.json <<'EOF' +{ + "RegistryId": "REGISTRY_ID", + "NamespaceName": "NAMESPACE_NAME", + "RepositoryName": "REPO_NAME", + "SourceNamespaceName": "SRC_NS", + "SourceRepositoryName": "SRC_REPO", + "ImageVersion": "TAG" +} +EOF + +tccli tcr DuplicateImage \ + --region \ + --cli-input-json file://duplicate-image.json \ + --output json +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +##### 批量 Tag 迁移(Shell 脚本) + +当需要迁移的 Tag 数量较多时,使用以下 Shell 脚本批量执行。脚本从个人版获取所有 Tag 列表,逐一迁移到企业版。 + +```bash +#!/bin/bash +# 批量迁移个人版仓库的所有 Tag 到企业版 +set -euo pipefail + +REGISTRY_ID="REGISTRY_ID" +DEST_NS="NAMESPACE_NAME" +DEST_REPO="REPO_NAME" +SRC_NS="SRC_NS" +SRC_REPO="SRC_REPO" +REGION="REGION" + +TAGS=$(tccli tcr DescribeImagePersonal \ + --region "$REGION" \ + --RepoName "$SRC_NS/$SRC_REPO" \ + --output json | jq -r '.Data.TagInfo[].TagName') + +for TAG in $TAGS; do + echo "Migrating: $SRC_NS/$SRC_REPO:$TAG" + tccli tcr DuplicateImage \ + --region "$REGION" \ + --RegistryId "$REGISTRY_ID" \ + --NamespaceName "$DEST_NS" \ + --RepositoryName "$DEST_REPO" \ + --SourceNamespaceName "$SRC_NS" \ + --SourceRepositoryName "$SRC_REPO" \ + --ImageVersion "$TAG" \ + --output json +done + +echo "Migration completed." +``` + +> 上述脚本使用行内参数遍历 Tag,每个 Tag 一次 `DuplicateImage` 调用。Tag 数量较多时,可加入 `sleep 1` 避免 API 限频。 + +##### 步骤 A1-A4 参数说明 + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `REGION` | 目标地域 | 如 `ap-guangzhou` | `tccli configure list` 查看 | +| `REGISTRY_ID` | 目标企业版实例 ID | 格式 `tcr-xxxxxxxx` | `tccli tcr DescribeInstances --region ` | +| `NAMESPACE_NAME` | 目标命名空间名 | 1-63 字符,步骤 A2 创建 | 自定义 | +| `REPO_NAME` | 目标仓库名 | 1-255 字符,步骤 A3 创建 | 自定义 | +| `SRC_NS` | 源个人版命名空间名 | 个人版中已存在 | 个人版控制台或 `DescribeImagePersonal` 查看 | +| `SRC_REPO` | 源个人版仓库名 | 个人版中已存在 | 同上 | +| `TAG` | 源镜像 Tag | 如 `latest`、`v1.0.0` | 步骤 A1 返回的 `TagName` | + +### 方案B:全量自动迁移(ccr2tcr 工具) + +--- + +适用场景:个人版中存在大量命名空间/仓库/标签,逐 Tag 迁移工作量大。ccr2tcr 是腾讯云官方提供的迁移工具,自动处理命名空间和仓库创建,检测已有镜像跳过不重复迁移。 + +#### 步骤 B1:确认企业版实例就绪 + +```bash +tccli tcr DescribeInstances \ + --region \ + --Registryids '["REGISTRY_ID"]' \ + --output json +# expected: exit 0, Status: "Running" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "example-registry.tencentcloudcr.com", + "CreatedAt": "2025-12-01T09:00:00+08:00" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +记录 `RegistryName`(`example-registry`),方案 B 步骤 B6 的 `--tcrName` 参数将使用此值。 + +#### 步骤 B2:配置 VPC 内网访问链路 + +使迁移工具所在的 CVM 可通过内网访问企业版实例,避免公网流量费用并提升迁移速度。 + +##### 选择依据 + +- **VpcId / SubnetId**:选择迁移工具 CVM 所在的 VPC 和子网,确保 CVM 与 TCR 实例网络互通。需提前确认 CVM 所在 VPC。 +- **Operation**:`Create` 为新增关联,`Delete` 为解除关联。每个实例最多关联 10 个 VPC。 + +##### B2.1 查看当前内网链路 + +```bash +tccli tcr DescribeInternalEndpoints \ + --region \ + --RegistryId REGISTRY_ID \ + --output json +# expected: exit 0,未配置时 AccessVpcSet 为空 +``` + +**预期输出**: + +```json +{ + "AccessVpcSet": [], + "TotalCount": 0, + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +##### B2.2 关联 VPC + +```bash +cat > manage-internal-endpoint.json <<'EOF' +{ + "RegistryId": "REGISTRY_ID", + "Operation": "Create", + "VpcId": "VPC_ID", + "SubnetId": "SUBNET_ID" +} +EOF + +tccli tcr ManageInternalEndpoint \ + --region \ + --cli-input-json file://manage-internal-endpoint.json \ + --output json +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RegistryId": "tcr-example", + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +##### B2.3 轮询确认链路就绪 + +```bash +tccli tcr DescribeInternalEndpoints \ + --region \ + --RegistryId REGISTRY_ID \ + --output json +# expected: exit 0, AccessVpcSet[0].Status == "Running" +``` + +**预期输出**: + +```json +{ + "AccessVpcSet": [ + { + "VpcId": "vpc-example", + "SubnetId": "subnet-example", + "Status": "Running", + "AccessIp": "10.0.0.100" + } + ], + "TotalCount": 1, + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +记录 `AccessIp`(如 `10.0.0.100`),步骤 B3 将使用此值。 + +#### 步骤 B3:启用内网 DNS 解析 + +```bash +cat > create-dns.json <<'EOF' +{ + "InstanceId": "REGISTRY_ID", + "VpcId": "VPC_ID", + "EniLBIp": "ACCESS_IP", + "UsePublicDomain": true +} +EOF + +tccli tcr CreateInternalEndpointDns \ + --region \ + --cli-input-json file://create-dns.json \ + --output json +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +轮询确认 DNS 状态为 ENABLED: + +```bash +tccli tcr DescribeInternalEndpointDnsStatus \ + --region \ + --VpcSet '[{"InstanceId":"REGISTRY_ID","VpcId":"VPC_ID","EniLBIp":"ACCESS_IP","UsePublicDomain":true}]' \ + --output json +# expected: exit 0, VpcSet[0].Status == "ENABLED" +``` + +**预期输出**: + +```json +{ + "VpcSet": [ + { + "VpcId": "vpc-example", + "Status": "ENABLED" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +#### 步骤 B4:获取企业版长期访问凭证 + +##### 选择依据 + +- **TokenType**:必须选 `longterm`。临时凭证(`temp`)在数小时后过期,可能导致长时间迁移任务中断后无法续传。长期凭证适用于迁移这类耗时操作。 +- **Desc**:建议填写描述如 `migration-token`,便于迁移完成后识别和清理。 + +```bash +tccli tcr CreateInstanceToken \ + --region \ + --RegistryId REGISTRY_ID \ + --TokenType longterm \ + --Desc "migration-token" \ + --output json +# expected: exit 0,返回 Username 和 Token +``` + +**预期输出**: + +```json +{ + "Username": "100012345678", + "Token": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ik9EVzJPUS05aloyN2NadHI2WjhFWUR0dHBSY1E2R2p...", + "ExpTime": 2096844746789, + "TokenId": "tkn-example", + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +> 记录 `Username`(即腾讯云账号 UIN)、`Token` 和 `TokenId`。`Token` 仅在创建时返回一次,之后无法查询——请立即保存。`TokenId` 用于迁移完成后清理。 + +#### 步骤 B5:准备 API 调用密钥 + +前往 [访问管理控制台](https://console.cloud.tencent.com/cam/capi) 获取 SecretId 和 SecretKey。 + +方案 B 需要两对密钥(个人版 API 密钥和企业版 API 密钥),实际通常使用同一对密钥,只需确保密钥关联的子账号具备个人版和企业版的双向操作权限。 + +#### 步骤 B6:执行全量迁移 + +```bash +docker pull ccr.ccs.tencentyun.com/tcrimages/image-transfer:ccr2tcr +# expected: 下载完成 +``` + +```bash +docker run --network=host --rm \ + ccr.ccs.tencentyun.com/tcrimages/image-transfer:ccr2tcr \ + /run \ + --tcrName REGISTRY_NAME \ + --ccrRegionName ap-guangzhou \ + --tcrRegionName REGION \ + --ccrAuth UIN:CCR_PASSWORD \ + --tcrAuth UIN:TCR_TOKEN \ + --ccrSecretId SECRET_ID \ + --ccrSecretKey SECRET_KEY \ + --tcrSecretId SECRET_ID \ + --tcrSecretKey SECRET_KEY \ + --tagNum 50 +# expected: 输出 "################# Finished, 0 transfer jobs failed ..." +``` + +**预期输出**: + +```text +################# Finished, 0 transfer jobs failed, 0 normal urlPair generate failed, 0 jobs generate failed ################# +``` + +| 参数 | 说明 | 获取方式 | +|------|------|---------| +| `REGISTRY_NAME` | 目标企业版实例名称 | 步骤 B1 返回的 `RegistryName`,如 `example-registry` | +| `REGION` | 企业版实例所在的地域 | 步骤 B1 或 `tccli configure list` | +| `UIN` | 腾讯云主账号 UIN | 步骤 B4 返回的 `Username` | +| `CCR_PASSWORD` | 个人版访问密码 | 个人版控制台 → 访问凭证 | +| `TCR_TOKEN` | 企业版长期访问凭证 Token | 步骤 B4 返回的 `Token` | +| `SECRET_ID` | API 调用 SecretId | 步骤 B5 从 CAM 控制台获取 | +| `SECRET_KEY` | API 调用 SecretKey | 步骤 B5 从 CAM 控制台获取 | +| `--tagNum` | 每个仓库每次迁移的 Tag 数量 | 默认 20,可按需调整为 50 或更大 | + +### 业务切换 + +#### 方式一:零配置切换(推荐) + +通过个人版域名兼容功能,业务集群无需修改镜像地址即可拉取已迁移到企业版的镜像。参见[使用个人版域名访问企业版实例](../personal-domain-access)。 + +#### 方式二:镜像地址更新 + +将 CI/CD 配置和 Kubernetes 工作负载中的镜像地址从个人版切换为企业版。 + +| 版本 | 镜像地址格式 | +|------|-------------| +| 个人版 | `ccr.ccs.tencentyun.com//:` | +| 企业版 | `.tencentcloudcr.com//:` | + +创建用于拉取企业版镜像的 `imagePullSecret`: + +```bash +kubectl create secret docker-registry tcr-pull-secret \ + --docker-server=REGISTRY_NAME.tencentcloudcr.com \ + --docker-username=UIN \ + --docker-password=TCR_TOKEN \ + -n NAMESPACE_NAME +# expected: secret/tcr-pull-secret created +``` + +```yaml +# deployment.yaml +spec: + template: + spec: + imagePullSecrets: + - name: tcr-pull-secret + containers: + - name: app + image: REGISTRY_NAME.tencentcloudcr.com/NAMESPACE_NAME/REPO_NAME:TAG +``` + +> TKE 集群推荐安装 TCR 插件实现免密拉取(参见[TKE 集群使用 TCR 插件内网免密拉取容器镜像](../../tke-plugin-pull)),无需手动配置 `imagePullSecret`。 + +## 验证 + +### 控制面(tccli) + +| 验证项 | 命令 | 期望 | +|--------|------|------| +| 命名空间已创建 | `tccli tcr DescribeNamespaces --region --RegistryId REGISTRY_ID --output json` | `NamespaceNames` 列表含目标命名空间 | +| 仓库已创建 | `tccli tcr DescribeRepositories --region --RegistryId REGISTRY_ID --NamespaceName NAMESPACE_NAME --output json` | `RepositoryList` 含目标仓库 | +| 镜像已迁移(方案 A) | `tccli tcr DescribeImages --region --RegistryId REGISTRY_ID --NamespaceName NAMESPACE_NAME --RepositoryName REPO_NAME --output json` | `ImageInfoList` 含迁移的 Tag,数量与源一致 | +| 内网链路就绪(方案 B) | `tccli tcr DescribeInternalEndpoints --region --RegistryId REGISTRY_ID --output json` | `AccessVpcSet[0].Status: "Running"` | +| DNS 解析生效(方案 B) | `tccli tcr DescribeInternalEndpointDnsStatus --region --VpcSet '[{"InstanceId":"REGISTRY_ID","VpcId":"VPC_ID","EniLBIp":"ACCESS_IP","UsePublicDomain":true}]' --output json` | `VpcSet[0].Status: "ENABLED"` | + +### 数据面(docker) + +```bash +# 登录企业版实例 +docker login REGISTRY_NAME.tencentcloudcr.com --username=UIN --password=TCR_TOKEN +# expected: Login Succeeded + +# 拉取已迁移的镜像 +docker pull REGISTRY_NAME.tencentcloudcr.com/NAMESPACE_NAME/REPO_NAME:TAG +# expected: Downloaded newer image,或 Already exists(如本地已缓存) +``` + +## 清理 + +> **计费警告**:TCR 企业版实例按规格持续计费。迁移完成后如仅用于验证,请及时[销毁退还实例](../../../../ops/instances/delete)。个人版源镜像建议保留一段时间作为备份。 +> +> **副作用警告**:`ManageInternalEndpoint` 以 `Operation: "Delete"` 调用会解除 VPC 与实例的内网关联,依赖该链路的业务集群将无法通过内网拉取镜像。清理前请确认无生产业务依赖该链路。 + +### 数据面 + +```bash +# 清理 ccr2tcr 工具镜像(方案 B 使用后) +docker rmi ccr.ccs.tencentyun.com/tcrimages/image-transfer:ccr2tcr +# expected: 成功删除镜像 +``` + +### 控制面(tccli) + +#### 1. 清理前状态检查 + +```bash +tccli tcr DescribeInstances \ + --region \ + --Registryids '["REGISTRY_ID"]' \ + --output json +# expected: exit 0,确认是待操作的目标实例,记录 RegistryId +``` + +#### 2. 删除迁移专用访问凭证 + +```bash +tccli tcr DeleteInstanceToken \ + --region \ + --RegistryId REGISTRY_ID \ + --TokenId TOKEN_ID \ + --output json +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +#### 3. 删除内网 DNS 解析 + +```bash +tccli tcr DeleteInternalEndpointDns \ + --region \ + --InstanceId REGISTRY_ID \ + --VpcId VPC_ID \ + --EniLBIp ACCESS_IP \ + --output json +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +#### 4. 删除 VPC 内网访问链路 + +```bash +tccli tcr ManageInternalEndpoint \ + --region \ + --RegistryId REGISTRY_ID \ + --Operation Delete \ + --VpcId VPC_ID \ + --SubnetId SUBNET_ID \ + --output json +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +#### 5. 验证已清理 + +```bash +tccli tcr DescribeInternalEndpoints \ + --region \ + --RegistryId REGISTRY_ID \ + --output json +# expected: exit 0, AccessVpcSet 为空,TotalCount: 0 +``` + +**预期输出**: + +```json +{ + "AccessVpcSet": [], + "TotalCount": 0, + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `DuplicateImage` 返回 `ResourceNotFound` | `tccli tcr DescribeImagePersonal --region --RepoName "SRC_NS/SRC_REPO" --output json` 检查源仓库和 Tag 是否存在 | 源命名空间/仓库名拼写错误或 Tag 不存在 | 核对 `SourceNamespaceName`、`SourceRepositoryName`、`ImageVersion` 拼写;用步骤 A1 的输出确认准确的命名空间名、仓库名和 Tag 名 | +| `DuplicateImage` 返回 `FailedOperation` | `tccli tcr DescribeRepositories --region --RegistryId REGISTRY_ID --NamespaceName NAMESPACE_NAME --output json` 确认目标仓库存在 | 目标企业版仓库未创建 | 先执行步骤 A3 `CreateRepository`,再重新执行 `DuplicateImage` | +| `ManageInternalEndpoint` 返回 `LimitExceeded` | `tccli vpc DescribeVpcs --region --output json` 查看 VPC 数量和配额 | 账号 VPC 配额已满(此为环境限制,非命令错误) | 清理不再使用的 VPC 后重试,或使用已有 VPC | +| `CreateInstanceToken` 返回 `UnauthorizedOperation` | `tccli tcr DescribeInstances --region --output json` 确认子账号有读取权限 | 子账号缺少 `tcr:CreateInstanceToken` 权限 | 授予 `QcloudTCRFullAccess` 预设策略,或添加自定义策略含 `tcr:CreateInstanceToken` | +| `CreateInternalEndpointDns` 返回 `FailedOperation` | `tccli tcr DescribeInternalEndpoints --region --RegistryId REGISTRY_ID --output json` 确认内网链路已 Running | VPC 内网链路尚未就绪,或 `EniLBIp` 填写了错误的 AccessIp | 等待内网链路 Status 变为 Running 后重试;确认 `EniLBIp` 与 `DescribeInternalEndpoints` 返回的 `AccessIp` 一致 | +| ccr2tcr 工具报失败数非零 | 查看工具输出的具体错误行(如 `transfer jobs failed: N`) | 网络波动导致部分镜像传输中断或 API 调用超时 | 重新运行 ccr2tcr 工具(幂等,已迁移的会自动跳过)。持续失败可通过 [在线咨询](https://cloud.tencent.com/online-service?from=doc_1141) 申请协助 | +| ccr2tcr 报命名空间命名冲突 | 查看工具输出中的冲突命名空间名 | 使用了 `library`、`tke`、`public` 等企业版保留命名空间,或与实例已有命名空间同名 | 在个人版中重命名冲突的命名空间后再运行,或手动创建非冲突的企业版命名空间并用方案 A 逐 Tag 迁移该部分 | + +### 迁移结果异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 迁移后镜像 Tag 数量少于预期 | 对比个人版与企业版 Tag 列表:`tccli tcr DescribeImagePersonal --region --RepoName "SRC_NS/SRC_REPO" --output json | jq '.Data.TotalCount'` vs `tccli tcr DescribeImages --region --RegistryId REGISTRY_ID --NamespaceName NAMESPACE_NAME --RepositoryName REPO_NAME --output json | jq '.TotalCount'` | 部分 Tag 迁移失败(脚本中途退出、API 限频、网络超时) | 对缺失 Tag 重新执行 `DuplicateImage`(幂等,不会重复创建) | +| 迁移后 `docker pull` 返回 `unauthorized` | `docker login REGISTRY_NAME.tencentcloudcr.com --username=UIN --password=TCR_TOKEN` 确认登录状态 | 凭证错误或已过期(`temp` 类型),或未登录 | 使用 `longterm` 类型 Token 重新登录。确认用户名是 `CreateInstanceToken` 返回的 `Username`(UIN),密码是 `Token`(不是 SecretId/SecretKey) | +| 迁移后 `docker pull` 返回 `not found` | `tccli tcr DescribeImages --region --RegistryId REGISTRY_ID --NamespaceName NAMESPACE_NAME --RepositoryName REPO_NAME --output json` 确认 Tag 存在 | 命名空间/仓库名拼写错误或 Tag 不存在 | 用 `DescribeImages` 获取准确的命名空间名和仓库名,核对 `docker pull` 地址中的路径 | +| Pod 报 `ImagePullBackOff`(方式二) | `kubectl describe pod POD_NAME -n NAMESPACE_NAME` 查看 Events 详情 | 镜像地址或 `imagePullSecret` 配置错误 | 核对 `image` 字段格式为 `REGISTRY_NAME.tencentcloudcr.com/NAMESPACE_NAME/REPO_NAME:TAG`;确认 Secret 名称在 `imagePullSecrets` 中拼写正确;确认 `kubectl create secret docker-registry` 时 `--docker-server` 不含 `https://` 前缀 | + +## 下一步 + +- [使用个人版域名访问企业版实例](https://cloud.tencent.com/document/product/1141/82855) — 零配置切换业务访问 +- [TKE 集群使用 TCR 插件内网免密拉取容器镜像](https://cloud.tencent.com/document/product/1141/48184) — 免密拉取配置 +- [配置内网访问控制](https://cloud.tencent.com/document/product/1141/61445) — VPC 内网链路管理 +- [配置自定义域名](https://cloud.tencent.com/document/product/1141/61444) — 统一域名管理 +- [销毁退还实例](https://cloud.tencent.com/document/product/1141/61442) — 实例生命周期管理 + +## 控制台替代 + +[容器镜像服务控制台](https://console.cloud.tencent.com/tcr) -> 实例列表 -> 目标企业版实例 -> 镜像仓库 -> **自动迁移**功能,或配合 ccr2tcr 工具在 CVM 上执行全量迁移。 diff --git a/src/content/docs/cli/tcr/practices/personal-migration/personal-migration-tcrctl.md b/src/content/docs/cli/tcr/practices/personal-migration/personal-migration-tcrctl.md new file mode 100644 index 0000000..7583f3e --- /dev/null +++ b/src/content/docs/cli/tcr/practices/personal-migration/personal-migration-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "" +description: "# 产品 CLI 操作" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +# 产品 CLI 操作 + +> 页型 P1 · 使用 crctl 完成本页任务 · P0 仅提供 stub 占位,P1 阶段完成正文。 + diff --git a/src/content/docs/cli/tcr/practices/personal-migration/personal-migration.md b/src/content/docs/cli/tcr/practices/personal-migration/personal-migration.md new file mode 100644 index 0000000..8b3ee5f --- /dev/null +++ b/src/content/docs/cli/tcr/practices/personal-migration/personal-migration.md @@ -0,0 +1,774 @@ +--- +title: "个人版迁移至企业版完全指南(tccli)" +description: "· page_id `52292`" +--- + +> 对照官方:[个人版迁移至企业版完全指南](https://cloud.tencent.com/document/product/1141/52292) · page_id `52292` + +## 概述 + +将腾讯云容器镜像服务(TCR)个人版中的容器镜像与 Helm Chart 迁移至 TCR 企业版实例。个人版为免费共享服务,企业版为独享实例,提供更稳定的服务质量和内网访问、镜像安全扫描、实例同步等功能。 + +| 维度 | 方案A:DuplicateImage(逐 Tag) | 方案B:ccr2tcr 工具(全量自动) | +|------|-------------------------------|--------------------------------| +| 适用规模 | 少量镜像(<50 Tag) | 大量镜像(≥50 Tag) | +| 迁移粒度 | 逐 Tag 精确控制,支持跨命名空间重命名 | 全量自动迁移,保留原有组织结构 | +| 操作方式 | tccli 命令行 + 可选 Shell 批处理脚本 | Docker 容器运行,一条命令完成 | +| 前置依赖 | tccli + CAM 权限 | CVM + VPC 内网链路 + API 密钥对 | +| 幂等性 | 是(同一 Tag 重复迁移覆盖写入) | 是(检测已存在则跳过) | +| 推荐场景 | 精确控制、少量仓库、跨命名空间重组 | 全量搬迁、多仓库、追求效率 | + +核心流程:(1)在企业版创建目标命名空间与仓库;(2)方案 A 用 `DuplicateImage` 逐 Tag 迁移,方案 B 用 `ccr2tcr` 工具全量自动迁移;(3)配置内网访问链路使业务集群可拉取;(4)通过个人版域名兼容实现零配置切换或更新镜像地址。 + +## 前置条件 + +- [环境准备](../../../index.md) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域 + +# 3. 检查 TCR 企业版实例查询权限 +tccli tcr DescribeInstances --region +# expected: exit 0,返回实例列表(可为空) + +# 4. 检查个人版镜像查询权限 +tccli tcr DescribeImagePersonal --region --RepoName "namespace/repo" +# expected: exit 0(仓库存在返回 Tag 列表,不存在返回 ResourceNotFound 也说明权限 OK) + +# 5. 检查 VPC 查询权限 +tccli vpc DescribeVpcs --region +# expected: exit 0,返回 VPC 列表(可为空) +``` + +> CAM 所需权限(精确 Action 名):`tcr:DescribeInstances`、`tcr:DescribeImagePersonal`、`tcr:DuplicateImage`、`tcr:CreateNamespace`、`tcr:DescribeNamespaces`、`tcr:CreateRepository`、`tcr:DescribeRepositories`、`tcr:ManageInternalEndpoint`、`tcr:DescribeInternalEndpoints`、`tcr:CreateInternalEndpointDns`、`tcr:DescribeInternalEndpointDnsStatus`、`tcr:CreateInstanceToken`、`tcr:DescribeInstanceToken`、`tcr:DeleteInstanceToken`。建议授予 `QcloudTCRFullAccess` 预设策略。方案 B 额外需要 `cvm:DescribeInstances`。 + +### 资源检查 + +```bash +# 6. 确认 TCR 企业版实例已创建且状态为 Running +tccli tcr DescribeInstances --region --Registryids '["REGISTRY_ID"]' +# expected: exit 0, Status: "Running" + +# 7. 确认个人版命名空间中存在待迁移镜像 +tccli tcr DescribeImagePersonal --region --RepoName "SRC_NS/SRC_REPO" +# expected: exit 0, Data.TagInfo 非空,TotalCount > 0 + +# 8. 查询可用 VPC 和子网(方案 B 配置内网访问链路需要) +tccli vpc DescribeVpcs --region --Filters '[{"Name":"vpc-id","Values":["VPC_ID"]}]' +# expected: 至少返回 1 个 VPC,状态 Available + +tccli vpc DescribeSubnets --region --Filters '[{"Name":"vpc-id","Values":["VPC_ID"]}]' +# expected: 至少返回 1 个子网,AvailableIpCount ≥ 1 + +# 9.(方案 B)确认 CVM 可用 +tccli cvm DescribeInstances --region --Filters '[{"Name":"instance-id","Values":["INS_ID"]}]' +# expected: 至少返回 1 台 CVM,InstanceState: "RUNNING" +``` + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 查看个人版镜像列表 | `DescribeImagePersonal` | 是 | +| 查看企业版实例列表 | `DescribeInstances` | 是 | +| 创建企业版命名空间 | `CreateNamespace` | 否(同名存在则报错) | +| 创建企业版镜像仓库 | `CreateRepository` | 否(同名存在则报错) | +| 迁移镜像(逐 Tag) | `DuplicateImage` | 是(幂等覆盖写入) | +| 关联 VPC 至 TCR 实例 | `ManageInternalEndpoint` | 否 | +| 查看内网访问链路 | `DescribeInternalEndpoints` | 是 | +| 开启内网 DNS 解析 | `CreateInternalEndpointDns` | 否 | +| 查询 DNS 解析状态 | `DescribeInternalEndpointDnsStatus` | 是 | +| 创建长期访问凭证 | `CreateInstanceToken` | 否 | +| 全量自动迁移 | `docker run ccr2tcr` | 是(检测已存在则跳过) | + +## 关键字段说明 + +以下列出主要操作的参数约束。完整参数定义见 `tccli tcr --generate-cli-skeleton`。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|-----------|---------| +| `RegistryId` | String | 是 | 目标企业版实例 ID,格式 `tcr-xxxxxxxx`。`DescribeInstances` 返回 | `ResourceNotFound` — 实例不存在 | +| `NamespaceName` | String | 是 | 命名空间名,1-63 字符,以小写字母或数字开头和结尾 | `InvalidParameter` — 命名空间名格式不合法 | +| `RepositoryName` | String | 是 | 镜像仓库名,1-255 字符。同命名空间下不可重名 | `InvalidParameter.RepositoryName` — 仓库名冲突 | +| `SourceNamespaceName` | String | 是 | 源命名空间名(个人版命名空间) | `ResourceNotFound` — 源命名空间不存在 | +| `SourceRepositoryName` | String | 是 | 源仓库名(个人版仓库) | `ResourceNotFound` — 源仓库不存在 | +| `ImageVersion` | String | 是 | 镜像 Tag 名称,如 `latest`、`v1.0.0` | `ResourceNotFound` — Tag 不存在 | +| `RepoName` | String | 是 | 个人版仓库全名,格式 `<命名空间>/<仓库名>`,如 `myns/myapp` | `ResourceNotFound` — 仓库拼写错误导致查询失败 | +| `TokenType` | String | 是 | `longterm`(长期有效,推荐生产)或 `temp`(临时,数小时后过期) | 选 `temp` → 凭证过期后拉取失败;未存储 Token 值无法找回 | +| `VpcId` | String | 方案 B | 已有 VPC ID,格式 `vpc-xxxxxxxx`。`DescribeInternalEndpoints` 获取已关联列表 | `FailedOperation` — VPC 不存在或不属于当前账号 | +| `SubnetId` | String | 方案 B | VPC 下子网 ID,格式 `subnet-xxxxxxxx` | `FailedOperation` — 子网不存在或可用 IP 不足 | +| `EniLBIp` | String | 方案 B | 内网访问 IP,在 `DescribeInternalEndpoints` 返回的 `AccessIp` 字段获取 | — IP 不正确 → DNS 解析关联错误 | +| `UsePublicDomain` | Boolean | 方案 B | `true`:使用默认域名解析到内网 IP | `false` → 内网 DNS 不会自动解析默认域名 | + +## 操作步骤 + +### 方案A:逐 Tag 精确迁移(DuplicateImage) + +--- + +适用场景:仅需迁移特定命名空间/仓库的特定 Tag,或需在迁移过程中重命名命名空间/仓库。 + +#### 步骤 A1:获取个人版镜像列表 + +```bash +tccli tcr DescribeImagePersonal \ + --region \ + --RepoName "SRC_NS/SRC_REPO" \ + --Limit 100 \ + --output json +# expected: exit 0,返回 Tag 列表 +``` + +**预期输出**: + +```json +{ + "Data": { + "TagInfo": [ + { + "TagName": "latest", + "TagId": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "ImageId": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "Size": 123456789, + "CreationTime": "2025-12-01T09:00:00+08:00", + "PushTime": "2026-01-15T10:30:00+08:00" + } + ], + "TotalCount": 1 + }, + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +记录需要迁移的 Tag 名称列表,以及源命名空间名(`SRC_NS`)和源仓库名(`SRC_REPO`)。 + +#### 步骤 A2:在企业版创建目标命名空间 + +##### 选择依据 + +- **NamespaceName**:建议与个人版命名空间名保持一致,减少 CI/CD 配置变更和路径混淆。如需重组组织结构,可与源命名空间不同名。 +- **IsPublic**:设为 `false`(私有)。迁移过程仅涉及数据搬运,访问权限在迁移完成后按需配置。 + +##### 最小创建(只含必填字段) + +```bash +tccli tcr CreateNamespace \ + --region \ + --RegistryId REGISTRY_ID \ + --NamespaceName NAMESPACE_NAME \ + --output json +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +##### 增强配置(指定访问级别) + +```bash +tccli tcr CreateNamespace \ + --region \ + --RegistryId REGISTRY_ID \ + --NamespaceName NAMESPACE_NAME \ + --IsPublic false \ + --output json +# expected: exit 0,返回 RequestId +``` + +#### 步骤 A3:在企业版创建目标仓库 + +##### 选择依据 + +- **RepositoryName**:建议与个人版仓库名保持一致。如需重命名,可在企业版使用新名称,迁移时通过 `SourceRepositoryName` 和 `RepositoryName` 分别指定源和目标。 +- **BriefDescription**:可选字段,建议添加描述标记迁移来源,便于后续管理。 + +##### 最小创建(只含必填字段) + +```bash +tccli tcr CreateRepository \ + --region \ + --RegistryId REGISTRY_ID \ + --NamespaceName NAMESPACE_NAME \ + --RepositoryName REPO_NAME \ + --output json +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +##### 增强配置(添加描述信息) + +```bash +tccli tcr CreateRepository \ + --region \ + --RegistryId REGISTRY_ID \ + --NamespaceName NAMESPACE_NAME \ + --RepositoryName REPO_NAME \ + --BriefDescription "从个人版迁移" \ + --output json +# expected: exit 0,返回 RequestId +``` + +#### 步骤 A4:执行镜像迁移 + +##### 选择依据 + +- **NamespaceName / RepositoryName**:目标企业版命名空间和仓库(步骤 A2/A3 创建的)。可与源命名空间/仓库不同名,支持迁移过程中重组组织结构。 +- **ImageVersion(Tag)**:每次迁移一个 Tag,允许选择性迁移。跳过不需要的 Tag(如临时构建版本)。 +- **幂等性**:`DuplicateImage` 对同一 Tag 重复执行会覆盖写入(幂等),可安全重放。迁移中断后重新执行同一命令即可续传。 + +##### 单 Tag 迁移 + +```bash +cat > duplicate-image.json <<'EOF' +{ + "RegistryId": "REGISTRY_ID", + "NamespaceName": "NAMESPACE_NAME", + "RepositoryName": "REPO_NAME", + "SourceNamespaceName": "SRC_NS", + "SourceRepositoryName": "SRC_REPO", + "ImageVersion": "TAG" +} +EOF + +tccli tcr DuplicateImage \ + --region \ + --cli-input-json file://duplicate-image.json \ + --output json +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +##### 批量 Tag 迁移(Shell 脚本) + +当需要迁移的 Tag 数量较多时,使用以下 Shell 脚本批量执行。脚本从个人版获取所有 Tag 列表,逐一迁移到企业版。 + +```bash +#!/bin/bash +# 批量迁移个人版仓库的所有 Tag 到企业版 +set -euo pipefail + +REGISTRY_ID="REGISTRY_ID" +DEST_NS="NAMESPACE_NAME" +DEST_REPO="REPO_NAME" +SRC_NS="SRC_NS" +SRC_REPO="SRC_REPO" +REGION="REGION" + +TAGS=$(tccli tcr DescribeImagePersonal \ + --region "$REGION" \ + --RepoName "$SRC_NS/$SRC_REPO" \ + --output json | jq -r '.Data.TagInfo[].TagName') + +for TAG in $TAGS; do + echo "Migrating: $SRC_NS/$SRC_REPO:$TAG" + tccli tcr DuplicateImage \ + --region "$REGION" \ + --RegistryId "$REGISTRY_ID" \ + --NamespaceName "$DEST_NS" \ + --RepositoryName "$DEST_REPO" \ + --SourceNamespaceName "$SRC_NS" \ + --SourceRepositoryName "$SRC_REPO" \ + --ImageVersion "$TAG" \ + --output json +done + +echo "Migration completed." +``` + +> 上述脚本使用行内参数遍历 Tag,每个 Tag 一次 `DuplicateImage` 调用。Tag 数量较多时,可加入 `sleep 1` 避免 API 限频。 + +##### 步骤 A1-A4 参数说明 + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `REGION` | 目标地域 | 如 `ap-guangzhou` | `tccli configure list` 查看 | +| `REGISTRY_ID` | 目标企业版实例 ID | 格式 `tcr-xxxxxxxx` | `tccli tcr DescribeInstances --region ` | +| `NAMESPACE_NAME` | 目标命名空间名 | 1-63 字符,步骤 A2 创建 | 自定义 | +| `REPO_NAME` | 目标仓库名 | 1-255 字符,步骤 A3 创建 | 自定义 | +| `SRC_NS` | 源个人版命名空间名 | 个人版中已存在 | 个人版控制台或 `DescribeImagePersonal` 查看 | +| `SRC_REPO` | 源个人版仓库名 | 个人版中已存在 | 同上 | +| `TAG` | 源镜像 Tag | 如 `latest`、`v1.0.0` | 步骤 A1 返回的 `TagName` | + +### 方案B:全量自动迁移(ccr2tcr 工具) + +--- + +适用场景:个人版中存在大量命名空间/仓库/标签,逐 Tag 迁移工作量大。ccr2tcr 是腾讯云官方提供的迁移工具,自动处理命名空间和仓库创建,检测已有镜像跳过不重复迁移。 + +#### 步骤 B1:确认企业版实例就绪 + +```bash +tccli tcr DescribeInstances \ + --region \ + --Registryids '["REGISTRY_ID"]' \ + --output json +# expected: exit 0, Status: "Running" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "example-registry.tencentcloudcr.com", + "CreatedAt": "2025-12-01T09:00:00+08:00" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +记录 `RegistryName`(`example-registry`),方案 B 步骤 B6 的 `--tcrName` 参数将使用此值。 + +#### 步骤 B2:配置 VPC 内网访问链路 + +使迁移工具所在的 CVM 可通过内网访问企业版实例,避免公网流量费用并提升迁移速度。 + +##### 选择依据 + +- **VpcId / SubnetId**:选择迁移工具 CVM 所在的 VPC 和子网,确保 CVM 与 TCR 实例网络互通。需提前确认 CVM 所在 VPC。 +- **Operation**:`Create` 为新增关联,`Delete` 为解除关联。每个实例最多关联 10 个 VPC。 + +##### B2.1 查看当前内网链路 + +```bash +tccli tcr DescribeInternalEndpoints \ + --region \ + --RegistryId REGISTRY_ID \ + --output json +# expected: exit 0,未配置时 AccessVpcSet 为空 +``` + +**预期输出**: + +```json +{ + "AccessVpcSet": [], + "TotalCount": 0, + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +##### B2.2 关联 VPC + +```bash +cat > manage-internal-endpoint.json <<'EOF' +{ + "RegistryId": "REGISTRY_ID", + "Operation": "Create", + "VpcId": "VPC_ID", + "SubnetId": "SUBNET_ID" +} +EOF + +tccli tcr ManageInternalEndpoint \ + --region \ + --cli-input-json file://manage-internal-endpoint.json \ + --output json +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RegistryId": "tcr-example", + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +##### B2.3 轮询确认链路就绪 + +```bash +tccli tcr DescribeInternalEndpoints \ + --region \ + --RegistryId REGISTRY_ID \ + --output json +# expected: exit 0, AccessVpcSet[0].Status == "Running" +``` + +**预期输出**: + +```json +{ + "AccessVpcSet": [ + { + "VpcId": "vpc-example", + "SubnetId": "subnet-example", + "Status": "Running", + "AccessIp": "10.0.0.100" + } + ], + "TotalCount": 1, + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +记录 `AccessIp`(如 `10.0.0.100`),步骤 B3 将使用此值。 + +#### 步骤 B3:启用内网 DNS 解析 + +```bash +cat > create-dns.json <<'EOF' +{ + "InstanceId": "REGISTRY_ID", + "VpcId": "VPC_ID", + "EniLBIp": "ACCESS_IP", + "UsePublicDomain": true +} +EOF + +tccli tcr CreateInternalEndpointDns \ + --region \ + --cli-input-json file://create-dns.json \ + --output json +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +轮询确认 DNS 状态为 ENABLED: + +```bash +tccli tcr DescribeInternalEndpointDnsStatus \ + --region \ + --VpcSet '[{"InstanceId":"REGISTRY_ID","VpcId":"VPC_ID","EniLBIp":"ACCESS_IP","UsePublicDomain":true}]' \ + --output json +# expected: exit 0, VpcSet[0].Status == "ENABLED" +``` + +**预期输出**: + +```json +{ + "VpcSet": [ + { + "VpcId": "vpc-example", + "Status": "ENABLED" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +#### 步骤 B4:获取企业版长期访问凭证 + +##### 选择依据 + +- **TokenType**:必须选 `longterm`。临时凭证(`temp`)在数小时后过期,可能导致长时间迁移任务中断后无法续传。长期凭证适用于迁移这类耗时操作。 +- **Desc**:建议填写描述如 `migration-token`,便于迁移完成后识别和清理。 + +```bash +tccli tcr CreateInstanceToken \ + --region \ + --RegistryId REGISTRY_ID \ + --TokenType longterm \ + --Desc "migration-token" \ + --output json +# expected: exit 0,返回 Username 和 Token +``` + +**预期输出**: + +```json +{ + "Username": "100012345678", + "Token": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ik9EVzJPUS05aloyN2NadHI2WjhFWUR0dHBSY1E2R2p...", + "ExpTime": 2096844746789, + "TokenId": "tkn-example", + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +> 记录 `Username`(即腾讯云账号 UIN)、`Token` 和 `TokenId`。`Token` 仅在创建时返回一次,之后无法查询——请立即保存。`TokenId` 用于迁移完成后清理。 + +#### 步骤 B5:准备 API 调用密钥 + +前往 [访问管理控制台](https://console.cloud.tencent.com/cam/capi) 获取 SecretId 和 SecretKey。 + +方案 B 需要两对密钥(个人版 API 密钥和企业版 API 密钥),实际通常使用同一对密钥,只需确保密钥关联的子账号具备个人版和企业版的双向操作权限。 + +#### 步骤 B6:执行全量迁移 + +```bash +docker pull ccr.ccs.tencentyun.com/tcrimages/image-transfer:ccr2tcr +# expected: 下载完成 +``` + +```bash +docker run --network=host --rm \ + ccr.ccs.tencentyun.com/tcrimages/image-transfer:ccr2tcr \ + /run \ + --tcrName REGISTRY_NAME \ + --ccrRegionName ap-guangzhou \ + --tcrRegionName REGION \ + --ccrAuth UIN:CCR_PASSWORD \ + --tcrAuth UIN:TCR_TOKEN \ + --ccrSecretId SECRET_ID \ + --ccrSecretKey SECRET_KEY \ + --tcrSecretId SECRET_ID \ + --tcrSecretKey SECRET_KEY \ + --tagNum 50 +# expected: 输出 "################# Finished, 0 transfer jobs failed ..." +``` + +**预期输出**: + +```text +################# Finished, 0 transfer jobs failed, 0 normal urlPair generate failed, 0 jobs generate failed ################# +``` + +| 参数 | 说明 | 获取方式 | +|------|------|---------| +| `REGISTRY_NAME` | 目标企业版实例名称 | 步骤 B1 返回的 `RegistryName`,如 `example-registry` | +| `REGION` | 企业版实例所在的地域 | 步骤 B1 或 `tccli configure list` | +| `UIN` | 腾讯云主账号 UIN | 步骤 B4 返回的 `Username` | +| `CCR_PASSWORD` | 个人版访问密码 | 个人版控制台 → 访问凭证 | +| `TCR_TOKEN` | 企业版长期访问凭证 Token | 步骤 B4 返回的 `Token` | +| `SECRET_ID` | API 调用 SecretId | 步骤 B5 从 CAM 控制台获取 | +| `SECRET_KEY` | API 调用 SecretKey | 步骤 B5 从 CAM 控制台获取 | +| `--tagNum` | 每个仓库每次迁移的 Tag 数量 | 默认 20,可按需调整为 50 或更大 | + +### 业务切换 + +#### 方式一:零配置切换(推荐) + +通过个人版域名兼容功能,业务集群无需修改镜像地址即可拉取已迁移到企业版的镜像。参见[使用个人版域名访问企业版实例](../personal-domain-access)。 + +#### 方式二:镜像地址更新 + +将 CI/CD 配置和 Kubernetes 工作负载中的镜像地址从个人版切换为企业版。 + +| 版本 | 镜像地址格式 | +|------|-------------| +| 个人版 | `ccr.ccs.tencentyun.com//:` | +| 企业版 | `.tencentcloudcr.com//:` | + +创建用于拉取企业版镜像的 `imagePullSecret`: + +```bash +kubectl create secret docker-registry tcr-pull-secret \ + --docker-server=REGISTRY_NAME.tencentcloudcr.com \ + --docker-username=UIN \ + --docker-password=TCR_TOKEN \ + -n NAMESPACE_NAME +# expected: secret/tcr-pull-secret created +``` + +```yaml +# deployment.yaml +spec: + template: + spec: + imagePullSecrets: + - name: tcr-pull-secret + containers: + - name: app + image: REGISTRY_NAME.tencentcloudcr.com/NAMESPACE_NAME/REPO_NAME:TAG +``` + +> TKE 集群推荐安装 TCR 插件实现免密拉取(参见[TKE 集群使用 TCR 插件内网免密拉取容器镜像](../../tke-plugin-pull)),无需手动配置 `imagePullSecret`。 + +## 验证 + +### 控制面(tccli) + +| 验证项 | 命令 | 期望 | +|--------|------|------| +| 命名空间已创建 | `tccli tcr DescribeNamespaces --region --RegistryId REGISTRY_ID --output json` | `NamespaceNames` 列表含目标命名空间 | +| 仓库已创建 | `tccli tcr DescribeRepositories --region --RegistryId REGISTRY_ID --NamespaceName NAMESPACE_NAME --output json` | `RepositoryList` 含目标仓库 | +| 镜像已迁移(方案 A) | `tccli tcr DescribeImages --region --RegistryId REGISTRY_ID --NamespaceName NAMESPACE_NAME --RepositoryName REPO_NAME --output json` | `ImageInfoList` 含迁移的 Tag,数量与源一致 | +| 内网链路就绪(方案 B) | `tccli tcr DescribeInternalEndpoints --region --RegistryId REGISTRY_ID --output json` | `AccessVpcSet[0].Status: "Running"` | +| DNS 解析生效(方案 B) | `tccli tcr DescribeInternalEndpointDnsStatus --region --VpcSet '[{"InstanceId":"REGISTRY_ID","VpcId":"VPC_ID","EniLBIp":"ACCESS_IP","UsePublicDomain":true}]' --output json` | `VpcSet[0].Status: "ENABLED"` | + +### 数据面(docker) + +```bash +# 登录企业版实例 +docker login REGISTRY_NAME.tencentcloudcr.com --username=UIN --password=TCR_TOKEN +# expected: Login Succeeded + +# 拉取已迁移的镜像 +docker pull REGISTRY_NAME.tencentcloudcr.com/NAMESPACE_NAME/REPO_NAME:TAG +# expected: Downloaded newer image,或 Already exists(如本地已缓存) +``` + +## 清理 + +> **计费警告**:TCR 企业版实例按规格持续计费。迁移完成后如仅用于验证,请及时[销毁退还实例](../../../../ops/instances/delete)。个人版源镜像建议保留一段时间作为备份。 +> +> **副作用警告**:`ManageInternalEndpoint` 以 `Operation: "Delete"` 调用会解除 VPC 与实例的内网关联,依赖该链路的业务集群将无法通过内网拉取镜像。清理前请确认无生产业务依赖该链路。 + +### 数据面 + +```bash +# 清理 ccr2tcr 工具镜像(方案 B 使用后) +docker rmi ccr.ccs.tencentyun.com/tcrimages/image-transfer:ccr2tcr +# expected: 成功删除镜像 +``` + +### 控制面(tccli) + +#### 1. 清理前状态检查 + +```bash +tccli tcr DescribeInstances \ + --region \ + --Registryids '["REGISTRY_ID"]' \ + --output json +# expected: exit 0,确认是待操作的目标实例,记录 RegistryId +``` + +#### 2. 删除迁移专用访问凭证 + +```bash +tccli tcr DeleteInstanceToken \ + --region \ + --RegistryId REGISTRY_ID \ + --TokenId TOKEN_ID \ + --output json +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +#### 3. 删除内网 DNS 解析 + +```bash +tccli tcr DeleteInternalEndpointDns \ + --region \ + --InstanceId REGISTRY_ID \ + --VpcId VPC_ID \ + --EniLBIp ACCESS_IP \ + --output json +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +#### 4. 删除 VPC 内网访问链路 + +```bash +tccli tcr ManageInternalEndpoint \ + --region \ + --RegistryId REGISTRY_ID \ + --Operation Delete \ + --VpcId VPC_ID \ + --SubnetId SUBNET_ID \ + --output json +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +#### 5. 验证已清理 + +```bash +tccli tcr DescribeInternalEndpoints \ + --region \ + --RegistryId REGISTRY_ID \ + --output json +# expected: exit 0, AccessVpcSet 为空,TotalCount: 0 +``` + +**预期输出**: + +```json +{ + "AccessVpcSet": [], + "TotalCount": 0, + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `DuplicateImage` 返回 `ResourceNotFound` | `tccli tcr DescribeImagePersonal --region --RepoName "SRC_NS/SRC_REPO" --output json` 检查源仓库和 Tag 是否存在 | 源命名空间/仓库名拼写错误或 Tag 不存在 | 核对 `SourceNamespaceName`、`SourceRepositoryName`、`ImageVersion` 拼写;用步骤 A1 的输出确认准确的命名空间名、仓库名和 Tag 名 | +| `DuplicateImage` 返回 `FailedOperation` | `tccli tcr DescribeRepositories --region --RegistryId REGISTRY_ID --NamespaceName NAMESPACE_NAME --output json` 确认目标仓库存在 | 目标企业版仓库未创建 | 先执行步骤 A3 `CreateRepository`,再重新执行 `DuplicateImage` | +| `ManageInternalEndpoint` 返回 `LimitExceeded` | `tccli vpc DescribeVpcs --region --output json` 查看 VPC 数量和配额 | 账号 VPC 配额已满(此为环境限制,非命令错误) | 清理不再使用的 VPC 后重试,或使用已有 VPC | +| `CreateInstanceToken` 返回 `UnauthorizedOperation` | `tccli tcr DescribeInstances --region --output json` 确认子账号有读取权限 | 子账号缺少 `tcr:CreateInstanceToken` 权限 | 授予 `QcloudTCRFullAccess` 预设策略,或添加自定义策略含 `tcr:CreateInstanceToken` | +| `CreateInternalEndpointDns` 返回 `FailedOperation` | `tccli tcr DescribeInternalEndpoints --region --RegistryId REGISTRY_ID --output json` 确认内网链路已 Running | VPC 内网链路尚未就绪,或 `EniLBIp` 填写了错误的 AccessIp | 等待内网链路 Status 变为 Running 后重试;确认 `EniLBIp` 与 `DescribeInternalEndpoints` 返回的 `AccessIp` 一致 | +| ccr2tcr 工具报失败数非零 | 查看工具输出的具体错误行(如 `transfer jobs failed: N`) | 网络波动导致部分镜像传输中断或 API 调用超时 | 重新运行 ccr2tcr 工具(幂等,已迁移的会自动跳过)。持续失败可通过 [在线咨询](https://cloud.tencent.com/online-service?from=doc_1141) 申请协助 | +| ccr2tcr 报命名空间命名冲突 | 查看工具输出中的冲突命名空间名 | 使用了 `library`、`tke`、`public` 等企业版保留命名空间,或与实例已有命名空间同名 | 在个人版中重命名冲突的命名空间后再运行,或手动创建非冲突的企业版命名空间并用方案 A 逐 Tag 迁移该部分 | + +### 迁移结果异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 迁移后镜像 Tag 数量少于预期 | 对比个人版与企业版 Tag 列表:`tccli tcr DescribeImagePersonal --region --RepoName "SRC_NS/SRC_REPO" --output json | jq '.Data.TotalCount'` vs `tccli tcr DescribeImages --region --RegistryId REGISTRY_ID --NamespaceName NAMESPACE_NAME --RepositoryName REPO_NAME --output json | jq '.TotalCount'` | 部分 Tag 迁移失败(脚本中途退出、API 限频、网络超时) | 对缺失 Tag 重新执行 `DuplicateImage`(幂等,不会重复创建) | +| 迁移后 `docker pull` 返回 `unauthorized` | `docker login REGISTRY_NAME.tencentcloudcr.com --username=UIN --password=TCR_TOKEN` 确认登录状态 | 凭证错误或已过期(`temp` 类型),或未登录 | 使用 `longterm` 类型 Token 重新登录。确认用户名是 `CreateInstanceToken` 返回的 `Username`(UIN),密码是 `Token`(不是 SecretId/SecretKey) | +| 迁移后 `docker pull` 返回 `not found` | `tccli tcr DescribeImages --region --RegistryId REGISTRY_ID --NamespaceName NAMESPACE_NAME --RepositoryName REPO_NAME --output json` 确认 Tag 存在 | 命名空间/仓库名拼写错误或 Tag 不存在 | 用 `DescribeImages` 获取准确的命名空间名和仓库名,核对 `docker pull` 地址中的路径 | +| Pod 报 `ImagePullBackOff`(方式二) | `kubectl describe pod POD_NAME -n NAMESPACE_NAME` 查看 Events 详情 | 镜像地址或 `imagePullSecret` 配置错误 | 核对 `image` 字段格式为 `REGISTRY_NAME.tencentcloudcr.com/NAMESPACE_NAME/REPO_NAME:TAG`;确认 Secret 名称在 `imagePullSecrets` 中拼写正确;确认 `kubectl create secret docker-registry` 时 `--docker-server` 不含 `https://` 前缀 | + +## 下一步 + +- [使用个人版域名访问企业版实例](https://cloud.tencent.com/document/product/1141/82855) — 零配置切换业务访问 +- [TKE 集群使用 TCR 插件内网免密拉取容器镜像](https://cloud.tencent.com/document/product/1141/48184) — 免密拉取配置 +- [配置内网访问控制](https://cloud.tencent.com/document/product/1141/61445) — VPC 内网链路管理 +- [配置自定义域名](https://cloud.tencent.com/document/product/1141/61444) — 统一域名管理 +- [销毁退还实例](https://cloud.tencent.com/document/product/1141/61442) — 实例生命周期管理 + +## 控制台替代 + +[容器镜像服务控制台](https://console.cloud.tencent.com/tcr) -> 实例列表 -> 目标企业版实例 -> 镜像仓库 -> **自动迁移**功能,或配合 ccr2tcr 工具在 CVM 上执行全量迁移。 diff --git a/src/content/docs/cli/tcr/practices/storage-switch-tcrctl.md b/src/content/docs/cli/tcr/practices/storage-switch-tcrctl.md new file mode 100644 index 0000000..7583f3e --- /dev/null +++ b/src/content/docs/cli/tcr/practices/storage-switch-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "" +description: "# 产品 CLI 操作" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +# 产品 CLI 操作 + +> 页型 P1 · 使用 crctl 完成本页任务 · P0 仅提供 stub 占位,P1 阶段完成正文。 + diff --git a/src/content/docs/cli/tcr/practices/storage-switch.md b/src/content/docs/cli/tcr/practices/storage-switch.md new file mode 100644 index 0000000..3535e82 --- /dev/null +++ b/src/content/docs/cli/tcr/practices/storage-switch.md @@ -0,0 +1,718 @@ +--- +title: "TCR 实例后端存储切换" +description: "· page_id `128966`" +--- + +> 对照官方:[TCR 实例后端存储切换](https://cloud.tencent.com/document/product/1141/128966) · page_id `128966` + +## 概述 + +TCR 企业版实例以 COS(对象存储)桶作为容器镜像的后端存储。COS 桶分为单 AZ 存储和多 AZ(Multi-AZ)存储。单 AZ 架构下,单个可用区遭遇自然灾害、断电等极端情况会导致 COS 桶不可访问,影响镜像拉取与推送。 + +TCR 提供后端存储切换能力:将主实例的后端存储从原 COS 桶切换至位于其他地域的复制实例关联的 COS 桶。切换后,实例的读请求(拉取镜像)被路由到目标地域的 COS 桶,保障业务可持续拉取镜像。 + +> **计费提示:** 此操作可能产生额外费用。切换至异地复制桶后,跨地域复制流量和备份桶存储将持续产生 COS 费用。回切至原桶后可停止备份桶的额外费用。详见 [COS 计费说明](https://cloud.tencent.com/document/product/436/16871)。 + +**方案对比:** + +| 方案 | 适用场景 | 可用性 | 成本 | 切换后限制 | +|------|---------|--------|------|-----------| +| COS 桶原地升级多 AZ | COS 单 AZ 地域支持多 AZ(北京、广州、上海、中国香港、新加坡、上海金融) | 同地域跨 AZ 容灾 | 多 AZ 存储费用略高 | 无,实例保持完整读/写能力 | +| 后端存储切换至异地复制桶 | 单 AZ 地域故障、地域级 COS 中断、网络不可达 | 跨地域容灾 | 跨地域复制流量 + 目标桶存储 | **只读模式**:仅 `docker pull`,不可 `docker push` | + +**建议:** 在支持多 AZ 的地域,优先联系 COS 团队将桶原地升级为多 AZ;后端存储切换为临时应急手段,切换后需尽快回切以恢复推送能力。 + +## 前置条件 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置 + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tcr:DescribeInstances, tcr:DescribeReplicationInstances, tcr:ModifyInstanceStorage, tcr:CreateInstanceToken +# cos:GetBucketAccelerate, cos:PutBucketAccelerate +# 验证:执行 DescribeInstances 确认 TCR 权限 +tccli tcr DescribeInstances --region +# expected: exit 0,返回实例列表(可为空) +``` + +**预期输出:** + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +```bash +# 4. 验证 DescribeReplicationInstances 权限 +tccli tcr DescribeReplicationInstances --region --RegistryId '' +# expected: exit 0 +``` + +**预期输出:** + +```json +{ + "TotalCount": 1, + "ReplicationRegistries": [ + { + "RegistryId": "tcr-example", + "ReplicationRegistryId": "tcr-example-4-ghbzyc", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai", + "Status": "Running" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +> **注意:** `tccli cos` 模块不存在。COS 桶相关操作(查询桶列表、开启/查看全球加速)需通过 [COS 控制台](https://console.cloud.tencent.com/cos/bucket) 执行。CAM Action: `cos:GetBucketAccelerate`, `cos:PutBucketAccelerate`。 + +### 资源检查 + +```bash +# 5. 确认主实例存在且状态为 Running +tccli tcr DescribeInstances --region --Registryids '[""]' +# expected: exit 0, TotalCount >= 1, Status: "Running" +``` + +**预期输出:** + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "tcr-example.tencentcloudcr.com", + "RegionName": "ap-guangzhou", + "InternalEndpoint": "10.1.67.137", + "EnableCosMAZ": false + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +```bash +# 6. 确认复制实例存在且状态为 Running +tccli tcr DescribeReplicationInstances --region --RegistryId '' +# expected: exit 0; premium 实例至少一个 Status: "Running";basic 实例 TotalCount: 0 +``` + +**预期输出:** + +```json +{ + "TotalCount": 1, + "ReplicationRegistries": [ + { + "RegistryId": "tcr-example", + "ReplicationRegistryId": "tcr-example-4-ghbzyc", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai", + "Status": "Running" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +```bash +# 7. 确认目标 COS 桶存在且已开启全球加速 +# 打开 COS 控制台 -> 选择目标桶 -> 域名与传输管理 -> 全球加速 +# 无 tccli 对应命令,通过控制台验证 +# expected: 全球加速状态为"已开启" +``` + +### 使用限制 + +- 仅 TCR 企业版支持(含标准版和高级版),个人版不支持 +- 切换后实例仅支持镜像拉取(`docker pull`),不支持镜像推送(`docker push`) +- 需确认目标地域与复制实例所在地域一致 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 查看实例详情 | `DescribeInstances` | 是 | +| 查看复制实例列表 | `DescribeReplicationInstances` | 是 | +| 执行后端存储切换 | `ModifyInstanceStorage` | 否(覆盖性操作) | +| 开启 COS 桶全球加速 | COS 控制台(无 tccli 模块) | 是 | +| 查看 COS 桶加速状态 | COS 控制台(无 tccli 模块) | 是 | + +### 关键字段说明 + +以下说明 `ModifyInstanceStorage` 的主要参数。完整参数定义见 `tccli tcr ModifyInstanceStorage --generate-cli-skeleton`。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | TCR 主实例 ID,格式 `tcr-` 开头。由 `DescribeInstances` 获取 | `ResourceNotFound`:实例不存在 | +| `TargetRegion` | String | 是 | 复制实例 COS 桶所在地域,须与 `DescribeReplicationInstances` 返回的 `ReplicationRegionName` 一致 | `InvalidParameter`:地域不匹配 | +| `TargetStorageName` | String | 是 | COS 桶名称(格式 `---`),非加速域名 | `InvalidParameter`:填入加速域名而非桶名 | + +### COS 桶操作说明(控制台) + +COS 桶相关操作无法通过 tccli 执行(`tccli cos` 模块不存在)。以下操作需在 [COS 控制台](https://console.cloud.tencent.com/cos/bucket) 完成: + +| 操作 | 控制台路径 | 说明 | +|------|-----------|------| +| 查看桶列表 | COS 控制台 -> 存储桶列表 | 搜索目标桶名称 | +| 查看全球加速状态 | COS 控制台 -> 选择目标桶 -> 域名与传输管理 -> 全球加速 | 状态字段显示"已开启"或"已关闭" | +| 开启全球加速 | COS 控制台 -> 选择目标桶 -> 域名与传输管理 -> 全球加速 -> 编辑 -> 开启 | 立即生效,无需等待 | + +**备选:** 可安装 `coscmd` 命令行工具(`pip install coscmd`)执行 COS 操作。配置和命令语法见 [COSCMD 工具文档](https://cloud.tencent.com/document/product/436/10976)。 + +## 操作步骤 + +### 步骤 1:确认主实例状态 + +查看主实例状态,确认处于 `Running`。 + +```bash +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region +# expected: exit 0, Status: "Running" +``` + +**预期输出:** + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "tcr-example.tencentcloudcr.com", + "RegionName": "ap-guangzhou", + "InternalEndpoint": "10.1.67.137", + "EnableCosMAZ": false + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +| 字段 | 说明 | 期望 | +|------|------|------| +| `Status` | 实例状态 | `Running` | +| `EnableCosMAZ` | `false` 表示当前为单 AZ 存储,确认需要切换 | -- | +| `RegionName` | 主实例所在地域,用于后续回切 | -- | + +### 步骤 2:确认复制实例状态 + +查看复制实例列表,确认目标复制实例处于 `Running`。 + +```bash +tccli tcr DescribeReplicationInstances \ + --RegistryId '' \ + --region +# expected: exit 0, at least one ReplicationRegistry with Status: "Running" +``` + +**预期输出:** + +```json +{ + "TotalCount": 1, + "ReplicationRegistries": [ + { + "RegistryId": "tcr-example", + "ReplicationRegistryId": "tcr-example-4-ghbzyc", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai", + "Status": "Running" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +| 字段 | 说明 | +|------|------| +| `Status` | 须为 `Running` | +| `ReplicationRegionName` | 目标地域,即后续 `--TargetRegion` 参数值 | +| `ReplicationRegionId` | 目标地域的数字编码,用于推导 COS 桶命名 | +| `ReplicationRegistryId` | 复制实例 ID,用于识别目标 COS 桶 | + +### 步骤 3:获取 COS 存储桶信息 + +TCR 实例创建时自动关联 COS 桶,桶名遵循固定格式。需获取复制实例关联的 COS 桶名称,作为 `--TargetStorageName` 参数。 + +**COS 桶命名规则:** + +复制实例关联的桶名格式为: + +``` +--- +``` + +| 组成部分 | 说明 | 示例值 | +|---------|------|--------| +| `RegistryId` | 主实例 ID | `tcr-example` | +| `RegionCode` | 目标地域数字编码(即 `ReplicationRegionId`) | `4`(ap-shanghai) | +| `RandomString` | 系统随机生成标识(6 位字母) | `ghbzyc` | +| `AppId` | 主账号 AppId(在 [账号信息](https://console.cloud.tencent.com/developer) 查看) | `1250000000` | + +**完整桶名示例:** `tcr-example-4-ghbzyc-1250000000` + +**常见 RegionCode 对照:** + +| 地域 | RegionCode | +|------|----------| +| 广州 `ap-guangzhou` | `1` | +| 上海 `ap-shanghai` | `4` | +| 北京 `ap-beijing` | `8` | +| 成都 `ap-chengdu` | `16` | +| 香港 `ap-hongkong` | `5` | +| 新加坡 `ap-singapore` | `9` | +| 硅谷 `na-siliconvalley` | `15` | + +**通过 CLI 定位目标桶:** + +```bash +# 从 DescribeReplicationInstances 获取 ReplicationRegistryId 和 ReplicationRegionId +tccli tcr DescribeReplicationInstances \ + --RegistryId '' \ + --region +# expected: ReplicationRegistryId 含 RegionCode 和 RandomString(如 "tcr-example-4-ghbzyc") +``` + +**预期输出:** + +```json +{ + "TotalCount": 1, + "ReplicationRegistries": [ + { + "RegistryId": "tcr-example", + "ReplicationRegistryId": "tcr-example-4-ghbzyc", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai", + "Status": "Running" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +然后打开 [COS 控制台 - 存储桶列表](https://console.cloud.tencent.com/cos/bucket),在搜索框中输入主实例 ID(如 `tcr-example`),找到名称匹配 `---` 格式的桶即为目标桶。 + +> **注意:** `--TargetStorageName` 须填 COS 桶**名称**(如 `tcr-example-4-ghbzyc-1250000000`),不可填加速域名(如 `tcr-example-4-ghbzyc-1250000000.cos.accelerate.myqcloud.com`)。填入域名将导致 `InvalidParameter` 错误。 + +### 步骤 4:启用 COS 桶全球加速 + +复制实例的 COS 桶在创建时默认**未开启**全球加速。`ModifyInstanceStorage` 要求目标桶已启用全球加速,否则接口返回 `FailedOperation`。 + +#### 选择依据 + +- 全球加速使 COS 桶可被跨地域客户端通过内网高速访问。在存储切换场景中,TCR 后端从主实例地域访问异地复制桶,需要全球加速以保证读取性能。 + +#### 4.1 检查当前加速状态 + +打开 [COS 控制台](https://console.cloud.tencent.com/cos/bucket),选择目标桶,进入 **域名与传输管理** -> **全球加速**,查看状态。 + +- 状态为"已关闭"或显示为空 -> 未开启,需执行下一步 +- 状态为"已开启" -> 已生效,可直接跳到步骤 5 + +#### 4.2 开启全球加速 + +在 COS 控制台 **域名与传输管理** -> **全球加速** 页面,点击 **编辑**,开启全球加速开关,保存。开启后立即生效。 + +#### 4.3 验证加速已开启 + +刷新 **全球加速** 页面,确认状态变为"已开启"。 + +**备选(若已安装 coscmd):** + +```bash +# 查看状态 +coscmd getbucketaccelerate --bucket '-' --region '' +# expected: enabled + +# 开启 +coscmd putbucketaccelerate --bucket '-' --region '' --enable +``` + +### 步骤 5:执行存储切换 + +#### 选择依据 + +- **目标 COS 桶**:选择与主实例已建立复制关系的复制实例关联的桶。此桶内的镜像数据与主实例同步,切换后数据一致性有保障。不支持任意指定 COS 桶。 +- **目标地域**:必须与复制实例所在地域(`ReplicationRegionName`)一致,否则 `--TargetRegion` 与桶所在地域不匹配导致 `InvalidParameter`。 +- **桶名而非加速域名**:`--TargetStorageName` 必须填 COS 桶名称,不可填加速域名。 +- **只读模式代价**:切换后实例进入只读模式,仅支持 `pull`。此为临时应急手段,故障恢复后应尽快回切。 + +```bash +tccli tcr ModifyInstanceStorage \ + --RegistryId '' \ + --TargetRegion '' \ + --TargetStorageName '' \ + --region +# expected: exit 0, 返回 RegistryId +``` + +**预期输出:** + +```json +{ + "RegistryId": "tcr-example", + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | TCR 主实例 ID | 格式 `tcr-` 开头 | 步骤 1 `DescribeInstances` 输出的 `RegistryId` | +| `` | 目标 COS 桶所在地域 | 须与 `ReplicationRegionName` 一致 | 步骤 2 `DescribeReplicationInstances` 输出的 `ReplicationRegionName` | +| `` | 复制实例关联的 COS 桶名称 | 格式 `---` | 步骤 3 确认的目标桶名 | +| `` | 主实例所在地域 | -- | `tccli configure list` 或步骤 1 `RegionName` | + +> **警告:此操作是破坏性的,不可逆。** 切换后实例进入**只读模式** -- 仅支持镜像拉取(`docker pull`),不支持镜像推送(`docker push`)。如需恢复推送能力,必须再次调用 `ModifyInstanceStorage` 将存储指向原 COS 桶(见[清理](#清理))。切换至异地桶后将产生跨地域复制流量和备份桶存储费用。 + +### 步骤 6:等待切换生效 + +`ModifyInstanceStorage` 返回成功后,后端服务需要约 1-2 分钟滚动更新。轮询确认实例恢复 `Running` 状态: + +```bash +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region +# expected: exit 0, Status: "Running" +``` + +**预期输出:** + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "tcr-example.tencentcloudcr.com", + "InternalEndpoint": "10.1.67.137" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +多维度验证切换是否真正生效: + +| 维度 | 检查内容 | 命令 | 预期 | +|------|---------|------|------| +| 状态 | 实例是否恢复 Running | `DescribeInstances --Registryids '[""]'` | `Status: "Running"` | +| 控制面 | `ModifyInstanceStorage` 返回 `RegistryId` | 步骤 5 输出 | `RegistryId` 非空,与主实例 ID 一致 | +| 复制实例 | 目标复制实例状态正常 | `DescribeReplicationInstances --RegistryId ''` | 目标复制实例 `Status: "Running"` | +| 数据面 | 镜像拉取可达 | `docker pull .tencentcloudcr.com//:` | 拉取成功(见[验证](#验证)) | +| 只读模式 | 推送被拒绝 | `docker push .tencentcloudcr.com//:` | 推送失败(预期行为) | + +> 直到所有维度确认无误后继续。最长等待约 3 分钟。超时参见 [排障](#排障)。 + +## 验证 + +### 控制面(tccli) + +```bash +# 确认主实例状态 +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region +# expected: exit 0, Status: "Running" +``` + +**预期输出:** + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "tcr-example.tencentcloudcr.com" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +```bash +# 确认复制实例状态 +tccli tcr DescribeReplicationInstances \ + --RegistryId '' \ + --region +# expected: exit 0, 目标复制实例 Status: "Running" +``` + +**预期输出:** + +```json +{ + "TotalCount": 1, + "ReplicationRegistries": [ + { + "RegistryId": "tcr-example", + "ReplicationRegistryId": "tcr-example-4-ghbzyc", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai", + "Status": "Running" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +### COS 全球加速验证(控制台) + +确认目标桶全球加速仍为"已开启"状态: +打开 [COS 控制台](https://console.cloud.tencent.com/cos/bucket) -> 选择目标桶 -> **域名与传输管理** -> **全球加速**。 + +### 数据面(docker) + +切换完成后,用 Docker 客户端拉取实例内已有镜像,验证存储切换生效。 + +首先获取临时登录凭证: + +```bash +tccli tcr CreateInstanceToken \ + --RegistryId '' \ + --TokenType temp \ + --Desc "存储切换验证用临时凭证" \ + --region +# expected: exit 0, 返回 Username 和 Token +``` + +**预期输出:** + +```json +{ + "Username": "100012345678", + "Token": "eyJhbGciOi...", + "ExpTime": 1718552800, + "TokenId": "tcr-example-token-abc123", + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +```bash +docker login .tencentcloudcr.com \ + --username \ + --password +# expected: Login Succeeded +``` + +然后拉取镜像: + +```bash +docker pull .tencentcloudcr.com//: +# expected: 拉取成功 +``` + +```text +TAG: Pulling from / +Digest: sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +Status: Downloaded newer image for .tencentcloudcr.com//: +``` + +**预期结果:** + +- 拉取成功 -- 存储切换已生效,镜像数据从目标 COS 桶被正常读取 +- 推送失败 -- 预期行为,实例处于只读模式(仅 `pull`,不可 `push`) + +## 清理 + +后端存储切换操作本身不新建资源,但切换后实例处于只读模式且持续产生跨地域 COS 费用。如需**回切到原 COS 桶**并恢复推送能力,按以下步骤操作。 + +> **计费警告:** 切换至备份桶后,跨地域复制流量和备份桶存储将持续产生 COS 费用。回切至原桶后可停止备份桶的额外费用。详见 [COS 计费说明](https://cloud.tencent.com/document/product/436/16871)。 + +> **副作用警告:** 再次调用 `ModifyInstanceStorage` 回切时,实例的后端存储指向将变更。回切期间(约 1-2 分钟)实例不可用。回切后恢复完整读/写能力。此操作不删除任何资源(原桶和备份桶均保留),仅变更实例的路由指向。 + +### 1. 清理前状态检查 + +```bash +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region +# expected: exit 0, Status: "Running",确认是待回切的实例 +``` + +**预期输出:** + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +```bash +tccli tcr DescribeReplicationInstances \ + --RegistryId '' \ + --region +# expected: exit 0, 确认复制实例 ID,记录当前后端存储指向的地域 +``` + +**预期输出:** + +```json +{ + "TotalCount": 1, + "ReplicationRegistries": [ + { + "RegistryId": "tcr-example", + "ReplicationRegistryId": "tcr-example-4-ghbzyc", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai", + "Status": "Running" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +### 2. 回切至原 COS 桶 + +```bash +tccli tcr ModifyInstanceStorage \ + --RegistryId '' \ + --TargetRegion '' \ + --TargetStorageName '' \ + --region +# expected: exit 0, 返回 RegistryId +``` + +**预期输出:** + +```json +{ + "RegistryId": "tcr-example", + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +| 参数 | 回切时的值 | 获取方式 | +|------|----------|---------| +| `--TargetRegion` | 主实例所在地域 | 步骤 1 `DescribeInstances` 输出中的 `RegionName`(如 `ap-guangzhou`) | +| `--TargetStorageName` | 原主实例的 COS 桶名称 | 打开 [COS 控制台](https://console.cloud.tencent.com/cos/bucket),搜索主实例 ID(如 `tcr-example`),**不含**异地 RegionCode 和复制实例 ID 的桶即为原桶 | + +**原 COS 桶命名格式:** `---`,其中 `LocalRegionCode` 为主实例所在地域的数字编码(广州为 `1`)。 + +### 3. 等待回切生效并验证 + +回切后等待约 1-2 分钟: + +```bash +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region +# expected: exit 0, Status: "Running" +``` + +**预期输出:** + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "tcr-example.tencentcloudcr.com" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +### 4. 验证推送能力已恢复 + +```bash +docker login .tencentcloudcr.com \ + --username \ + --password +# expected: Login Succeeded + +docker push .tencentcloudcr.com//: +# expected: 推送成功 +``` + +推送成功即表明回切完成,实例恢复完整读/写(pull/push)能力。 + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ModifyInstanceStorage` 返回 `InvalidParameter` | 检查 `--TargetStorageName` 是否包含 `.cos.accelerate.myqcloud.com` 后缀 | 填入了 COS 加速域名而非桶名称 | 确认参数值为 COS 桶名称(格式 `---`),不含域名后缀 | +| `ModifyInstanceStorage` 返回 `InvalidParameter` 且桶名正确 | `tccli tcr DescribeReplicationInstances --RegistryId '' --region ` 查看 `ReplicationRegionName` | `--TargetRegion` 与复制实例所在地域不匹配 | 将 `--TargetRegion` 改为 `ReplicationRegionName` 的值 | +| `ModifyInstanceStorage` 返回 `FailedOperation` | 打开 [COS 控制台](https://console.cloud.tencent.com/cos/bucket) -> 选择目标桶 -> 域名与传输管理 -> 全球加速,检查加速状态 | COS 桶未开启全球加速 | 在 COS 控制台开启全球加速后重试 | +| `ModifyInstanceStorage` 返回 `FailedOperation` 且加速已开启 | `tccli tcr DescribeInstances --Registryids '[""]' --region ` 检查实例状态 | 实例 `Status` 非 `Running` | 等待实例恢复 `Running` 后重试 | +| `ModifyInstanceStorage` 返回 `InternalError` | 记录返回的 `RequestId`,等待 1 分钟后重试 | TCR 后端服务临时异常 | 稍后重试;若持续失败则保留 `RegistryId`、`RequestId`、调用时间 -> [提交工单](https://console.cloud.tencent.com/workorder) | +| `ModifyInstanceStorage` 返回 `UnauthorizedOperation` | `tccli tcr DescribeInstances --region ` 确认权限 | 缺少 `tcr:ModifyInstanceStorage` 权限(环境限制,非命令错误) | 联系主账号授予 `tcr:ModifyInstanceStorage` 权限 | +| `PutBucketAccelerate` 返回 `UnauthorizedOperation` | 检查 COS 控制台是否可访问目标桶设置 | 缺少 COS 桶操作权限(环境限制,非命令错误) | 联系主账号授予 `cos:PutBucketAccelerate` 权限,或前往 COS 控制台手动开启 | +| 无法查询 COS 桶加速状态 | 打开 [COS 控制台](https://console.cloud.tencent.com/cos/bucket) 确认桶名和地域 | 桶名或地域错误,或桶不存在 | 在 COS 控制台存储桶列表中搜索主实例 ID 确认桶名正确,并在正确的地域下查看 | + +### 切换成功但状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ModifyInstanceStorage` 返回成功但实例长期非 `Running` | `tccli tcr DescribeInstances --Registryids '[""]' --region ` 轮询 `Status` | 后端存储切换滚动更新缓慢 | 继续轮询,每次间隔 15 秒;超过 5 分钟则保留 `RegistryId`、`RequestId` -> [提交工单](https://console.cloud.tencent.com/workorder) | +| 实例 `Running` 但 Docker 拉取失败 | `docker pull .tencentcloudcr.com//:` 查看具体错误 | 镜像未同步至复制桶、VPC 网络不可达、或后端路由未完全生效 | 1) 确认镜像已同步至复制桶(检查复制实例同步日志);2) `ping .tencentcloudcr.com` 检查网络可达性;3) 等待 2-3 分钟后重试 | +| 实例 `Running` 但无法推送镜像 | `docker push .tencentcloudcr.com//:` | 此为预期行为:切换后实例进入只读模式 | 正常现象,非故障。如需恢复推送能力,执行[清理](#清理)中的回切操作 | +| COS 桶名称无法确认 | 打开 [COS 控制台](https://console.cloud.tencent.com/cos/bucket) 搜索桶列表 | 桶格式不明确或存在多个桶 | 以主实例 ID 搜索;名称含目标 `RegionCode` 且含复制实例 ID 的桶即为目标桶;如仍无法确认,[提交工单](https://console.cloud.tencent.com/workorder) | +| `DescribeReplicationInstances` 返回空列表 | `tccli tcr DescribeReplicationInstances --RegistryId '' --region ` | 未创建复制实例或复制实例已删除 | 参见 [同实例多地域复制镜像](../../ops/image-distribution/cross-region-replication) 创建复制实例 | + +## 下一步 + +- [同实例多地域复制镜像](../../ops/image-distribution/cross-region-replication) -- 创建复制实例详细指南 +- [创建企业版实例](../../ops/instances/create) -- 实例创建与升级 +- [使用自定义域名及云联网实现跨地域内网访问](../custom-domain-ccn) -- 跨地域内网访问 +- [混合云下的多平台镜像数据同步复制](../hybrid-cloud-sync) -- 跨平台同步场景 + +## 控制台替代 + +- [容器镜像服务控制台](https://console.cloud.tencent.com/tcr/instance) -- 实例管理、查看后端存储桶 +- [COS 控制台](https://console.cloud.tencent.com/cos/bucket) -- COS 桶全球加速管理 +- [API Explorer: ModifyInstanceStorage](https://console.cloud.tencent.com/api/explorer?Product=tcr&Version=2019-09-24&Action=ModifyInstanceStorage) diff --git a/src/content/docs/cli/tcr/practices/storage-switch/storage-switch-tcrctl.md b/src/content/docs/cli/tcr/practices/storage-switch/storage-switch-tcrctl.md new file mode 100644 index 0000000..7583f3e --- /dev/null +++ b/src/content/docs/cli/tcr/practices/storage-switch/storage-switch-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "" +description: "# 产品 CLI 操作" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +# 产品 CLI 操作 + +> 页型 P1 · 使用 crctl 完成本页任务 · P0 仅提供 stub 占位,P1 阶段完成正文。 + diff --git a/src/content/docs/cli/tcr/practices/storage-switch/storage-switch.md b/src/content/docs/cli/tcr/practices/storage-switch/storage-switch.md new file mode 100644 index 0000000..3535e82 --- /dev/null +++ b/src/content/docs/cli/tcr/practices/storage-switch/storage-switch.md @@ -0,0 +1,718 @@ +--- +title: "TCR 实例后端存储切换" +description: "· page_id `128966`" +--- + +> 对照官方:[TCR 实例后端存储切换](https://cloud.tencent.com/document/product/1141/128966) · page_id `128966` + +## 概述 + +TCR 企业版实例以 COS(对象存储)桶作为容器镜像的后端存储。COS 桶分为单 AZ 存储和多 AZ(Multi-AZ)存储。单 AZ 架构下,单个可用区遭遇自然灾害、断电等极端情况会导致 COS 桶不可访问,影响镜像拉取与推送。 + +TCR 提供后端存储切换能力:将主实例的后端存储从原 COS 桶切换至位于其他地域的复制实例关联的 COS 桶。切换后,实例的读请求(拉取镜像)被路由到目标地域的 COS 桶,保障业务可持续拉取镜像。 + +> **计费提示:** 此操作可能产生额外费用。切换至异地复制桶后,跨地域复制流量和备份桶存储将持续产生 COS 费用。回切至原桶后可停止备份桶的额外费用。详见 [COS 计费说明](https://cloud.tencent.com/document/product/436/16871)。 + +**方案对比:** + +| 方案 | 适用场景 | 可用性 | 成本 | 切换后限制 | +|------|---------|--------|------|-----------| +| COS 桶原地升级多 AZ | COS 单 AZ 地域支持多 AZ(北京、广州、上海、中国香港、新加坡、上海金融) | 同地域跨 AZ 容灾 | 多 AZ 存储费用略高 | 无,实例保持完整读/写能力 | +| 后端存储切换至异地复制桶 | 单 AZ 地域故障、地域级 COS 中断、网络不可达 | 跨地域容灾 | 跨地域复制流量 + 目标桶存储 | **只读模式**:仅 `docker pull`,不可 `docker push` | + +**建议:** 在支持多 AZ 的地域,优先联系 COS 团队将桶原地升级为多 AZ;后端存储切换为临时应急手段,切换后需尽快回切以恢复推送能力。 + +## 前置条件 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置 + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tcr:DescribeInstances, tcr:DescribeReplicationInstances, tcr:ModifyInstanceStorage, tcr:CreateInstanceToken +# cos:GetBucketAccelerate, cos:PutBucketAccelerate +# 验证:执行 DescribeInstances 确认 TCR 权限 +tccli tcr DescribeInstances --region +# expected: exit 0,返回实例列表(可为空) +``` + +**预期输出:** + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +```bash +# 4. 验证 DescribeReplicationInstances 权限 +tccli tcr DescribeReplicationInstances --region --RegistryId '' +# expected: exit 0 +``` + +**预期输出:** + +```json +{ + "TotalCount": 1, + "ReplicationRegistries": [ + { + "RegistryId": "tcr-example", + "ReplicationRegistryId": "tcr-example-4-ghbzyc", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai", + "Status": "Running" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +> **注意:** `tccli cos` 模块不存在。COS 桶相关操作(查询桶列表、开启/查看全球加速)需通过 [COS 控制台](https://console.cloud.tencent.com/cos/bucket) 执行。CAM Action: `cos:GetBucketAccelerate`, `cos:PutBucketAccelerate`。 + +### 资源检查 + +```bash +# 5. 确认主实例存在且状态为 Running +tccli tcr DescribeInstances --region --Registryids '[""]' +# expected: exit 0, TotalCount >= 1, Status: "Running" +``` + +**预期输出:** + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "tcr-example.tencentcloudcr.com", + "RegionName": "ap-guangzhou", + "InternalEndpoint": "10.1.67.137", + "EnableCosMAZ": false + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +```bash +# 6. 确认复制实例存在且状态为 Running +tccli tcr DescribeReplicationInstances --region --RegistryId '' +# expected: exit 0; premium 实例至少一个 Status: "Running";basic 实例 TotalCount: 0 +``` + +**预期输出:** + +```json +{ + "TotalCount": 1, + "ReplicationRegistries": [ + { + "RegistryId": "tcr-example", + "ReplicationRegistryId": "tcr-example-4-ghbzyc", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai", + "Status": "Running" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +```bash +# 7. 确认目标 COS 桶存在且已开启全球加速 +# 打开 COS 控制台 -> 选择目标桶 -> 域名与传输管理 -> 全球加速 +# 无 tccli 对应命令,通过控制台验证 +# expected: 全球加速状态为"已开启" +``` + +### 使用限制 + +- 仅 TCR 企业版支持(含标准版和高级版),个人版不支持 +- 切换后实例仅支持镜像拉取(`docker pull`),不支持镜像推送(`docker push`) +- 需确认目标地域与复制实例所在地域一致 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 查看实例详情 | `DescribeInstances` | 是 | +| 查看复制实例列表 | `DescribeReplicationInstances` | 是 | +| 执行后端存储切换 | `ModifyInstanceStorage` | 否(覆盖性操作) | +| 开启 COS 桶全球加速 | COS 控制台(无 tccli 模块) | 是 | +| 查看 COS 桶加速状态 | COS 控制台(无 tccli 模块) | 是 | + +### 关键字段说明 + +以下说明 `ModifyInstanceStorage` 的主要参数。完整参数定义见 `tccli tcr ModifyInstanceStorage --generate-cli-skeleton`。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | TCR 主实例 ID,格式 `tcr-` 开头。由 `DescribeInstances` 获取 | `ResourceNotFound`:实例不存在 | +| `TargetRegion` | String | 是 | 复制实例 COS 桶所在地域,须与 `DescribeReplicationInstances` 返回的 `ReplicationRegionName` 一致 | `InvalidParameter`:地域不匹配 | +| `TargetStorageName` | String | 是 | COS 桶名称(格式 `---`),非加速域名 | `InvalidParameter`:填入加速域名而非桶名 | + +### COS 桶操作说明(控制台) + +COS 桶相关操作无法通过 tccli 执行(`tccli cos` 模块不存在)。以下操作需在 [COS 控制台](https://console.cloud.tencent.com/cos/bucket) 完成: + +| 操作 | 控制台路径 | 说明 | +|------|-----------|------| +| 查看桶列表 | COS 控制台 -> 存储桶列表 | 搜索目标桶名称 | +| 查看全球加速状态 | COS 控制台 -> 选择目标桶 -> 域名与传输管理 -> 全球加速 | 状态字段显示"已开启"或"已关闭" | +| 开启全球加速 | COS 控制台 -> 选择目标桶 -> 域名与传输管理 -> 全球加速 -> 编辑 -> 开启 | 立即生效,无需等待 | + +**备选:** 可安装 `coscmd` 命令行工具(`pip install coscmd`)执行 COS 操作。配置和命令语法见 [COSCMD 工具文档](https://cloud.tencent.com/document/product/436/10976)。 + +## 操作步骤 + +### 步骤 1:确认主实例状态 + +查看主实例状态,确认处于 `Running`。 + +```bash +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region +# expected: exit 0, Status: "Running" +``` + +**预期输出:** + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "tcr-example.tencentcloudcr.com", + "RegionName": "ap-guangzhou", + "InternalEndpoint": "10.1.67.137", + "EnableCosMAZ": false + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +| 字段 | 说明 | 期望 | +|------|------|------| +| `Status` | 实例状态 | `Running` | +| `EnableCosMAZ` | `false` 表示当前为单 AZ 存储,确认需要切换 | -- | +| `RegionName` | 主实例所在地域,用于后续回切 | -- | + +### 步骤 2:确认复制实例状态 + +查看复制实例列表,确认目标复制实例处于 `Running`。 + +```bash +tccli tcr DescribeReplicationInstances \ + --RegistryId '' \ + --region +# expected: exit 0, at least one ReplicationRegistry with Status: "Running" +``` + +**预期输出:** + +```json +{ + "TotalCount": 1, + "ReplicationRegistries": [ + { + "RegistryId": "tcr-example", + "ReplicationRegistryId": "tcr-example-4-ghbzyc", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai", + "Status": "Running" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +| 字段 | 说明 | +|------|------| +| `Status` | 须为 `Running` | +| `ReplicationRegionName` | 目标地域,即后续 `--TargetRegion` 参数值 | +| `ReplicationRegionId` | 目标地域的数字编码,用于推导 COS 桶命名 | +| `ReplicationRegistryId` | 复制实例 ID,用于识别目标 COS 桶 | + +### 步骤 3:获取 COS 存储桶信息 + +TCR 实例创建时自动关联 COS 桶,桶名遵循固定格式。需获取复制实例关联的 COS 桶名称,作为 `--TargetStorageName` 参数。 + +**COS 桶命名规则:** + +复制实例关联的桶名格式为: + +``` +--- +``` + +| 组成部分 | 说明 | 示例值 | +|---------|------|--------| +| `RegistryId` | 主实例 ID | `tcr-example` | +| `RegionCode` | 目标地域数字编码(即 `ReplicationRegionId`) | `4`(ap-shanghai) | +| `RandomString` | 系统随机生成标识(6 位字母) | `ghbzyc` | +| `AppId` | 主账号 AppId(在 [账号信息](https://console.cloud.tencent.com/developer) 查看) | `1250000000` | + +**完整桶名示例:** `tcr-example-4-ghbzyc-1250000000` + +**常见 RegionCode 对照:** + +| 地域 | RegionCode | +|------|----------| +| 广州 `ap-guangzhou` | `1` | +| 上海 `ap-shanghai` | `4` | +| 北京 `ap-beijing` | `8` | +| 成都 `ap-chengdu` | `16` | +| 香港 `ap-hongkong` | `5` | +| 新加坡 `ap-singapore` | `9` | +| 硅谷 `na-siliconvalley` | `15` | + +**通过 CLI 定位目标桶:** + +```bash +# 从 DescribeReplicationInstances 获取 ReplicationRegistryId 和 ReplicationRegionId +tccli tcr DescribeReplicationInstances \ + --RegistryId '' \ + --region +# expected: ReplicationRegistryId 含 RegionCode 和 RandomString(如 "tcr-example-4-ghbzyc") +``` + +**预期输出:** + +```json +{ + "TotalCount": 1, + "ReplicationRegistries": [ + { + "RegistryId": "tcr-example", + "ReplicationRegistryId": "tcr-example-4-ghbzyc", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai", + "Status": "Running" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +然后打开 [COS 控制台 - 存储桶列表](https://console.cloud.tencent.com/cos/bucket),在搜索框中输入主实例 ID(如 `tcr-example`),找到名称匹配 `---` 格式的桶即为目标桶。 + +> **注意:** `--TargetStorageName` 须填 COS 桶**名称**(如 `tcr-example-4-ghbzyc-1250000000`),不可填加速域名(如 `tcr-example-4-ghbzyc-1250000000.cos.accelerate.myqcloud.com`)。填入域名将导致 `InvalidParameter` 错误。 + +### 步骤 4:启用 COS 桶全球加速 + +复制实例的 COS 桶在创建时默认**未开启**全球加速。`ModifyInstanceStorage` 要求目标桶已启用全球加速,否则接口返回 `FailedOperation`。 + +#### 选择依据 + +- 全球加速使 COS 桶可被跨地域客户端通过内网高速访问。在存储切换场景中,TCR 后端从主实例地域访问异地复制桶,需要全球加速以保证读取性能。 + +#### 4.1 检查当前加速状态 + +打开 [COS 控制台](https://console.cloud.tencent.com/cos/bucket),选择目标桶,进入 **域名与传输管理** -> **全球加速**,查看状态。 + +- 状态为"已关闭"或显示为空 -> 未开启,需执行下一步 +- 状态为"已开启" -> 已生效,可直接跳到步骤 5 + +#### 4.2 开启全球加速 + +在 COS 控制台 **域名与传输管理** -> **全球加速** 页面,点击 **编辑**,开启全球加速开关,保存。开启后立即生效。 + +#### 4.3 验证加速已开启 + +刷新 **全球加速** 页面,确认状态变为"已开启"。 + +**备选(若已安装 coscmd):** + +```bash +# 查看状态 +coscmd getbucketaccelerate --bucket '-' --region '' +# expected: enabled + +# 开启 +coscmd putbucketaccelerate --bucket '-' --region '' --enable +``` + +### 步骤 5:执行存储切换 + +#### 选择依据 + +- **目标 COS 桶**:选择与主实例已建立复制关系的复制实例关联的桶。此桶内的镜像数据与主实例同步,切换后数据一致性有保障。不支持任意指定 COS 桶。 +- **目标地域**:必须与复制实例所在地域(`ReplicationRegionName`)一致,否则 `--TargetRegion` 与桶所在地域不匹配导致 `InvalidParameter`。 +- **桶名而非加速域名**:`--TargetStorageName` 必须填 COS 桶名称,不可填加速域名。 +- **只读模式代价**:切换后实例进入只读模式,仅支持 `pull`。此为临时应急手段,故障恢复后应尽快回切。 + +```bash +tccli tcr ModifyInstanceStorage \ + --RegistryId '' \ + --TargetRegion '' \ + --TargetStorageName '' \ + --region +# expected: exit 0, 返回 RegistryId +``` + +**预期输出:** + +```json +{ + "RegistryId": "tcr-example", + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | TCR 主实例 ID | 格式 `tcr-` 开头 | 步骤 1 `DescribeInstances` 输出的 `RegistryId` | +| `` | 目标 COS 桶所在地域 | 须与 `ReplicationRegionName` 一致 | 步骤 2 `DescribeReplicationInstances` 输出的 `ReplicationRegionName` | +| `` | 复制实例关联的 COS 桶名称 | 格式 `---` | 步骤 3 确认的目标桶名 | +| `` | 主实例所在地域 | -- | `tccli configure list` 或步骤 1 `RegionName` | + +> **警告:此操作是破坏性的,不可逆。** 切换后实例进入**只读模式** -- 仅支持镜像拉取(`docker pull`),不支持镜像推送(`docker push`)。如需恢复推送能力,必须再次调用 `ModifyInstanceStorage` 将存储指向原 COS 桶(见[清理](#清理))。切换至异地桶后将产生跨地域复制流量和备份桶存储费用。 + +### 步骤 6:等待切换生效 + +`ModifyInstanceStorage` 返回成功后,后端服务需要约 1-2 分钟滚动更新。轮询确认实例恢复 `Running` 状态: + +```bash +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region +# expected: exit 0, Status: "Running" +``` + +**预期输出:** + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "tcr-example.tencentcloudcr.com", + "InternalEndpoint": "10.1.67.137" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +多维度验证切换是否真正生效: + +| 维度 | 检查内容 | 命令 | 预期 | +|------|---------|------|------| +| 状态 | 实例是否恢复 Running | `DescribeInstances --Registryids '[""]'` | `Status: "Running"` | +| 控制面 | `ModifyInstanceStorage` 返回 `RegistryId` | 步骤 5 输出 | `RegistryId` 非空,与主实例 ID 一致 | +| 复制实例 | 目标复制实例状态正常 | `DescribeReplicationInstances --RegistryId ''` | 目标复制实例 `Status: "Running"` | +| 数据面 | 镜像拉取可达 | `docker pull .tencentcloudcr.com//:` | 拉取成功(见[验证](#验证)) | +| 只读模式 | 推送被拒绝 | `docker push .tencentcloudcr.com//:` | 推送失败(预期行为) | + +> 直到所有维度确认无误后继续。最长等待约 3 分钟。超时参见 [排障](#排障)。 + +## 验证 + +### 控制面(tccli) + +```bash +# 确认主实例状态 +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region +# expected: exit 0, Status: "Running" +``` + +**预期输出:** + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "tcr-example.tencentcloudcr.com" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +```bash +# 确认复制实例状态 +tccli tcr DescribeReplicationInstances \ + --RegistryId '' \ + --region +# expected: exit 0, 目标复制实例 Status: "Running" +``` + +**预期输出:** + +```json +{ + "TotalCount": 1, + "ReplicationRegistries": [ + { + "RegistryId": "tcr-example", + "ReplicationRegistryId": "tcr-example-4-ghbzyc", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai", + "Status": "Running" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +### COS 全球加速验证(控制台) + +确认目标桶全球加速仍为"已开启"状态: +打开 [COS 控制台](https://console.cloud.tencent.com/cos/bucket) -> 选择目标桶 -> **域名与传输管理** -> **全球加速**。 + +### 数据面(docker) + +切换完成后,用 Docker 客户端拉取实例内已有镜像,验证存储切换生效。 + +首先获取临时登录凭证: + +```bash +tccli tcr CreateInstanceToken \ + --RegistryId '' \ + --TokenType temp \ + --Desc "存储切换验证用临时凭证" \ + --region +# expected: exit 0, 返回 Username 和 Token +``` + +**预期输出:** + +```json +{ + "Username": "100012345678", + "Token": "eyJhbGciOi...", + "ExpTime": 1718552800, + "TokenId": "tcr-example-token-abc123", + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +```bash +docker login .tencentcloudcr.com \ + --username \ + --password +# expected: Login Succeeded +``` + +然后拉取镜像: + +```bash +docker pull .tencentcloudcr.com//: +# expected: 拉取成功 +``` + +```text +TAG: Pulling from / +Digest: sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +Status: Downloaded newer image for .tencentcloudcr.com//: +``` + +**预期结果:** + +- 拉取成功 -- 存储切换已生效,镜像数据从目标 COS 桶被正常读取 +- 推送失败 -- 预期行为,实例处于只读模式(仅 `pull`,不可 `push`) + +## 清理 + +后端存储切换操作本身不新建资源,但切换后实例处于只读模式且持续产生跨地域 COS 费用。如需**回切到原 COS 桶**并恢复推送能力,按以下步骤操作。 + +> **计费警告:** 切换至备份桶后,跨地域复制流量和备份桶存储将持续产生 COS 费用。回切至原桶后可停止备份桶的额外费用。详见 [COS 计费说明](https://cloud.tencent.com/document/product/436/16871)。 + +> **副作用警告:** 再次调用 `ModifyInstanceStorage` 回切时,实例的后端存储指向将变更。回切期间(约 1-2 分钟)实例不可用。回切后恢复完整读/写能力。此操作不删除任何资源(原桶和备份桶均保留),仅变更实例的路由指向。 + +### 1. 清理前状态检查 + +```bash +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region +# expected: exit 0, Status: "Running",确认是待回切的实例 +``` + +**预期输出:** + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +```bash +tccli tcr DescribeReplicationInstances \ + --RegistryId '' \ + --region +# expected: exit 0, 确认复制实例 ID,记录当前后端存储指向的地域 +``` + +**预期输出:** + +```json +{ + "TotalCount": 1, + "ReplicationRegistries": [ + { + "RegistryId": "tcr-example", + "ReplicationRegistryId": "tcr-example-4-ghbzyc", + "ReplicationRegionId": 4, + "ReplicationRegionName": "ap-shanghai", + "Status": "Running" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +### 2. 回切至原 COS 桶 + +```bash +tccli tcr ModifyInstanceStorage \ + --RegistryId '' \ + --TargetRegion '' \ + --TargetStorageName '' \ + --region +# expected: exit 0, 返回 RegistryId +``` + +**预期输出:** + +```json +{ + "RegistryId": "tcr-example", + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +| 参数 | 回切时的值 | 获取方式 | +|------|----------|---------| +| `--TargetRegion` | 主实例所在地域 | 步骤 1 `DescribeInstances` 输出中的 `RegionName`(如 `ap-guangzhou`) | +| `--TargetStorageName` | 原主实例的 COS 桶名称 | 打开 [COS 控制台](https://console.cloud.tencent.com/cos/bucket),搜索主实例 ID(如 `tcr-example`),**不含**异地 RegionCode 和复制实例 ID 的桶即为原桶 | + +**原 COS 桶命名格式:** `---`,其中 `LocalRegionCode` 为主实例所在地域的数字编码(广州为 `1`)。 + +### 3. 等待回切生效并验证 + +回切后等待约 1-2 分钟: + +```bash +tccli tcr DescribeInstances \ + --Registryids '[""]' \ + --region +# expected: exit 0, Status: "Running" +``` + +**预期输出:** + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "tcr-example.tencentcloudcr.com" + } + ], + "RequestId": "00000000-0000-0000-0000-000000000000" +} +``` + +### 4. 验证推送能力已恢复 + +```bash +docker login .tencentcloudcr.com \ + --username \ + --password +# expected: Login Succeeded + +docker push .tencentcloudcr.com//: +# expected: 推送成功 +``` + +推送成功即表明回切完成,实例恢复完整读/写(pull/push)能力。 + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ModifyInstanceStorage` 返回 `InvalidParameter` | 检查 `--TargetStorageName` 是否包含 `.cos.accelerate.myqcloud.com` 后缀 | 填入了 COS 加速域名而非桶名称 | 确认参数值为 COS 桶名称(格式 `---`),不含域名后缀 | +| `ModifyInstanceStorage` 返回 `InvalidParameter` 且桶名正确 | `tccli tcr DescribeReplicationInstances --RegistryId '' --region ` 查看 `ReplicationRegionName` | `--TargetRegion` 与复制实例所在地域不匹配 | 将 `--TargetRegion` 改为 `ReplicationRegionName` 的值 | +| `ModifyInstanceStorage` 返回 `FailedOperation` | 打开 [COS 控制台](https://console.cloud.tencent.com/cos/bucket) -> 选择目标桶 -> 域名与传输管理 -> 全球加速,检查加速状态 | COS 桶未开启全球加速 | 在 COS 控制台开启全球加速后重试 | +| `ModifyInstanceStorage` 返回 `FailedOperation` 且加速已开启 | `tccli tcr DescribeInstances --Registryids '[""]' --region ` 检查实例状态 | 实例 `Status` 非 `Running` | 等待实例恢复 `Running` 后重试 | +| `ModifyInstanceStorage` 返回 `InternalError` | 记录返回的 `RequestId`,等待 1 分钟后重试 | TCR 后端服务临时异常 | 稍后重试;若持续失败则保留 `RegistryId`、`RequestId`、调用时间 -> [提交工单](https://console.cloud.tencent.com/workorder) | +| `ModifyInstanceStorage` 返回 `UnauthorizedOperation` | `tccli tcr DescribeInstances --region ` 确认权限 | 缺少 `tcr:ModifyInstanceStorage` 权限(环境限制,非命令错误) | 联系主账号授予 `tcr:ModifyInstanceStorage` 权限 | +| `PutBucketAccelerate` 返回 `UnauthorizedOperation` | 检查 COS 控制台是否可访问目标桶设置 | 缺少 COS 桶操作权限(环境限制,非命令错误) | 联系主账号授予 `cos:PutBucketAccelerate` 权限,或前往 COS 控制台手动开启 | +| 无法查询 COS 桶加速状态 | 打开 [COS 控制台](https://console.cloud.tencent.com/cos/bucket) 确认桶名和地域 | 桶名或地域错误,或桶不存在 | 在 COS 控制台存储桶列表中搜索主实例 ID 确认桶名正确,并在正确的地域下查看 | + +### 切换成功但状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ModifyInstanceStorage` 返回成功但实例长期非 `Running` | `tccli tcr DescribeInstances --Registryids '[""]' --region ` 轮询 `Status` | 后端存储切换滚动更新缓慢 | 继续轮询,每次间隔 15 秒;超过 5 分钟则保留 `RegistryId`、`RequestId` -> [提交工单](https://console.cloud.tencent.com/workorder) | +| 实例 `Running` 但 Docker 拉取失败 | `docker pull .tencentcloudcr.com//:` 查看具体错误 | 镜像未同步至复制桶、VPC 网络不可达、或后端路由未完全生效 | 1) 确认镜像已同步至复制桶(检查复制实例同步日志);2) `ping .tencentcloudcr.com` 检查网络可达性;3) 等待 2-3 分钟后重试 | +| 实例 `Running` 但无法推送镜像 | `docker push .tencentcloudcr.com//:` | 此为预期行为:切换后实例进入只读模式 | 正常现象,非故障。如需恢复推送能力,执行[清理](#清理)中的回切操作 | +| COS 桶名称无法确认 | 打开 [COS 控制台](https://console.cloud.tencent.com/cos/bucket) 搜索桶列表 | 桶格式不明确或存在多个桶 | 以主实例 ID 搜索;名称含目标 `RegionCode` 且含复制实例 ID 的桶即为目标桶;如仍无法确认,[提交工单](https://console.cloud.tencent.com/workorder) | +| `DescribeReplicationInstances` 返回空列表 | `tccli tcr DescribeReplicationInstances --RegistryId '' --region ` | 未创建复制实例或复制实例已删除 | 参见 [同实例多地域复制镜像](../../ops/image-distribution/cross-region-replication) 创建复制实例 | + +## 下一步 + +- [同实例多地域复制镜像](../../ops/image-distribution/cross-region-replication) -- 创建复制实例详细指南 +- [创建企业版实例](../../ops/instances/create) -- 实例创建与升级 +- [使用自定义域名及云联网实现跨地域内网访问](../custom-domain-ccn) -- 跨地域内网访问 +- [混合云下的多平台镜像数据同步复制](../hybrid-cloud-sync) -- 跨平台同步场景 + +## 控制台替代 + +- [容器镜像服务控制台](https://console.cloud.tencent.com/tcr/instance) -- 实例管理、查看后端存储桶 +- [COS 控制台](https://console.cloud.tencent.com/cos/bucket) -- COS 桶全球加速管理 +- [API Explorer: ModifyInstanceStorage](https://console.cloud.tencent.com/api/explorer?Product=tcr&Version=2019-09-24&Action=ModifyInstanceStorage) diff --git a/src/content/docs/cli/tcr/practices/tke-plugin-pull-tcrctl.md b/src/content/docs/cli/tcr/practices/tke-plugin-pull-tcrctl.md new file mode 100644 index 0000000..6a863b4 --- /dev/null +++ b/src/content/docs/cli/tcr/practices/tke-plugin-pull-tcrctl.md @@ -0,0 +1,26 @@ +--- +title: "TKE 集群使用 TCR 插件内网免密拉取容器镜像(tcrctl / tkectl)" +description: "· page_id `48184`" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> 对照官方:[TKE 集群使用 TCR 插件内网免密拉取容器镜像](https://cloud.tencent.com/document/product/1141/48184) · page_id `48184` +> +> **P1 — 占位符** · 待 P0 全量完成后填充 `tcrctl` / `tkectl` 操作体。 + +## Overview + +(P1 待撰写:`tcrctl registry vpc-link` / `tkectl addon install tcr` 等产品 CLI 命令封装本页控制面操作。) + +## Before you begin + +参见同目录 [tccli 操作.md](.) 的 Before you begin。 + +## Procedure + +> P1 待撰写。 + +## Next steps + +参见同目录 [tccli 操作.md](.) 的 Next steps。 diff --git a/src/content/docs/cli/tcr/practices/tke-plugin-pull.md b/src/content/docs/cli/tcr/practices/tke-plugin-pull.md new file mode 100644 index 0000000..4361712 --- /dev/null +++ b/src/content/docs/cli/tcr/practices/tke-plugin-pull.md @@ -0,0 +1,875 @@ +--- +title: "TKE 集群使用 TCR 插件内网免密拉取容器镜像" +description: "· page_id `48184`" +--- + +> 对照官方:[TKE 集群使用 TCR 插件内网免密拉取容器镜像](https://cloud.tencent.com/document/product/1141/48184) · page_id `48184` + +## 概述 + +在 TKE 集群中安装 TCR 插件,实现内网免密拉取 TCR 企业版实例内的容器镜像并创建工作负载。TCR 插件自动为集群内节点配置关联 TCR 实例的内网解析,部署工作负载时无需显式配置 `imagePullSecrets`。 + +核心流程:准备容器镜像 → 关联集群 VPC 至 TCR 实例 → 配置内网域名解析 → 安装 TCR 插件 → 创建免密工作负载。 + +## 前置条件 + +- [环境准备](../../../index.md) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域(如 ap-guangzhou) + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tcr:DescribeInstances, tcr:CreateNamespace, tcr:CreateInstanceToken +# tcr:ManageInternalEndpoint, tcr:DescribeInternalEndpoints +# tcr:CreateInternalEndpointDns, tcr:DescribeInternalEndpointDnsStatus +# tcr:DeleteInternalEndpointDns +# tke:DescribeClusters, tke:InstallAddon, tke:DescribeAddon, tke:DeleteAddon +# vpc:DescribeVpcs, vpc:DescribeSubnets +# 验证:执行 DescribeInstances 确认 TCR 权限 +tccli tcr DescribeInstances --region +# expected: exit 0,返回实例列表(可为空) + +# 验证:执行 DescribeClusters 确认 TKE 权限 +tccli tke DescribeClusters --region +# expected: exit 0,返回集群列表(可为空) + +# 4. 检查 kubectl 可用性 +kubectl version --client +# expected: 输出版本信息,如 Client Version: v1.28.0 + +# 5. 检查 Docker 可用性(需推送镜像至 TCR) +docker --version +# expected: 输出版本信息,如 Docker version 24.0.0 +``` + +### 资源检查 + +```bash +# 6. 确认 TCR 企业版实例存在且运行(需为企业版,basic/standard/premium 均可) +tccli tcr DescribeInstances --region +# expected: Registries 列表中目标实例 RegistryType 为 basic/standard/premium,Status 为 "Running" + +# 7. 确认 TKE 集群存在且运行(需自建 TKE 集群,本文档未在测试环境中实际执行 TKE 集群操作) +tccli tke DescribeClusters --region --ClusterIds '["CLUSTER_ID"]' +# expected: ClusterStatus 为 "Running",ClusterVersion >= 1.14 + +# 8. 确认 TKE 集群 Kubernetes 版本 >= 1.14 +tccli tke DescribeClusters --region --ClusterIds '["CLUSTER_ID"]' | jq '.Clusters[0].ClusterVersion' +# expected: 版本号 >= 1.14(如 "1.28.3") + +# 9. 查询 TKE 集群所在 VPC 和子网 +tccli tke DescribeClusters --region \ + --ClusterIds '["CLUSTER_ID"]' +# expected: ClusterNetworkSettings.VpcId 非空,记录此值供后续步骤使用 + +tccli vpc DescribeSubnets --region \ + --Filters '[{"Name":"vpc-id","Values":["VPC_ID"]}]' +# expected: 至少返回 1 个子网,AvailableIpCount >= 1 +``` + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 查看 TCR 实例列表 | `DescribeInstances` | 是 | +| 创建命名空间 | `CreateNamespace` | 否 | +| 创建长期访问凭证 | `CreateInstanceToken --TokenType longterm` | 否 | +| 推送镜像至 TCR | `docker login` / `docker tag` / `docker push` | — | +| 查看 TCR 内网访问链路 | `DescribeInternalEndpoints` | 是 | +| 查看 TKE 集群列表 | `DescribeClusters` | 是 | +| 查看集群已安装组件 | `DescribeAddon` | 是 | +| 安装 TCR 组件 | `InstallAddon --AddonName TCR` | 否 | +| 卸载 TCR 组件 | `DeleteAddon --AddonName TCR` | 是 | +| 创建工作负载(Deployment) | `kubectl apply -f deployment.yaml` | 否 | + +## 关键字段说明 + +### `ManageInternalEndpoint` + +以下说明 `ManageInternalEndpoint` 的主要参数。完整参数定义见 `tccli tcr ManageInternalEndpoint --help`。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | TCR 实例 ID,格式 `tcr-xxxxxxxx`。由 `DescribeInstances` 返回 | 实例不存在返回 `ResourceNotFound` | +| `Operation` | String | 是 | `Create` 或 `Delete` | 错误值返回 `InvalidParameter` | +| `VpcId` | String | 是 | VPC ID,格式 `vpc-xxxxxxxx`。从 TKE 集群 `ClusterNetworkSettings.VpcId` 获取 | VPC 不存在返回 `InvalidParameter.VpcIdNotFound` | +| `SubnetId` | String | 是 | 子网 ID,格式 `subnet-xxxxxxxx`。需与 `VpcId` 属于同一 VPC | 子网不存在返回 `InvalidParameter.SubnetIdNotFound` | +| `RegionId` | Integer | 否 | 地域数字编码(ap-guangzhou=1)。省略时由 `--region` 自动推断 | 错误编码可能导致关联到错误地域 | + +### `CreateInternalEndpointDns` + +以下说明 `CreateInternalEndpointDns` 的主要参数。完整参数定义见 `tccli tcr CreateInternalEndpointDns --help`。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `InstanceId` | String | 是 | TCR 实例 ID,格式 `tcr-xxxxxxxx` | 实例不存在返回 `ResourceNotFound` | +| `VpcId` | String | 是 | VPC ID。必须先通过 `ManageInternalEndpoint` 关联至 TCR 实例 | VPC 未关联返回 `UnsupportedOperation` | +| `EniLBIp` | String | 是 | 内网访问 IP,从 `DescribeInternalEndpoints` 返回的 `EniLBIp` 获取(非 `AccessIp`) | IP 错误导致 DNS 解析失败 | +| `UsePublicDomain` | Boolean | 否 | 是否使用 TCR 公网域名做内网解析,推荐 `true`。默认 `true` | `false` 时需额外配置自定义域名和 SSL 证书 | +| `RegionName` | String | 否 | 地域名称(如 `ap-guangzhou`)。省略时由 `--region` 自动推断 | 填错可能导致跨地域解析异常 | + +## 操作步骤 + +### 步骤 1:查询 TCR 实例 + +```bash +tccli tcr DescribeInstances --region +# expected: exit 0,Registries 列表中包含目标实例 +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "example-registry.tencentcloudcr.com", + "RegionName": "ap-guangzhou", + "InternalEndpoint": "10.1.65.238" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `REGISTRY_ID` | TCR 实例 ID | 格式 `tcr-xxxxxxxx` | `DescribeInstances` 返回的 `RegistryId` | +| `REGION` | 地域 | 如 `ap-guangzhou` | `tccli configure list` 或 `DescribeInstances` 返回的 `RegionName` | + +### 步骤 2:创建命名空间 + +TCR 企业版实例没有默认命名空间,且不支持在推送镜像时自动创建,需手动创建。 + +#### 选择依据 + +- **NamespaceName**:按实际项目命名。本文示例使用 `demo-tcr`。命名空间名在同一实例内唯一。 +- **IsPublic**:设为 `true`,允许匿名拉取该命名空间下的镜像。若需访问控制,设 `false`,后续需配合 TCR 插件或手动配置 `imagePullSecrets`。 + +```bash +tccli tcr CreateNamespace \ + --RegistryId REGISTRY_ID \ + --NamespaceName demo-tcr \ + --IsPublic true \ + --region +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +验证命名空间创建成功: + +```bash +tccli tcr DescribeNamespaces \ + --RegistryId REGISTRY_ID \ + --region +# expected: NamespaceList 中包含 demo-tcr,IsPublic 为 true +``` + +**预期输出**: + +```json +{ + "NamespaceInfoList": [ + { + "NamespaceName": "demo-tcr", + "IsPublic": true + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +### 步骤 3:获取长期访问凭证 + +使用 `CreateInstanceToken` 创建长期访问凭证,用于 `docker login` 登录 TCR 实例。无需跳转至服务级账号管理页面。 + +#### 选择依据 + +- **TokenType**:选择 `longterm` 而非短期临时凭证。长期凭证适合持续集成和本地开发场景,无需频繁刷新。注意长期凭证有效期较长,需妥善保管。 +- **Desc**:可选。建议填写描述(如 `"cli-demo-token"`)以便后续在控制台识别该凭证用途。 + +```bash +tccli tcr CreateInstanceToken \ + --RegistryId REGISTRY_ID \ + --TokenType longterm \ + --Desc "cli-demo-token" \ + --region +# expected: exit 0,返回 Username 和 Token +``` + +**预期输出**: + +```json +{ + "Username": "UIN", + "Token": "eyJhbGciOiJSUzI1NiIsImtpZCI6...", + "ExpTime": 2096844746789, + "TokenId": "TOKEN_ID", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +记录 `Username` 和 `Token`,供下一步 `docker login` 使用。**注意** `Token` 仅在创建时返回一次,请妥善保存。 + +### 步骤 4:推送容器镜像至 TCR + +本步骤需要本地安装 Docker 环境,且客户端 IP 在 TCR 实例的网络访问策略允许范围内。 + +```bash +# 登录 TCR 实例 +docker login REGISTRY_DOMAIN \ + --username USERNAME \ + --password TOKEN +# expected: Login Succeeded +``` + +**预期输出**: + +```text +Login Succeeded +``` + +```bash +# 拉取测试镜像(以 getting-started 为例) +docker pull getting-started:latest +# expected: 拉取成功,输出 Digest + +# 标记镜像 +docker tag getting-started:latest REGISTRY_DOMAIN/demo-tcr/getting-started:latest +# expected: 无输出 + +# 推送镜像至 TCR +docker push REGISTRY_DOMAIN/demo-tcr/getting-started:latest +# expected: 推送成功,输出 Digest +``` + +**预期输出**: + +```text +The push refers to repository [REGISTRY_DOMAIN/demo-tcr/getting-started] +latest: digest: sha256:a1b2c3d4e5f6... size: 1234 +``` + +| 占位符 | 说明 | 获取方式 | +|--------|------|---------| +| `REGISTRY_DOMAIN` | TCR 实例公网域名 | `DescribeInstances` 返回的 `PublicDomain` | +| `USERNAME` | 登录用户名 | 步骤 3 中 `CreateInstanceToken` 返回的 `Username` | +| `TOKEN` | 长期访问凭证 | 步骤 3 中 `CreateInstanceToken` 返回的 `Token` | + +> 若镜像仓库尚不存在,推送时会自动创建,无需提前手动执行 `CreateRepository`。 + +### 步骤 5:查询 TKE 集群 VPC 信息 + +> **说明**:以下 TKE 集群相关操作(`DescribeClusters`、`InstallAddon`)需在自建 TKE 集群环境下执行。本文档中的 TCR 内网端点操作(`ManageInternalEndpoint`、`CreateInternalEndpointDns`、`DescribeInternalEndpoints`)已在 premium 实例上真实验证通过。实际操作时请替换为自有 TKE 集群所在 VPC 和子网。 + +查询集群网络配置,获取 `VpcId`: + +```bash +tccli tke DescribeClusters \ + --ClusterIds '["CLUSTER_ID"]' \ + --region +# expected: exit 0,返回集群网络配置 +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Clusters": [ + { + "ClusterId": "cls-example", + "ClusterName": "tke-demo", + "ClusterVersion": "1.28.3", + "ClusterStatus": "Running", + "ClusterNetworkSettings": { + "VpcId": "vpc-example", + "ClusterCIDR": "172.16.0.0/16" + } + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +记录 `VpcId` 的值,用于下一步关联 TCR 实例。 + +### 步骤 6:关联集群 VPC 至 TCR 实例 + +TCR 企业版默认拒绝全部来源的外部访问。需将 TKE 集群所在的 VPC 关联至 TCR 实例,建立内网访问链路。 + +#### 选择依据 + +- **Operation**:选择 `Create`,本次目标为新增 VPC 关联。若需移除则使用 `Delete`。 +- **VpcId**:使用步骤 5 中 TKE 集群的 `VpcId`,确保 TCR 内网访问链路指向集群所在网络。 +- **SubnetId**:选择与 `VpcId` 同 VPC 的子网。推荐选择集群节点所在的子网以减少跨子网路由。 +- **RegionId**:推荐省略此字段,由 `--region` 自动推断地域编码,避免硬编码导致跨地域操作失误。 + +#### 最小配置 + +仅含必填字段。`manage-internal-endpoint-minimal.json`: + +```json +{ + "RegistryId": "REGISTRY_ID", + "Operation": "Create", + "VpcId": "VPC_ID", + "SubnetId": "SUBNET_ID" +} +``` + +#### 增强配置 + +含可选字段。`manage-internal-endpoint-enhanced.json`: + +```json +{ + "RegistryId": "REGISTRY_ID", + "Operation": "Create", + "VpcId": "VPC_ID", + "SubnetId": "SUBNET_ID", + "RegionId": 1 +} +``` + +执行前先查看当前内网访问链路状态,确认尚无 VPC 关联: + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0,AccessVpcSet 为空或 null +``` + +**预期输出**: + +```json +{ + "AccessVpcSet": null, + "TotalCount": 0, + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +执行关联: + +```bash +tccli tcr ManageInternalEndpoint \ + --cli-input-json file://manage-internal-endpoint-minimal.json \ + --region +# expected: exit 0,返回 RegistryId 和 RequestId +``` + +**预期输出**: + +```json +{ + "RegistryId": "REGISTRY_ID", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +关联后轮询验证(多维度): + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0,AccessVpcSet 中包含目标 VPC +``` + +**预期输出**: + +```json +{ + "AccessVpcSet": [ + { + "VpcId": "vpc-example", + "SubnetId": "subnet-example", + "Status": "Running", + "AccessIp": "10.0.0.10", + "EniLBIp": "10.0.0.11" + } + ], + "TotalCount": 1, + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 关联状态 | `AccessVpcSet[].Status` | `Running` | +| VPC 匹配 | `AccessVpcSet[].VpcId` | 与传入的 `VpcId` 一致 | +| 子网匹配 | `AccessVpcSet[].SubnetId` | 与传入的 `SubnetId` 一致 | +| 访问 IP | `AccessVpcSet[].EniLBIp` | 非空,记录此值供步骤 7 使用 | + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `VPC_ID` | VPC 实例 ID | 格式 `vpc-xxxxxxxx` | 步骤 5 中 `DescribeClusters` 返回的 `VpcId` | +| `SUBNET_ID` | 子网 ID | 格式 `subnet-xxxxxxxx`,需与 `VpcId` 同 VPC | `tccli vpc DescribeSubnets --region --Filters '[{"Name":"vpc-id","Values":["VPC_ID"]}]'` | +| `ENI_LB_IP` | 内网访问 IP | 为 `EniLBIp` 而非 `AccessIp` | 本步骤 `DescribeInternalEndpoints` 返回的 `EniLBIp` | + +### 步骤 7:配置内网域名解析 + +为 TCR 实例创建内网域名解析,使集群内 Pod 能通过内网域名访问 TCR。 + +#### 选择依据 + +- **UsePublicDomain**:选择 `true`,使用 TCR 公网域名(如 `example-registry.tencentcloudcr.com`)作为内网解析目标。相比自建域名,公网域名无需额外申请和配置 SSL 证书,且与公网访问使用同一域名,避免镜像地址不一致。 +- **EniLBIp**:使用步骤 6 中 `DescribeInternalEndpoints` 返回的 `EniLBIp`,而非 `AccessIp`。`EniLBIp` 是负载均衡 VIP,支持高可用;`AccessIp` 是单点 IP。 +- **RegionName**:推荐省略,由 `--region` 自动推断。 + +#### 最小配置 + +仅含必填字段。`create-internal-endpoint-dns-minimal.json`: + +```json +{ + "InstanceId": "REGISTRY_ID", + "VpcId": "VPC_ID", + "EniLBIp": "ENI_LB_IP" +} +``` + +#### 增强配置 + +含可选字段。`create-internal-endpoint-dns-enhanced.json`: + +```json +{ + "InstanceId": "REGISTRY_ID", + "VpcId": "VPC_ID", + "EniLBIp": "ENI_LB_IP", + "UsePublicDomain": true, + "RegionName": "REGION" +} +``` + +```bash +tccli tcr CreateInternalEndpointDns \ + --cli-input-json file://create-internal-endpoint-dns-minimal.json \ + --region +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +验证内网域名解析状态: + +```bash +tccli tcr DescribeInternalEndpointDnsStatus \ + --region \ + --VpcSet '[{"InstanceId":"REGISTRY_ID","VpcId":"VPC_ID","EniLBIp":"ENI_LB_IP","UsePublicDomain":true}]' +# expected: exit 0,Status 为 ENABLED +``` + +**预期输出**: + +```json +{ + "VpcSet": [ + { + "Status": "ENABLED" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +`Status` 为 `ENABLED` 表示内网域名解析已生效。 + +> **说明**:若所在地域暂不支持自动 DNS 解析,可在 TKE 集群节点自定义脚本中添加 `echo 'ENI_LB_IP TCR_DOMAIN' >> /etc/hosts` 手动配置。 + +### 步骤 8:安装 TCR 组件 + +在 TKE 集群中安装 TCR 扩展组件。组件安装完成后,集群内 Pod 可内网免密拉取关联实例的镜像。 + +> **注意**:此步骤需在自建 TKE 集群环境下执行(需 `tke:InstallAddon` 权限),本文档未在测试环境中实际执行此命令。以下命令格式经 API 文档验证,参数取值正确。 + +#### 选择依据 + +- **AddonName**:固定为 `TCR`,无其他可选值。 +- **RawValues**:`registryId` 设为步骤 6 中关联的 TCR 实例 ID。此配置告知 TCR 组件应为哪个实例提供免密拉取能力。一个集群可关联多个 TCR 实例,但此值仅支持单个实例;如需多实例免密,需多次调用 `InstallAddon` 并配置不同的 `registryId`。 +- **AddonVersion**:可选。省略时安装最新版本。 + +#### 最小配置 + +仅含必填字段: + +```bash +tccli tke InstallAddon \ + --ClusterId CLUSTER_ID \ + --AddonName TCR \ + --RawValues '{"registryId":"REGISTRY_ID"}' \ + --region +# expected: exit 0,返回 RequestId +``` + +#### 增强配置 + +指定组件版本: + +```bash +tccli tke InstallAddon \ + --ClusterId CLUSTER_ID \ + --AddonName TCR \ + --AddonVersion "1.0.0" \ + --RawValues '{"registryId":"REGISTRY_ID"}' \ + --region +# expected: exit 0,返回 RequestId +``` + +轮询确认组件安装状态(多维度): + +```bash +tccli tke DescribeAddon \ + --ClusterId CLUSTER_ID \ + --AddonName TCR \ + --region +# expected: exit 0,Addons[].AddonStatus 为 Running +``` + +> **DescribeAddon 响应行为**:该 API 有两种返回模式—— +> - **组件已安装**:返回 HTTP 200,JSON 体中含 `Addons` 数组,包含组件状态、版本等信息; +> - **组件未安装或集群不存在**:返回非 0 退出码及错误信息(如 `ResourceNotFound` 或 `UnknownParameter`)。在脚本中建议检查退出码(`$?`)区分这两种情况,而非假定必定返回空列表。 + +**预期输出**: + +```json +{ + "Addons": [ + { + "AddonName": "TCR", + "AddonVersion": "1.0.0", + "AddonStatus": "Running", + "RawValues": "{\"registryId\":\"REGISTRY_ID\"}" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 组件状态 | `Addons[].AddonStatus` | `Running`(非 `Upgrading`/`Failed`) | +| 版本信息 | `Addons[].AddonVersion` | 非空,如 `1.0.0` | +| 配置一致性 | `Addons[].RawValues` | 包含正确的 `registryId` | +| 集群就绪 | 命令退出码 | `0`(区分组件未安装时的非 0 退出码) | + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `CLUSTER_ID` | TKE 集群 ID | 格式 `cls-xxxxxxxx` | 步骤 5 中 `DescribeClusters` 返回的 `ClusterId` | + +### 步骤 9:使用 TCR 实例内容器镜像创建工作负载 + +组件安装完成后创建工作负载,无需在 Pod spec 中指定 `imagePullSecrets`。`deployment.yaml`: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: demo-app + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + app: demo-app + template: + metadata: + labels: + app: demo-app + spec: + containers: + - name: getting-started + image: REGISTRY_DOMAIN/demo-tcr/getting-started:latest +``` + +> **注意**:以下 kubectl 命令需 TKE 集群公网端点可达,未在本文档环境中实际执行。 + +```bash +kubectl apply -f deployment.yaml +# expected: deployment.apps/demo-app created +``` + +**预期输出**: + +```text +deployment.apps/demo-app created +``` + +> **重要**:请避免在工作负载中手动配置 `imagePullSecrets`,否则将导致无法加载 TCR 插件的免密拉取配置。 + +## 验证 + +### 控制面(tccli) + +验证 TCR 组件多维度正常: + +```bash +tccli tke DescribeAddon \ + --ClusterId CLUSTER_ID \ + --AddonName TCR \ + --region +# expected: exit 0,AddonStatus 为 Running +``` + +> 若组件已安装,返回 Addons 数组;若返回非 0 退出码,说明组件未安装或集群不存在。参见[步骤 8 的 DescribeAddon 响应行为说明](#步骤-8安装-tcr-组件)。 + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 组件状态 | `Addons[].AddonStatus` | `Running` | +| 配置一致 | `Addons[].RawValues` | 包含目标 `registryId` | + +验证内网访问链路多维度正常: + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0,AccessVpcSet 中包含目标 VPC +``` + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 关联状态 | `AccessVpcSet[].Status` | `Running` | +| 访问 IP | `AccessVpcSet[].EniLBIp` | 非空 | +| VPC 匹配 | `AccessVpcSet[].VpcId` | 与集群所在 VPC 一致 | +| 子网匹配 | `AccessVpcSet[].SubnetId` | 与关联时传入的 SubnetId 一致 | + +验证 TCR 实例本身运行状态: + +```bash +tccli tcr DescribeInstances \ + --Registryids '["REGISTRY_ID"]' \ + --region +# expected: exit 0,Status 为 Running +``` + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 实例状态 | `Status` | `Running` | +| 实例类型 | `RegistryType` | `premium`(或其他企业版类型) | +| 公网域名 | `PublicDomain` | 非空 | + +验证命名空间存在: + +```bash +tccli tcr DescribeNamespaces \ + --RegistryId REGISTRY_ID \ + --region +# expected: NamespaceList 中包含目标命名空间 +``` + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 命名空间存在 | `NamespaceInfoList[].NamespaceName` | 包含 `demo-tcr` | +| 公开状态 | `NamespaceInfoList[].IsPublic` | `true` | + +### 数据面(kubectl) + +> **注意**:以下 kubectl 命令需 TKE 集群公网端点可达,未在本文档环境中实际执行。 + +验证 Pod 运行状态和镜像拉取正常: + +```bash +kubectl get pods -l app=demo-app +# expected: READY 1/1, STATUS Running, RESTARTS 0 +``` + +**预期输出**: + +```text +NAME READY STATUS RESTARTS AGE +demo-app-xxxxxxxxxx-xxxxx 1/1 Running 0 60s +``` + +```bash +kubectl describe pod -l app=demo-app | grep -E "Image:|ImagePullSecrets" +# expected: 镜像地址为 TCR 域名,ImagePullSecrets 为 (免密拉取生效) +``` + +**预期输出**: + +```text + Image: REGISTRY_DOMAIN/demo-tcr/getting-started:latest + ImagePullSecrets: +``` + +确认镜像拉取成功且 `ImagePullSecrets` 为 ``,表明 TCR 插件免密拉取已生效。 + +## 清理 + +> **计费警告**:TKE 集群和 TCR 企业版实例都会持续产生费用。请及时清理不再使用的资源。本文只清理本次操作创建的资源(TCR 组件和内网关联),**不建议删除 TCR 实例及命名空间**,除非确认不再使用。 + +### 数据面(kubectl) + +> **注意**:以下 kubectl 命令需 TKE 集群公网端点可达,未在本文档环境中实际执行。 + +```bash +kubectl delete deployment demo-app +# expected: deployment.apps "demo-app" deleted +``` + +**预期输出**: + +```text +deployment.apps "demo-app" deleted +``` + +### 控制面(tccli) + +#### 1. 卸载 TCR 组件 + +> **副作用警告**:卸载 TCR 组件不会影响已运行的 Pod(镜像已拉取至本地),但新创建的 Pod 将失去免密拉取能力,可能导致 `ImagePullBackOff`。请确认集群中无新建工作负载依赖免密拉取后再执行。 + +**清理前状态检查**: + +```bash +tccli tke DescribeAddon \ + --ClusterId CLUSTER_ID \ + --AddonName TCR \ + --region +# expected: AddonStatus 为 Running,确认是目标集群中待卸载的 TCR 组件 +``` + +```bash +tccli tke DeleteAddon \ + --ClusterId CLUSTER_ID \ + --AddonName TCR \ + --region +# expected: exit 0,返回 RequestId +``` + +**验证已卸载**: + +```bash +tccli tke DescribeAddon \ + --ClusterId CLUSTER_ID \ + --AddonName TCR \ + --region +# expected: 返回非 0 退出码(组件已卸载,API 返回 ResourceNotFound)或 Addons 列表为空 +``` + +#### 2. 删除 TCR 内网域名解析 + +> **副作用警告**:删除域名解析后,TKE 集群内 Pod 将无法通过内网域名解析到 TCR 实例,可能触发 DNS 解析失败。确认无工作负载依赖内网域名后再执行。 + +**清理前状态检查**: + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId REGISTRY_ID \ + --region +# expected: 确认 AccessVpcSet 中包含待删除解析的 VPC 记录,EniLBIp 非空 +``` + +```bash +tccli tcr DeleteInternalEndpointDns \ + --InstanceId REGISTRY_ID \ + --VpcId VPC_ID \ + --EniLBIp ENI_LB_IP \ + --region +# expected: exit 0,返回 RequestId +``` + +#### 3. 移除 VPC 关联 + +> **副作用警告**:移除 VPC 关联后,该 VPC 内的资源将无法通过内网访问 TCR 实例。若该 VPC 关联了多个集群使用同一 TCR 实例,所有集群都将受影响。**此操作不可逆**,需重新执行 `ManageInternalEndpoint`(`Operation: Create`)才能恢复。 + +**清理前状态检查**: + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId REGISTRY_ID \ + --region +# expected: 确认 AccessVpcSet 中包含待移除的 VPC 记录 +``` + +`manage-internal-endpoint-delete.json`: + +```json +{ + "RegistryId": "REGISTRY_ID", + "Operation": "Delete", + "VpcId": "VPC_ID", + "SubnetId": "SUBNET_ID" +} +``` + +```bash +tccli tcr ManageInternalEndpoint \ + --cli-input-json file://manage-internal-endpoint-delete.json \ + --region +# expected: exit 0,返回 RegistryId 和 RequestId +``` + +**验证已移除**: + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId REGISTRY_ID \ + --region +# expected: AccessVpcSet 中不再包含目标 VPC 的记录 +``` + +> 不建议删除 TCR 实例及命名空间,除非确认不再使用。若确需删除实例内资源,按以下顺序操作:删除镜像仓库 → 删除命名空间 → 删除实例。 + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ManageInternalEndpoint` 返回 VPC 已关联 | `tccli tcr DescribeInternalEndpoints --RegistryId REGISTRY_ID --region ` 确认该 `VpcId` 是否已在 `AccessVpcSet` 中 | 该 VPC 已关联至当前 TCR 实例,重复创建被拒绝 | 若已存在则跳过此步;若需更换子网,先执行 `Operation: Delete` 移除旧关联再重新 `Create` | +| `CreateInternalEndpointDns` 返回 `UnsupportedOperation` | `tccli tcr DescribeInternalEndpoints --RegistryId REGISTRY_ID --region ` 确认 VPC 是否已在 `AccessVpcSet` 中且 `Status` 为 `Running` | VPC 尚未通过 `ManageInternalEndpoint` 关联至 TCR 实例,或地域不支持自动 DNS 解析 | 先执行 `ManageInternalEndpoint` 关联 VPC;若地域不支持自动解析,参见步骤 7 的手动 hosts 配置方案 | +| `docker push` 返回 `denied: requested access to the resource is denied` | `tccli tcr CreateInstanceToken --RegistryId REGISTRY_ID --TokenType longterm --region ` 确认 Token 是否有效 | 未通过 `docker login` 登录 TCR,或凭证已过期 | 重新获取 Token 后执行 `docker login`;确认客户端 IP 在 TCR 网络访问策略允许范围内 | +| `DescribeNamespaces` 返回空列表或 `ResourceNotFound` | `tccli tcr DescribeInstances --Registryids '["REGISTRY_ID"]' --region ` 确认 RegistryId 和 region 是否正确 | 传入了错误的 `RegistryId` 或 `region`,或命名空间尚未创建 | 用 `DescribeInstances` 确认实例 ID 和所在地域;确认已执行步骤 2 创建命名空间 | +| `LimitExceeded` | `tccli vpc DescribeVpcs --region ` 查看当前 VPC 配额使用情况 | 当前账号 VPC 数量已达上限,无法新建 VPC 用于内网访问链路。此为环境限制,非命令错误 | 清理不再使用的 VPC 后重试 `ManageInternalEndpoint`;或使用已有 VPC 和子网 | + +### 创建成功但状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| Pod 处于 `ImagePullBackOff`,Events 显示 `401 Unauthorized` | `tccli tcr DescribeInstances --Registryids '["REGISTRY_ID"]' --region ` 确认实例 ID;`kubectl get deployment demo-app -o yaml` 检查 `imagePullSecrets` | TCR 插件配置的 `registryId` 与实际实例不匹配,或工作负载中手动配置了 `imagePullSecrets` | 检查 `InstallAddon` 中 `RawValues` 的 `registryId` 是否与 `DescribeInstances` 返回的 `RegistryId` 一致;确认 Deployment YAML 中无 `imagePullSecrets` 字段 | +| TCR 组件状态为 `Upgrading` 或 `Failed` | `tccli tke DescribeClusters --ClusterIds '["CLUSTER_ID"]' --region ` 确认 `ClusterVersion`、`ClusterStatus` | 集群 Kubernetes 版本低于 1.14,或集群地域与 TCR 实例地域不一致 | 升级集群至 >= 1.14;确认集群地域与 TCR 实例地域一致 | +| Pod Events 显示 `UnknownHost`,无法解析 TCR 内网域名 | Pod 内执行 `nslookup REGISTRY_DOMAIN` 测试 DNS 解析 | `CreateInternalEndpointDns` 未成功创建或 VPC DNS 未生效 | 检查 `CreateInternalEndpointDns` 的 `EniLBIp` 是否正确(应为 `DescribeInternalEndpoints` 返回的 `EniLBIp` 而非 `AccessIp`);确认 `UsePublicDomain` 设为 `true` | +| 镜像拉取速度慢或无响应 | `tccli tcr DescribeInternalEndpoints --RegistryId REGISTRY_ID --region ` 确认内网链路状态 | 可能使用了公网域名拉取镜像,而非内网链路 | 确认 TCR 组件 `AddonStatus` 为 `Running`;确认内网访问链路 `Status` 为 `Running`;在 Pod 内执行 `curl -I https://REGISTRY_DOMAIN/v2/` 验证连通性 | +| 返回 ClusterId 但组件状态长时间不 Running | `tccli tke DescribeAddon --ClusterId CLUSTER_ID --AddonName TCR --region ` 持续轮询状态 | 组件安装异步进行,或集群资源不足导致安装缓慢 | 继续等待;超过 15 分钟则保留 `region`、`ClusterId`、`RequestId`、`AddonName` → 登录 [TKE 控制台](https://console.cloud.tencent.com/tke2/cluster) 查看组件安装详情 | + +## 下一步 + +- [从自建 Harbor 同步镜像到 TCR 企业版](../harbor-migration) +- [TCR 企业版实例管理 — 内网访问控制](../../ops/access/network/private-access) +- [Deployment 管理 — 创建工作负载](https://cloud.tencent.com/document/product/457/31705) +- [TCR 组件说明](https://cloud.tencent.com/document/product/457/49225) + +## 控制台替代 + +[控制台 → 容器镜像服务](https://console.cloud.tencent.com/tcr) 管理 TCR 实例的命名空间与镜像仓库;[控制台 → 容器服务](https://console.cloud.tencent.com/tke2/cluster) 进入集群后通过组件管理安装 TCR 组件,勾选"启用内网解析功能"。 diff --git a/src/content/docs/cli/tcr/practices/tke-plugin-pull/tke-plugin-pull-tcrctl.md b/src/content/docs/cli/tcr/practices/tke-plugin-pull/tke-plugin-pull-tcrctl.md new file mode 100644 index 0000000..6a863b4 --- /dev/null +++ b/src/content/docs/cli/tcr/practices/tke-plugin-pull/tke-plugin-pull-tcrctl.md @@ -0,0 +1,26 @@ +--- +title: "TKE 集群使用 TCR 插件内网免密拉取容器镜像(tcrctl / tkectl)" +description: "· page_id `48184`" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> 对照官方:[TKE 集群使用 TCR 插件内网免密拉取容器镜像](https://cloud.tencent.com/document/product/1141/48184) · page_id `48184` +> +> **P1 — 占位符** · 待 P0 全量完成后填充 `tcrctl` / `tkectl` 操作体。 + +## Overview + +(P1 待撰写:`tcrctl registry vpc-link` / `tkectl addon install tcr` 等产品 CLI 命令封装本页控制面操作。) + +## Before you begin + +参见同目录 [tccli 操作.md](.) 的 Before you begin。 + +## Procedure + +> P1 待撰写。 + +## Next steps + +参见同目录 [tccli 操作.md](.) 的 Next steps。 diff --git a/src/content/docs/cli/tcr/practices/tke-plugin-pull/tke-plugin-pull.md b/src/content/docs/cli/tcr/practices/tke-plugin-pull/tke-plugin-pull.md new file mode 100644 index 0000000..4361712 --- /dev/null +++ b/src/content/docs/cli/tcr/practices/tke-plugin-pull/tke-plugin-pull.md @@ -0,0 +1,875 @@ +--- +title: "TKE 集群使用 TCR 插件内网免密拉取容器镜像" +description: "· page_id `48184`" +--- + +> 对照官方:[TKE 集群使用 TCR 插件内网免密拉取容器镜像](https://cloud.tencent.com/document/product/1141/48184) · page_id `48184` + +## 概述 + +在 TKE 集群中安装 TCR 插件,实现内网免密拉取 TCR 企业版实例内的容器镜像并创建工作负载。TCR 插件自动为集群内节点配置关联 TCR 实例的内网解析,部署工作负载时无需显式配置 `imagePullSecrets`。 + +核心流程:准备容器镜像 → 关联集群 VPC 至 TCR 实例 → 配置内网域名解析 → 安装 TCR 插件 → 创建免密工作负载。 + +## 前置条件 + +- [环境准备](../../../index.md) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域(如 ap-guangzhou) + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tcr:DescribeInstances, tcr:CreateNamespace, tcr:CreateInstanceToken +# tcr:ManageInternalEndpoint, tcr:DescribeInternalEndpoints +# tcr:CreateInternalEndpointDns, tcr:DescribeInternalEndpointDnsStatus +# tcr:DeleteInternalEndpointDns +# tke:DescribeClusters, tke:InstallAddon, tke:DescribeAddon, tke:DeleteAddon +# vpc:DescribeVpcs, vpc:DescribeSubnets +# 验证:执行 DescribeInstances 确认 TCR 权限 +tccli tcr DescribeInstances --region +# expected: exit 0,返回实例列表(可为空) + +# 验证:执行 DescribeClusters 确认 TKE 权限 +tccli tke DescribeClusters --region +# expected: exit 0,返回集群列表(可为空) + +# 4. 检查 kubectl 可用性 +kubectl version --client +# expected: 输出版本信息,如 Client Version: v1.28.0 + +# 5. 检查 Docker 可用性(需推送镜像至 TCR) +docker --version +# expected: 输出版本信息,如 Docker version 24.0.0 +``` + +### 资源检查 + +```bash +# 6. 确认 TCR 企业版实例存在且运行(需为企业版,basic/standard/premium 均可) +tccli tcr DescribeInstances --region +# expected: Registries 列表中目标实例 RegistryType 为 basic/standard/premium,Status 为 "Running" + +# 7. 确认 TKE 集群存在且运行(需自建 TKE 集群,本文档未在测试环境中实际执行 TKE 集群操作) +tccli tke DescribeClusters --region --ClusterIds '["CLUSTER_ID"]' +# expected: ClusterStatus 为 "Running",ClusterVersion >= 1.14 + +# 8. 确认 TKE 集群 Kubernetes 版本 >= 1.14 +tccli tke DescribeClusters --region --ClusterIds '["CLUSTER_ID"]' | jq '.Clusters[0].ClusterVersion' +# expected: 版本号 >= 1.14(如 "1.28.3") + +# 9. 查询 TKE 集群所在 VPC 和子网 +tccli tke DescribeClusters --region \ + --ClusterIds '["CLUSTER_ID"]' +# expected: ClusterNetworkSettings.VpcId 非空,记录此值供后续步骤使用 + +tccli vpc DescribeSubnets --region \ + --Filters '[{"Name":"vpc-id","Values":["VPC_ID"]}]' +# expected: 至少返回 1 个子网,AvailableIpCount >= 1 +``` + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 查看 TCR 实例列表 | `DescribeInstances` | 是 | +| 创建命名空间 | `CreateNamespace` | 否 | +| 创建长期访问凭证 | `CreateInstanceToken --TokenType longterm` | 否 | +| 推送镜像至 TCR | `docker login` / `docker tag` / `docker push` | — | +| 查看 TCR 内网访问链路 | `DescribeInternalEndpoints` | 是 | +| 查看 TKE 集群列表 | `DescribeClusters` | 是 | +| 查看集群已安装组件 | `DescribeAddon` | 是 | +| 安装 TCR 组件 | `InstallAddon --AddonName TCR` | 否 | +| 卸载 TCR 组件 | `DeleteAddon --AddonName TCR` | 是 | +| 创建工作负载(Deployment) | `kubectl apply -f deployment.yaml` | 否 | + +## 关键字段说明 + +### `ManageInternalEndpoint` + +以下说明 `ManageInternalEndpoint` 的主要参数。完整参数定义见 `tccli tcr ManageInternalEndpoint --help`。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | TCR 实例 ID,格式 `tcr-xxxxxxxx`。由 `DescribeInstances` 返回 | 实例不存在返回 `ResourceNotFound` | +| `Operation` | String | 是 | `Create` 或 `Delete` | 错误值返回 `InvalidParameter` | +| `VpcId` | String | 是 | VPC ID,格式 `vpc-xxxxxxxx`。从 TKE 集群 `ClusterNetworkSettings.VpcId` 获取 | VPC 不存在返回 `InvalidParameter.VpcIdNotFound` | +| `SubnetId` | String | 是 | 子网 ID,格式 `subnet-xxxxxxxx`。需与 `VpcId` 属于同一 VPC | 子网不存在返回 `InvalidParameter.SubnetIdNotFound` | +| `RegionId` | Integer | 否 | 地域数字编码(ap-guangzhou=1)。省略时由 `--region` 自动推断 | 错误编码可能导致关联到错误地域 | + +### `CreateInternalEndpointDns` + +以下说明 `CreateInternalEndpointDns` 的主要参数。完整参数定义见 `tccli tcr CreateInternalEndpointDns --help`。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `InstanceId` | String | 是 | TCR 实例 ID,格式 `tcr-xxxxxxxx` | 实例不存在返回 `ResourceNotFound` | +| `VpcId` | String | 是 | VPC ID。必须先通过 `ManageInternalEndpoint` 关联至 TCR 实例 | VPC 未关联返回 `UnsupportedOperation` | +| `EniLBIp` | String | 是 | 内网访问 IP,从 `DescribeInternalEndpoints` 返回的 `EniLBIp` 获取(非 `AccessIp`) | IP 错误导致 DNS 解析失败 | +| `UsePublicDomain` | Boolean | 否 | 是否使用 TCR 公网域名做内网解析,推荐 `true`。默认 `true` | `false` 时需额外配置自定义域名和 SSL 证书 | +| `RegionName` | String | 否 | 地域名称(如 `ap-guangzhou`)。省略时由 `--region` 自动推断 | 填错可能导致跨地域解析异常 | + +## 操作步骤 + +### 步骤 1:查询 TCR 实例 + +```bash +tccli tcr DescribeInstances --region +# expected: exit 0,Registries 列表中包含目标实例 +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example-registry", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "example-registry.tencentcloudcr.com", + "RegionName": "ap-guangzhou", + "InternalEndpoint": "10.1.65.238" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `REGISTRY_ID` | TCR 实例 ID | 格式 `tcr-xxxxxxxx` | `DescribeInstances` 返回的 `RegistryId` | +| `REGION` | 地域 | 如 `ap-guangzhou` | `tccli configure list` 或 `DescribeInstances` 返回的 `RegionName` | + +### 步骤 2:创建命名空间 + +TCR 企业版实例没有默认命名空间,且不支持在推送镜像时自动创建,需手动创建。 + +#### 选择依据 + +- **NamespaceName**:按实际项目命名。本文示例使用 `demo-tcr`。命名空间名在同一实例内唯一。 +- **IsPublic**:设为 `true`,允许匿名拉取该命名空间下的镜像。若需访问控制,设 `false`,后续需配合 TCR 插件或手动配置 `imagePullSecrets`。 + +```bash +tccli tcr CreateNamespace \ + --RegistryId REGISTRY_ID \ + --NamespaceName demo-tcr \ + --IsPublic true \ + --region +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +验证命名空间创建成功: + +```bash +tccli tcr DescribeNamespaces \ + --RegistryId REGISTRY_ID \ + --region +# expected: NamespaceList 中包含 demo-tcr,IsPublic 为 true +``` + +**预期输出**: + +```json +{ + "NamespaceInfoList": [ + { + "NamespaceName": "demo-tcr", + "IsPublic": true + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +### 步骤 3:获取长期访问凭证 + +使用 `CreateInstanceToken` 创建长期访问凭证,用于 `docker login` 登录 TCR 实例。无需跳转至服务级账号管理页面。 + +#### 选择依据 + +- **TokenType**:选择 `longterm` 而非短期临时凭证。长期凭证适合持续集成和本地开发场景,无需频繁刷新。注意长期凭证有效期较长,需妥善保管。 +- **Desc**:可选。建议填写描述(如 `"cli-demo-token"`)以便后续在控制台识别该凭证用途。 + +```bash +tccli tcr CreateInstanceToken \ + --RegistryId REGISTRY_ID \ + --TokenType longterm \ + --Desc "cli-demo-token" \ + --region +# expected: exit 0,返回 Username 和 Token +``` + +**预期输出**: + +```json +{ + "Username": "UIN", + "Token": "eyJhbGciOiJSUzI1NiIsImtpZCI6...", + "ExpTime": 2096844746789, + "TokenId": "TOKEN_ID", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +记录 `Username` 和 `Token`,供下一步 `docker login` 使用。**注意** `Token` 仅在创建时返回一次,请妥善保存。 + +### 步骤 4:推送容器镜像至 TCR + +本步骤需要本地安装 Docker 环境,且客户端 IP 在 TCR 实例的网络访问策略允许范围内。 + +```bash +# 登录 TCR 实例 +docker login REGISTRY_DOMAIN \ + --username USERNAME \ + --password TOKEN +# expected: Login Succeeded +``` + +**预期输出**: + +```text +Login Succeeded +``` + +```bash +# 拉取测试镜像(以 getting-started 为例) +docker pull getting-started:latest +# expected: 拉取成功,输出 Digest + +# 标记镜像 +docker tag getting-started:latest REGISTRY_DOMAIN/demo-tcr/getting-started:latest +# expected: 无输出 + +# 推送镜像至 TCR +docker push REGISTRY_DOMAIN/demo-tcr/getting-started:latest +# expected: 推送成功,输出 Digest +``` + +**预期输出**: + +```text +The push refers to repository [REGISTRY_DOMAIN/demo-tcr/getting-started] +latest: digest: sha256:a1b2c3d4e5f6... size: 1234 +``` + +| 占位符 | 说明 | 获取方式 | +|--------|------|---------| +| `REGISTRY_DOMAIN` | TCR 实例公网域名 | `DescribeInstances` 返回的 `PublicDomain` | +| `USERNAME` | 登录用户名 | 步骤 3 中 `CreateInstanceToken` 返回的 `Username` | +| `TOKEN` | 长期访问凭证 | 步骤 3 中 `CreateInstanceToken` 返回的 `Token` | + +> 若镜像仓库尚不存在,推送时会自动创建,无需提前手动执行 `CreateRepository`。 + +### 步骤 5:查询 TKE 集群 VPC 信息 + +> **说明**:以下 TKE 集群相关操作(`DescribeClusters`、`InstallAddon`)需在自建 TKE 集群环境下执行。本文档中的 TCR 内网端点操作(`ManageInternalEndpoint`、`CreateInternalEndpointDns`、`DescribeInternalEndpoints`)已在 premium 实例上真实验证通过。实际操作时请替换为自有 TKE 集群所在 VPC 和子网。 + +查询集群网络配置,获取 `VpcId`: + +```bash +tccli tke DescribeClusters \ + --ClusterIds '["CLUSTER_ID"]' \ + --region +# expected: exit 0,返回集群网络配置 +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Clusters": [ + { + "ClusterId": "cls-example", + "ClusterName": "tke-demo", + "ClusterVersion": "1.28.3", + "ClusterStatus": "Running", + "ClusterNetworkSettings": { + "VpcId": "vpc-example", + "ClusterCIDR": "172.16.0.0/16" + } + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +记录 `VpcId` 的值,用于下一步关联 TCR 实例。 + +### 步骤 6:关联集群 VPC 至 TCR 实例 + +TCR 企业版默认拒绝全部来源的外部访问。需将 TKE 集群所在的 VPC 关联至 TCR 实例,建立内网访问链路。 + +#### 选择依据 + +- **Operation**:选择 `Create`,本次目标为新增 VPC 关联。若需移除则使用 `Delete`。 +- **VpcId**:使用步骤 5 中 TKE 集群的 `VpcId`,确保 TCR 内网访问链路指向集群所在网络。 +- **SubnetId**:选择与 `VpcId` 同 VPC 的子网。推荐选择集群节点所在的子网以减少跨子网路由。 +- **RegionId**:推荐省略此字段,由 `--region` 自动推断地域编码,避免硬编码导致跨地域操作失误。 + +#### 最小配置 + +仅含必填字段。`manage-internal-endpoint-minimal.json`: + +```json +{ + "RegistryId": "REGISTRY_ID", + "Operation": "Create", + "VpcId": "VPC_ID", + "SubnetId": "SUBNET_ID" +} +``` + +#### 增强配置 + +含可选字段。`manage-internal-endpoint-enhanced.json`: + +```json +{ + "RegistryId": "REGISTRY_ID", + "Operation": "Create", + "VpcId": "VPC_ID", + "SubnetId": "SUBNET_ID", + "RegionId": 1 +} +``` + +执行前先查看当前内网访问链路状态,确认尚无 VPC 关联: + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0,AccessVpcSet 为空或 null +``` + +**预期输出**: + +```json +{ + "AccessVpcSet": null, + "TotalCount": 0, + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +执行关联: + +```bash +tccli tcr ManageInternalEndpoint \ + --cli-input-json file://manage-internal-endpoint-minimal.json \ + --region +# expected: exit 0,返回 RegistryId 和 RequestId +``` + +**预期输出**: + +```json +{ + "RegistryId": "REGISTRY_ID", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +关联后轮询验证(多维度): + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0,AccessVpcSet 中包含目标 VPC +``` + +**预期输出**: + +```json +{ + "AccessVpcSet": [ + { + "VpcId": "vpc-example", + "SubnetId": "subnet-example", + "Status": "Running", + "AccessIp": "10.0.0.10", + "EniLBIp": "10.0.0.11" + } + ], + "TotalCount": 1, + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 关联状态 | `AccessVpcSet[].Status` | `Running` | +| VPC 匹配 | `AccessVpcSet[].VpcId` | 与传入的 `VpcId` 一致 | +| 子网匹配 | `AccessVpcSet[].SubnetId` | 与传入的 `SubnetId` 一致 | +| 访问 IP | `AccessVpcSet[].EniLBIp` | 非空,记录此值供步骤 7 使用 | + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `VPC_ID` | VPC 实例 ID | 格式 `vpc-xxxxxxxx` | 步骤 5 中 `DescribeClusters` 返回的 `VpcId` | +| `SUBNET_ID` | 子网 ID | 格式 `subnet-xxxxxxxx`,需与 `VpcId` 同 VPC | `tccli vpc DescribeSubnets --region --Filters '[{"Name":"vpc-id","Values":["VPC_ID"]}]'` | +| `ENI_LB_IP` | 内网访问 IP | 为 `EniLBIp` 而非 `AccessIp` | 本步骤 `DescribeInternalEndpoints` 返回的 `EniLBIp` | + +### 步骤 7:配置内网域名解析 + +为 TCR 实例创建内网域名解析,使集群内 Pod 能通过内网域名访问 TCR。 + +#### 选择依据 + +- **UsePublicDomain**:选择 `true`,使用 TCR 公网域名(如 `example-registry.tencentcloudcr.com`)作为内网解析目标。相比自建域名,公网域名无需额外申请和配置 SSL 证书,且与公网访问使用同一域名,避免镜像地址不一致。 +- **EniLBIp**:使用步骤 6 中 `DescribeInternalEndpoints` 返回的 `EniLBIp`,而非 `AccessIp`。`EniLBIp` 是负载均衡 VIP,支持高可用;`AccessIp` 是单点 IP。 +- **RegionName**:推荐省略,由 `--region` 自动推断。 + +#### 最小配置 + +仅含必填字段。`create-internal-endpoint-dns-minimal.json`: + +```json +{ + "InstanceId": "REGISTRY_ID", + "VpcId": "VPC_ID", + "EniLBIp": "ENI_LB_IP" +} +``` + +#### 增强配置 + +含可选字段。`create-internal-endpoint-dns-enhanced.json`: + +```json +{ + "InstanceId": "REGISTRY_ID", + "VpcId": "VPC_ID", + "EniLBIp": "ENI_LB_IP", + "UsePublicDomain": true, + "RegionName": "REGION" +} +``` + +```bash +tccli tcr CreateInternalEndpointDns \ + --cli-input-json file://create-internal-endpoint-dns-minimal.json \ + --region +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +验证内网域名解析状态: + +```bash +tccli tcr DescribeInternalEndpointDnsStatus \ + --region \ + --VpcSet '[{"InstanceId":"REGISTRY_ID","VpcId":"VPC_ID","EniLBIp":"ENI_LB_IP","UsePublicDomain":true}]' +# expected: exit 0,Status 为 ENABLED +``` + +**预期输出**: + +```json +{ + "VpcSet": [ + { + "Status": "ENABLED" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +`Status` 为 `ENABLED` 表示内网域名解析已生效。 + +> **说明**:若所在地域暂不支持自动 DNS 解析,可在 TKE 集群节点自定义脚本中添加 `echo 'ENI_LB_IP TCR_DOMAIN' >> /etc/hosts` 手动配置。 + +### 步骤 8:安装 TCR 组件 + +在 TKE 集群中安装 TCR 扩展组件。组件安装完成后,集群内 Pod 可内网免密拉取关联实例的镜像。 + +> **注意**:此步骤需在自建 TKE 集群环境下执行(需 `tke:InstallAddon` 权限),本文档未在测试环境中实际执行此命令。以下命令格式经 API 文档验证,参数取值正确。 + +#### 选择依据 + +- **AddonName**:固定为 `TCR`,无其他可选值。 +- **RawValues**:`registryId` 设为步骤 6 中关联的 TCR 实例 ID。此配置告知 TCR 组件应为哪个实例提供免密拉取能力。一个集群可关联多个 TCR 实例,但此值仅支持单个实例;如需多实例免密,需多次调用 `InstallAddon` 并配置不同的 `registryId`。 +- **AddonVersion**:可选。省略时安装最新版本。 + +#### 最小配置 + +仅含必填字段: + +```bash +tccli tke InstallAddon \ + --ClusterId CLUSTER_ID \ + --AddonName TCR \ + --RawValues '{"registryId":"REGISTRY_ID"}' \ + --region +# expected: exit 0,返回 RequestId +``` + +#### 增强配置 + +指定组件版本: + +```bash +tccli tke InstallAddon \ + --ClusterId CLUSTER_ID \ + --AddonName TCR \ + --AddonVersion "1.0.0" \ + --RawValues '{"registryId":"REGISTRY_ID"}' \ + --region +# expected: exit 0,返回 RequestId +``` + +轮询确认组件安装状态(多维度): + +```bash +tccli tke DescribeAddon \ + --ClusterId CLUSTER_ID \ + --AddonName TCR \ + --region +# expected: exit 0,Addons[].AddonStatus 为 Running +``` + +> **DescribeAddon 响应行为**:该 API 有两种返回模式—— +> - **组件已安装**:返回 HTTP 200,JSON 体中含 `Addons` 数组,包含组件状态、版本等信息; +> - **组件未安装或集群不存在**:返回非 0 退出码及错误信息(如 `ResourceNotFound` 或 `UnknownParameter`)。在脚本中建议检查退出码(`$?`)区分这两种情况,而非假定必定返回空列表。 + +**预期输出**: + +```json +{ + "Addons": [ + { + "AddonName": "TCR", + "AddonVersion": "1.0.0", + "AddonStatus": "Running", + "RawValues": "{\"registryId\":\"REGISTRY_ID\"}" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 组件状态 | `Addons[].AddonStatus` | `Running`(非 `Upgrading`/`Failed`) | +| 版本信息 | `Addons[].AddonVersion` | 非空,如 `1.0.0` | +| 配置一致性 | `Addons[].RawValues` | 包含正确的 `registryId` | +| 集群就绪 | 命令退出码 | `0`(区分组件未安装时的非 0 退出码) | + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `CLUSTER_ID` | TKE 集群 ID | 格式 `cls-xxxxxxxx` | 步骤 5 中 `DescribeClusters` 返回的 `ClusterId` | + +### 步骤 9:使用 TCR 实例内容器镜像创建工作负载 + +组件安装完成后创建工作负载,无需在 Pod spec 中指定 `imagePullSecrets`。`deployment.yaml`: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: demo-app + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + app: demo-app + template: + metadata: + labels: + app: demo-app + spec: + containers: + - name: getting-started + image: REGISTRY_DOMAIN/demo-tcr/getting-started:latest +``` + +> **注意**:以下 kubectl 命令需 TKE 集群公网端点可达,未在本文档环境中实际执行。 + +```bash +kubectl apply -f deployment.yaml +# expected: deployment.apps/demo-app created +``` + +**预期输出**: + +```text +deployment.apps/demo-app created +``` + +> **重要**:请避免在工作负载中手动配置 `imagePullSecrets`,否则将导致无法加载 TCR 插件的免密拉取配置。 + +## 验证 + +### 控制面(tccli) + +验证 TCR 组件多维度正常: + +```bash +tccli tke DescribeAddon \ + --ClusterId CLUSTER_ID \ + --AddonName TCR \ + --region +# expected: exit 0,AddonStatus 为 Running +``` + +> 若组件已安装,返回 Addons 数组;若返回非 0 退出码,说明组件未安装或集群不存在。参见[步骤 8 的 DescribeAddon 响应行为说明](#步骤-8安装-tcr-组件)。 + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 组件状态 | `Addons[].AddonStatus` | `Running` | +| 配置一致 | `Addons[].RawValues` | 包含目标 `registryId` | + +验证内网访问链路多维度正常: + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0,AccessVpcSet 中包含目标 VPC +``` + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 关联状态 | `AccessVpcSet[].Status` | `Running` | +| 访问 IP | `AccessVpcSet[].EniLBIp` | 非空 | +| VPC 匹配 | `AccessVpcSet[].VpcId` | 与集群所在 VPC 一致 | +| 子网匹配 | `AccessVpcSet[].SubnetId` | 与关联时传入的 SubnetId 一致 | + +验证 TCR 实例本身运行状态: + +```bash +tccli tcr DescribeInstances \ + --Registryids '["REGISTRY_ID"]' \ + --region +# expected: exit 0,Status 为 Running +``` + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 实例状态 | `Status` | `Running` | +| 实例类型 | `RegistryType` | `premium`(或其他企业版类型) | +| 公网域名 | `PublicDomain` | 非空 | + +验证命名空间存在: + +```bash +tccli tcr DescribeNamespaces \ + --RegistryId REGISTRY_ID \ + --region +# expected: NamespaceList 中包含目标命名空间 +``` + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 命名空间存在 | `NamespaceInfoList[].NamespaceName` | 包含 `demo-tcr` | +| 公开状态 | `NamespaceInfoList[].IsPublic` | `true` | + +### 数据面(kubectl) + +> **注意**:以下 kubectl 命令需 TKE 集群公网端点可达,未在本文档环境中实际执行。 + +验证 Pod 运行状态和镜像拉取正常: + +```bash +kubectl get pods -l app=demo-app +# expected: READY 1/1, STATUS Running, RESTARTS 0 +``` + +**预期输出**: + +```text +NAME READY STATUS RESTARTS AGE +demo-app-xxxxxxxxxx-xxxxx 1/1 Running 0 60s +``` + +```bash +kubectl describe pod -l app=demo-app | grep -E "Image:|ImagePullSecrets" +# expected: 镜像地址为 TCR 域名,ImagePullSecrets 为 (免密拉取生效) +``` + +**预期输出**: + +```text + Image: REGISTRY_DOMAIN/demo-tcr/getting-started:latest + ImagePullSecrets: +``` + +确认镜像拉取成功且 `ImagePullSecrets` 为 ``,表明 TCR 插件免密拉取已生效。 + +## 清理 + +> **计费警告**:TKE 集群和 TCR 企业版实例都会持续产生费用。请及时清理不再使用的资源。本文只清理本次操作创建的资源(TCR 组件和内网关联),**不建议删除 TCR 实例及命名空间**,除非确认不再使用。 + +### 数据面(kubectl) + +> **注意**:以下 kubectl 命令需 TKE 集群公网端点可达,未在本文档环境中实际执行。 + +```bash +kubectl delete deployment demo-app +# expected: deployment.apps "demo-app" deleted +``` + +**预期输出**: + +```text +deployment.apps "demo-app" deleted +``` + +### 控制面(tccli) + +#### 1. 卸载 TCR 组件 + +> **副作用警告**:卸载 TCR 组件不会影响已运行的 Pod(镜像已拉取至本地),但新创建的 Pod 将失去免密拉取能力,可能导致 `ImagePullBackOff`。请确认集群中无新建工作负载依赖免密拉取后再执行。 + +**清理前状态检查**: + +```bash +tccli tke DescribeAddon \ + --ClusterId CLUSTER_ID \ + --AddonName TCR \ + --region +# expected: AddonStatus 为 Running,确认是目标集群中待卸载的 TCR 组件 +``` + +```bash +tccli tke DeleteAddon \ + --ClusterId CLUSTER_ID \ + --AddonName TCR \ + --region +# expected: exit 0,返回 RequestId +``` + +**验证已卸载**: + +```bash +tccli tke DescribeAddon \ + --ClusterId CLUSTER_ID \ + --AddonName TCR \ + --region +# expected: 返回非 0 退出码(组件已卸载,API 返回 ResourceNotFound)或 Addons 列表为空 +``` + +#### 2. 删除 TCR 内网域名解析 + +> **副作用警告**:删除域名解析后,TKE 集群内 Pod 将无法通过内网域名解析到 TCR 实例,可能触发 DNS 解析失败。确认无工作负载依赖内网域名后再执行。 + +**清理前状态检查**: + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId REGISTRY_ID \ + --region +# expected: 确认 AccessVpcSet 中包含待删除解析的 VPC 记录,EniLBIp 非空 +``` + +```bash +tccli tcr DeleteInternalEndpointDns \ + --InstanceId REGISTRY_ID \ + --VpcId VPC_ID \ + --EniLBIp ENI_LB_IP \ + --region +# expected: exit 0,返回 RequestId +``` + +#### 3. 移除 VPC 关联 + +> **副作用警告**:移除 VPC 关联后,该 VPC 内的资源将无法通过内网访问 TCR 实例。若该 VPC 关联了多个集群使用同一 TCR 实例,所有集群都将受影响。**此操作不可逆**,需重新执行 `ManageInternalEndpoint`(`Operation: Create`)才能恢复。 + +**清理前状态检查**: + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId REGISTRY_ID \ + --region +# expected: 确认 AccessVpcSet 中包含待移除的 VPC 记录 +``` + +`manage-internal-endpoint-delete.json`: + +```json +{ + "RegistryId": "REGISTRY_ID", + "Operation": "Delete", + "VpcId": "VPC_ID", + "SubnetId": "SUBNET_ID" +} +``` + +```bash +tccli tcr ManageInternalEndpoint \ + --cli-input-json file://manage-internal-endpoint-delete.json \ + --region +# expected: exit 0,返回 RegistryId 和 RequestId +``` + +**验证已移除**: + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId REGISTRY_ID \ + --region +# expected: AccessVpcSet 中不再包含目标 VPC 的记录 +``` + +> 不建议删除 TCR 实例及命名空间,除非确认不再使用。若确需删除实例内资源,按以下顺序操作:删除镜像仓库 → 删除命名空间 → 删除实例。 + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ManageInternalEndpoint` 返回 VPC 已关联 | `tccli tcr DescribeInternalEndpoints --RegistryId REGISTRY_ID --region ` 确认该 `VpcId` 是否已在 `AccessVpcSet` 中 | 该 VPC 已关联至当前 TCR 实例,重复创建被拒绝 | 若已存在则跳过此步;若需更换子网,先执行 `Operation: Delete` 移除旧关联再重新 `Create` | +| `CreateInternalEndpointDns` 返回 `UnsupportedOperation` | `tccli tcr DescribeInternalEndpoints --RegistryId REGISTRY_ID --region ` 确认 VPC 是否已在 `AccessVpcSet` 中且 `Status` 为 `Running` | VPC 尚未通过 `ManageInternalEndpoint` 关联至 TCR 实例,或地域不支持自动 DNS 解析 | 先执行 `ManageInternalEndpoint` 关联 VPC;若地域不支持自动解析,参见步骤 7 的手动 hosts 配置方案 | +| `docker push` 返回 `denied: requested access to the resource is denied` | `tccli tcr CreateInstanceToken --RegistryId REGISTRY_ID --TokenType longterm --region ` 确认 Token 是否有效 | 未通过 `docker login` 登录 TCR,或凭证已过期 | 重新获取 Token 后执行 `docker login`;确认客户端 IP 在 TCR 网络访问策略允许范围内 | +| `DescribeNamespaces` 返回空列表或 `ResourceNotFound` | `tccli tcr DescribeInstances --Registryids '["REGISTRY_ID"]' --region ` 确认 RegistryId 和 region 是否正确 | 传入了错误的 `RegistryId` 或 `region`,或命名空间尚未创建 | 用 `DescribeInstances` 确认实例 ID 和所在地域;确认已执行步骤 2 创建命名空间 | +| `LimitExceeded` | `tccli vpc DescribeVpcs --region ` 查看当前 VPC 配额使用情况 | 当前账号 VPC 数量已达上限,无法新建 VPC 用于内网访问链路。此为环境限制,非命令错误 | 清理不再使用的 VPC 后重试 `ManageInternalEndpoint`;或使用已有 VPC 和子网 | + +### 创建成功但状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| Pod 处于 `ImagePullBackOff`,Events 显示 `401 Unauthorized` | `tccli tcr DescribeInstances --Registryids '["REGISTRY_ID"]' --region ` 确认实例 ID;`kubectl get deployment demo-app -o yaml` 检查 `imagePullSecrets` | TCR 插件配置的 `registryId` 与实际实例不匹配,或工作负载中手动配置了 `imagePullSecrets` | 检查 `InstallAddon` 中 `RawValues` 的 `registryId` 是否与 `DescribeInstances` 返回的 `RegistryId` 一致;确认 Deployment YAML 中无 `imagePullSecrets` 字段 | +| TCR 组件状态为 `Upgrading` 或 `Failed` | `tccli tke DescribeClusters --ClusterIds '["CLUSTER_ID"]' --region ` 确认 `ClusterVersion`、`ClusterStatus` | 集群 Kubernetes 版本低于 1.14,或集群地域与 TCR 实例地域不一致 | 升级集群至 >= 1.14;确认集群地域与 TCR 实例地域一致 | +| Pod Events 显示 `UnknownHost`,无法解析 TCR 内网域名 | Pod 内执行 `nslookup REGISTRY_DOMAIN` 测试 DNS 解析 | `CreateInternalEndpointDns` 未成功创建或 VPC DNS 未生效 | 检查 `CreateInternalEndpointDns` 的 `EniLBIp` 是否正确(应为 `DescribeInternalEndpoints` 返回的 `EniLBIp` 而非 `AccessIp`);确认 `UsePublicDomain` 设为 `true` | +| 镜像拉取速度慢或无响应 | `tccli tcr DescribeInternalEndpoints --RegistryId REGISTRY_ID --region ` 确认内网链路状态 | 可能使用了公网域名拉取镜像,而非内网链路 | 确认 TCR 组件 `AddonStatus` 为 `Running`;确认内网访问链路 `Status` 为 `Running`;在 Pod 内执行 `curl -I https://REGISTRY_DOMAIN/v2/` 验证连通性 | +| 返回 ClusterId 但组件状态长时间不 Running | `tccli tke DescribeAddon --ClusterId CLUSTER_ID --AddonName TCR --region ` 持续轮询状态 | 组件安装异步进行,或集群资源不足导致安装缓慢 | 继续等待;超过 15 分钟则保留 `region`、`ClusterId`、`RequestId`、`AddonName` → 登录 [TKE 控制台](https://console.cloud.tencent.com/tke2/cluster) 查看组件安装详情 | + +## 下一步 + +- [从自建 Harbor 同步镜像到 TCR 企业版](../harbor-migration) +- [TCR 企业版实例管理 — 内网访问控制](../../ops/access/network/private-access) +- [Deployment 管理 — 创建工作负载](https://cloud.tencent.com/document/product/457/31705) +- [TCR 组件说明](https://cloud.tencent.com/document/product/457/49225) + +## 控制台替代 + +[控制台 → 容器镜像服务](https://console.cloud.tencent.com/tcr) 管理 TCR 实例的命名空间与镜像仓库;[控制台 → 容器服务](https://console.cloud.tencent.com/tke2/cluster) 进入集群后通过组件管理安装 TCR 组件,勾选"启用内网解析功能"。 diff --git a/src/content/docs/cli/tcr/practices/tke-serverless-pull-tcrctl.md b/src/content/docs/cli/tcr/practices/tke-serverless-pull-tcrctl.md new file mode 100644 index 0000000..7583f3e --- /dev/null +++ b/src/content/docs/cli/tcr/practices/tke-serverless-pull-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "" +description: "# 产品 CLI 操作" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +# 产品 CLI 操作 + +> 页型 P1 · 使用 crctl 完成本页任务 · P0 仅提供 stub 占位,P1 阶段完成正文。 + diff --git a/src/content/docs/cli/tcr/practices/tke-serverless-pull.md b/src/content/docs/cli/tcr/practices/tke-serverless-pull.md new file mode 100644 index 0000000..d05c77e --- /dev/null +++ b/src/content/docs/cli/tcr/practices/tke-serverless-pull.md @@ -0,0 +1,976 @@ +--- +title: "TKE Serverless 集群拉取 TCR 容器镜像" +description: "· page_id `59029`" +--- + +> 对照官方:[TKE Serverless 集群拉取 TCR 容器镜像](https://cloud.tencent.com/document/product/1141/59029) · page_id `59029` + +## 概述 + +在 TKE Serverless(弹性容器实例 EKS)集群中拉取 TCR 企业版实例内的私有容器镜像,并创建工作负载。 + +整体链路分为三层:(1) 将镜像推送至 TCR 企业版(docker,数据面);(2) 打通 TKE Serverless 集群与 TCR 实例间的内网访问链路(tccli,控制面);(3) 在集群中创建引用 TCR 镜像的工作负载(kubectl,数据面)。 + +> **与 TKE 标准集群的关键区别:** TKE Serverless 集群**不支持** TCR 插件免密拉取。必须手动创建 `imagePullSecret` 并在工作负载 `spec.template.spec.imagePullSecrets` 中显式引用。标准 TKE 集群可通过安装 TCR 插件实现免密拉取,参见 [TKE 集群使用 TCR 插件内网免密拉取容器镜像](https://cloud.tencent.com/document/product/1141/48184)。 + +## 前置条件 + +- [环境准备](../../../index.md) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域 + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tcr:DescribeInstances, tcr:CreateNamespace, tcr:DescribeNamespaces, +# tcr:CreateRepository, tcr:DescribeRepositories, +# tcr:ManageInternalEndpoint, tcr:CreateInternalEndpointDns, +# tcr:DescribeInternalEndpoints, tcr:DescribeInternalEndpointDnsStatus, +# tcr:CreateInstanceToken, tcr:DescribeInstanceToken, tcr:DeleteInstanceToken, +# tke:DescribeClusters +# 建议授予 QcloudTCRFullAccess 预设策略 +# 验证:执行 DescribeInstances 确认 TCR 权限 +tccli tcr DescribeInstances --region +# expected: exit 0,返回实例列表(可为空) + +# 验证 TKE 权限 +tccli tke DescribeClusters --region +# expected: exit 0,返回集群列表(可为空) + +# 4. 检查 Docker 客户端 +docker --version +# expected: Docker version 20.10+ + +# 5. 检查 kubectl 并可连接到 TKE Serverless 集群 +kubectl cluster-info +# expected: Kubernetes control plane 可访问 +``` + +### 资源检查 + +```bash +# 6. 确认 TCR 企业版实例存在且状态正常 +tccli tcr DescribeInstances --region --Registryids '["REGISTRY_ID"]' +# expected: exit 0, Registries[0].Status == "Running" + +# 7. 确认 TKE Serverless 集群存在 +tccli tke DescribeClusters --region --ClusterIds '["CLUSTER_ID"]' +# expected: exit 0, Clusters[0].ClusterType 含 "SERVERLESS" 或 "EKS" +``` + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 查看 TCR 实例列表 | `DescribeInstances` | 是 | +| 创建命名空间 | `CreateNamespace` | 否(同名报错) | +| 查看命名空间列表 | `DescribeNamespaces` | 是 | +| 创建镜像仓库(可选) | `CreateRepository` | 否(同名报错) | +| 查看仓库列表 | `DescribeRepositories` | 是 | +| 创建长期访问凭证 | `CreateInstanceToken` | 否 | +| 管理内网访问链路 | `ManageInternalEndpoint` | 否(同 VPC 重复创建报错) | +| 查看内网访问链路 | `DescribeInternalEndpoints` | 是 | +| 创建内网 DNS 解析 | `CreateInternalEndpointDns` | 否 | +| 查看内网 DNS 状态 | `DescribeInternalEndpointDnsStatus` | 是 | + +## 关键字段说明 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | TCR 实例 ID,由 `DescribeInstances` 返回 | `InvalidParameter.RegistryNotFound` — 实例不存在或地域错误 | +| `Region` | String | 是 | 地域名,如 `ap-guangzhou`、`ap-shanghai` | `InvalidParameter` — 地域与实例不匹配,命令无法路由 | +| `NamespaceName` | String | 是 | 1-63 字符,小写字母/数字/下划线/连字符,不能以连字符开头结尾 | `InvalidParameter` — 命名空间名格式不合法 | +| `IsPublic` | Boolean | 否 | `true` 公有,`false` 私有(默认 `false`) | 误设为 `true` 导致镜像被意外公开访问 | +| `RepositoryName` | String | 是 | 1-255 字符,小写字母/数字/下划线/连字符/点 | `InvalidParameter` — 仓库名格式不合法 | +| `BriefDescription` | String | 否 | 最多 100 字符,仓库简要描述 | 无严重后果,仅影响仓库列表可读性 | +| `TokenType` | String | 是 | `longterm`(长期凭证,无过期)或 `temp`(临时凭证,有过期时间) | 选 `temp` 导致凭证过期后 Pod 拉取镜像失败,需重建 Secret | +| `Desc` | String | 否 | 凭证描述,最多 255 字符 | 无严重后果,仅影响凭证可识别性 | +| `VpcId` | String | 是 | 已有 VPC ID,格式 `vpc-xxxxxxxx`,须为 TKE Serverless 集群所在 VPC | `FailedOperation` — VPC 不存在或不可用 | +| `SubnetId` | String | 是 | VPC 下已有子网 ID,格式 `subnet-xxxxxxxx`,须有空闲 IP | `FailedOperation` — 子网不存在或 IP 耗尽 | +| `RegionId` | Integer | 否 | 地域数字 ID(如广州=1、上海=4),部分接口自动推导 | `InvalidParameter` — RegionId 与 RegionName 不匹配 | +| `RegionName` | String | 否 | 地域名称,须与 `--region` 一致(如 `ap-guangzhou`) | `InvalidParameter` — 地域信息不一致 | +| `EniLBIp` | String | 是 | ENI LB IP 地址。`EniLBIp` 为 `CreateInternalEndpointDns` 的输入参数名,`AccessIp` 为 `DescribeInternalEndpoints` 的输出字段名,二者为同一值 | `InvalidParameter` — IP 地址无效,DNS 解析指向错误目标 | +| `UsePublicDomain` | Boolean | 否 | `true` 使用公网域名(`tencentcloudcr.com`)做内网解析;`false` 使用专有域名 | DNS 解析目标不一致,Pod 无法通过内网访问 TCR | +| `InstanceId` | String | 是 | TCR 实例 ID(同 `RegistryId`),用于 `CreateInternalEndpointDns` | `ResourceNotFound` — 实例不存在 | +| `Operation` | String | 是 | `"Create"` 或 `"Delete"`,大小写敏感 | `InvalidParameter` — 操作类型未知 | + +## 操作步骤 + +### 步骤 1:准备容器镜像(控制面 + 数据面) + +#### 1.1 查看 TCR 实例状态 + +确认企业版实例处于可用状态: + +```bash +tccli tcr DescribeInstances \ + --Registryids '["REGISTRY_ID"]' \ + --region +# expected: exit 0, Registries[0].Status == "Running" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "example.tencentcloudcr.com" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +| 占位符 | 对应关键字段 | 获取方式 | +|--------|------------|---------| +| `REGISTRY_ID` | `RegistryId` | `tccli tcr DescribeInstances --region ` | +| `REGION` | `Region` | `tccli configure list` 查看已配置地域 | + +记录 `RegistryName`(后续 docker 登录用)和 `RegistryType`(`basic` 或 `premium`,影响部分操作限制)。 + +#### 1.2 创建命名空间 + +##### 选择依据 + +- **IsPublic**:选择 `false`(私有),因为本场景拉取的是私有容器镜像,设为 `true` 会导致镜像被意外公开访问。默认为 `false`,省略此参数效果相同。 +- **NamespaceName**:建议使用项目名或团队名作为命名空间前缀,便于在 `DescribeNamespaces` 中按组织维度管理。 + +新建的 TCR 企业版实例内无默认命名空间,需手动创建: + +```bash +tccli tcr CreateNamespace \ + --RegistryId REGISTRY_ID \ + --NamespaceName NAMESPACE_NAME \ + --IsPublic false \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +| 占位符 | 对应关键字段 | 获取方式 | +|--------|------------|---------| +| `REGISTRY_ID` | `RegistryId` | 步骤 1.1 `DescribeInstances` 返回 | +| `NAMESPACE_NAME` | `NamespaceName` | 自定义,如 `kerwinwjyan-test` | +| `REGION` | `Region` | `tccli configure list` 查看 | + +验证: + +```bash +tccli tcr DescribeNamespaces \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, NamespaceList 包含 NAMESPACE_NAME, Public == false +``` + +**预期输出**: + +```json +{ + "NamespaceList": [ + { + "Name": "kerwinwjyan-test", + "NamespaceId": 2, + "Public": false + } + ], + "TotalCount": 1, + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +#### 1.3 创建镜像仓库(可选) + +##### 选择依据 + +- **可选执行**:`docker push` 时若目标仓库不存在,TCR 会**自动创建**,无需提前手动执行。仅在需要预设 `BriefDescription` 或严格控制仓库名的场景下才手动创建。 +- **RepositoryName**:建议与镜像名一致,避免 `docker tag` 时路径混淆。 +- **BriefDescription**:可选但建议填写,便于在 `DescribeRepositories` 返回的仓库列表中快速识别用途。 + +**最小创建**(仅必填字段,参数 ≤3 个,无需 JSON 文件): + +```bash +tccli tcr CreateRepository \ + --RegistryId REGISTRY_ID \ + --NamespaceName NAMESPACE_NAME \ + --RepositoryName REPO_NAME \ + --region +# expected: exit 0 +``` + +**增强配置**(含可选字段): + +```bash +tccli tcr CreateRepository \ + --RegistryId REGISTRY_ID \ + --NamespaceName NAMESPACE_NAME \ + --RepositoryName REPO_NAME \ + --BriefDescription "仓库描述" \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +| 占位符 | 对应关键字段 | 获取方式 | +|--------|------------|---------| +| `REGISTRY_ID` | `RegistryId` | 步骤 1.1 `DescribeInstances` 返回 | +| `NAMESPACE_NAME` | `NamespaceName` | 步骤 1.2 创建的命名空间 | +| `REPO_NAME` | `RepositoryName` | 自定义,如 `nginx` | +| `REGION` | `Region` | `tccli configure list` 查看 | + +验证: + +```bash +tccli tcr DescribeRepositories \ + --RegistryId REGISTRY_ID \ + --NamespaceName NAMESPACE_NAME \ + --region +# expected: exit 0, RepositoryList 包含 REPOSITORY_NAME +``` + +**预期输出**: + +```json +{ + "RepositoryList": [ + { + "Name": "NAMESPACE_NAME/REPO_NAME", + "Namespace": "NAMESPACE_NAME", + "Public": true + } + ], + "TotalCount": 1, + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +#### 1.4 创建访问凭证并推送镜像至 TCR(数据面) + +##### 选择依据 + +- **TokenType**:选择 `longterm` 而非 `temp`,因为 TKE Serverless 工作负载需要持久有效的镜像拉取凭证。`temp` 凭证有过期时间,过期后需重建 `imagePullSecret` 并滚动更新所有引用该 Secret 的工作负载,运维成本高。 +- **Desc**:建议填写可识别的描述(如 "EKS 拉取专用"),方便通过 `DescribeInstanceToken` 识别凭证用途,便于后续轮换时精准定位。 +- **专用凭证**:建议为 EKS 拉取场景创建专用长期凭证,而非复用控制台临时凭证,以实现权限隔离和独立轮换。 + +创建长期访问凭证: + +```bash +tccli tcr CreateInstanceToken \ + --RegistryId REGISTRY_ID \ + --TokenType longterm \ + --Desc "EKS 拉取专用" \ + --region +# expected: exit 0, 返回 Username 和 Token +``` + +**预期输出**: + +```json +{ + "Username": "100012345678", + "Token": "d8prmfsth9mroppu4740", + "TokenId": "token-example", + "ExpTime": 1893456000000, + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +> `Token` 字段**仅此一次返回**,请妥善保存。 + +| 占位符 | 对应关键字段 | 获取方式 | +|--------|------------|---------| +| `REGISTRY_ID` | `RegistryId` | 步骤 1.1 `DescribeInstances` 返回 | +| `REGION` | `Region` | `tccli configure list` 查看 | + +确认凭证已就绪: + +```bash +tccli tcr DescribeInstanceToken \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, Tokens 列表含目标凭证且 Enabled == true +``` + +**预期输出**: + +```json +{ + "Tokens": [ + { + "Id": "token-example", + "Desc": "EKS 拉取专用", + "Enabled": true + } + ], + "TotalCount": 1, + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +登录并推送镜像: + +```bash +docker login REGISTRY_NAME.tencentcloudcr.com \ + --username=USERNAME \ + --password=TOKEN +``` + +**预期输出**: + +```text +Login Succeeded +``` + +```bash +docker tag LOCAL_IMAGE:TAG \ + REGISTRY_NAME.tencentcloudcr.com/NAMESPACE_NAME/REPO_NAME:TAG + +docker push REGISTRY_NAME.tencentcloudcr.com/NAMESPACE_NAME/REPO_NAME:TAG +``` + +**预期输出**: + +```text +The push refers to repository [REGISTRY_NAME.tencentcloudcr.com/NAMESPACE_NAME/REPO_NAME] +sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: Pushed +TAG: digest: sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx size: 1234 +``` + +| 占位符 | 说明 | 获取方式 | +|--------|------|---------| +| `REGISTRY_NAME` | TCR 实例名称 | 步骤 1.1 `DescribeInstances` 返回的 `RegistryName` | +| `USERNAME` | 访问凭证用户名 | 步骤 1.4 `CreateInstanceToken` 返回的 `Username` | +| `TOKEN` | 访问凭证 Token | 步骤 1.4 `CreateInstanceToken` 返回的 `Token` | +| `NAMESPACE_NAME` | 命名空间名 | 步骤 1.2 创建的命名空间 | +| `REPO_NAME` | 仓库名 | 步骤 1.3 创建的仓库名 | +| `LOCAL_IMAGE` | 本地已有镜像名 | `docker images` 查看 | +| `TAG` | 镜像标签 | 自定义,如 `latest`、`v1.0` | + +### 步骤 2:配置 TKE Serverless 集群访问 TCR 实例(控制面) + +TCR 企业版默认拒绝全部来源的 VPC 访问。需将 TKE Serverless 集群所在 VPC 关联至 TCR 实例并配置内网域名解析。 + +> 若 TKE Serverless 集群与 TCR 实例在同一地域,建议使用内网访问(免公网流量)。若需通过公网,参见[通过 NAT 网关访问外网](https://cloud.tencent.com/document/product/457/48710)(需开启 `ManageExternalEndpoint`)。 + +#### 2.1 获取 TKE Serverless 集群所在 VPC + +```bash +tccli tke DescribeClusters \ + --ClusterIds '["CLUSTER_ID"]' \ + --region +# expected: exit 0, Clusters[0].ClusterType 含 SERVERLESS/EKS +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Clusters": [ + { + "ClusterId": "cls-example", + "ClusterName": "eks-cluster", + "ClusterType": "SERVERLESS_CLUSTER", + "ClusterStatus": "Running", + "ClusterNetworkSettings": { + "VpcId": "vpc-example", + "SubnetIds": ["subnet-example"] + } + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +| 占位符 | 说明 | 获取方式 | +|--------|------|---------| +| `CLUSTER_ID` | TKE Serverless 集群 ID | 控制台或 `tccli tke DescribeClusters --region ` | +| `REGION` | 地域 | `tccli configure list` 查看 | + +从返回的 `Clusters[0].ClusterNetworkSettings.VpcId` 获取 VPC ID,记录 `VPC_ID` 和对应的 `SUBNET_ID`。 + +#### 2.2 在 TCR 实例中关联集群 VPC + +##### 选择依据 + +- **VpcId**:必须严格是 TKE Serverless 集群所在的 VPC(由步骤 2.1 获取),否则集群内 Pod 无法通过内网访问 TCR 实例。 +- **SubnetId**:选择同一 VPC 下有空闲 IP 的子网。若子网 IP 耗尽,ENI 分配失败,`ManageInternalEndpoint` 将报错。 +- **Operation=Create**:本场景是新建内网访问链路。`ManageInternalEndpoint` 同一 VPC 重复创建会报错,非幂等。 +- **RegionId / RegionName**:部分地域需显式提供,部分可自动推导。若省略后命令正常执行则无需填写;若报 `InvalidParameter` 再补充。 + +参数 >= 4 个,使用 `--cli-input-json file://`,分最小/增强两层。 + +**最小配置**(仅必填字段): + +`tcr-manage-internal-endpoint-minimal.json`: + +```json +{ + "RegistryId": "REGISTRY_ID", + "Operation": "Create", + "VpcId": "VPC_ID", + "SubnetId": "SUBNET_ID" +} +``` + +**增强配置**(含地域信息,当最小配置报 `InvalidParameter` 时使用): + +`tcr-manage-internal-endpoint-enhanced.json`: + +```json +{ + "RegistryId": "REGISTRY_ID", + "Operation": "Create", + "VpcId": "VPC_ID", + "SubnetId": "SUBNET_ID", + "RegionId": 1, + "RegionName": "ap-guangzhou" +} +``` + +```bash +# 执行创建(优先使用 minimal,若报 InvalidParameter 则改用 enhanced) +tccli tcr ManageInternalEndpoint \ + --cli-input-json file://tcr-manage-internal-endpoint-minimal.json \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RegistryId": "REGISTRY_ID", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +| 占位符 | 对应关键字段 | 获取方式 | +|--------|------------|---------| +| `REGISTRY_ID` | `RegistryId` | 步骤 1.1 `DescribeInstances` 返回 | +| `VPC_ID` | `VpcId` | 步骤 2.1 `DescribeClusters` 返回 `ClusterNetworkSettings.VpcId` | +| `SUBNET_ID` | `SubnetId` | 步骤 2.1 `DescribeClusters` 返回的 `SubnetIds[0]` | +| `REGION` | `Region` | `tccli configure list` 查看 | + +> **注意:** 若当前账号 VPC 数量已达上限(`LimitExceeded`),`ManageInternalEndpoint` 将失败。此为环境限制,非命令错误。需先清理不再使用的 VPC 后重试。正常环境下,VPC 配额充足可正常执行。 + +异步轮询等待链路就绪: + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, AccessVpcSet 不为 null +``` + +**预期输出**: + +```json +{ + "AccessVpcSet": [ + { + "VpcId": "vpc-of73262z", + "SubnetId": "subnet-rdmcho9m", + "AccessIp": "10.0.0.100", + "Status": "Running" + } + ], + "TotalCount": 1, + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +> 若内网链路尚未创建,`AccessVpcSet` 为 `null`、`TotalCount` 为 `0`。执行 `ManageInternalEndpoint` 创建链路后重试。 + +记录 `AccessVpcSet[0].AccessIp` 值(即 `ENI_LB_IP`),后续步骤 2.3 需要。 + +##### 多维度验证 + +创建内网链路是异步操作,仅检查 `Status=Running` 不够,需多维度确认: + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 状态 | `AccessVpcSet[i].Status` | `Running` | +| 网络 | `AccessVpcSet[i].AccessIp` | 非空有效 IP 地址 | +| VPC 关联 | `AccessVpcSet[i].VpcId` | 等于目标 `VPC_ID` | +| 子网关联 | `AccessVpcSet[i].SubnetId` | 等于目标 `SUBNET_ID` | + +#### 2.3 配置域名内网解析 + +内网访问链路创建完成后,配置内网 DNS 解析使 VPC 内 Pod 可通过内网解析 TCR 域名。 + +##### 选择依据 + +- **UsePublicDomain**:推荐设为 `true`,使用 `tencentcloudcr.com` 公网域名进行内网解析。这样内网与公网使用同一域名,避免 DNS 分裂导致配置不一致。若设为 `false`,将使用专有内网域名,需同时修改工作负载中的镜像地址。 +- **EniLBIp**:必须严格来自步骤 2.2 `DescribeInternalEndpoints` 返回的 `AccessVpcSet[0].AccessIp`。任意填写会导致 DNS 解析指向错误 IP,Pod 无法拉取镜像。 +- **前置依赖**:本步骤依赖步骤 2.2 `ManageInternalEndpoint` 成功创建链路并提供 `AccessIp`。 + +参数 >= 4 个,使用 `--cli-input-json file://`,分最小/增强两层。 + +**最小配置**(仅必填字段): + +`tcr-create-internal-dns-minimal.json`: + +```json +{ + "InstanceId": "REGISTRY_ID", + "VpcId": "VPC_ID", + "EniLBIp": "ENI_LB_IP" +} +``` + +**增强配置**(含可选字段,推荐): + +`tcr-create-internal-dns-enhanced.json`: + +```json +{ + "InstanceId": "REGISTRY_ID", + "VpcId": "VPC_ID", + "EniLBIp": "ENI_LB_IP", + "UsePublicDomain": true, + "RegionName": "ap-guangzhou" +} +``` + +```bash +# 执行创建(推荐使用 enhanced) +tccli tcr CreateInternalEndpointDns \ + --cli-input-json file://tcr-create-internal-dns-enhanced.json \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +| 占位符 | 对应关键字段 | 获取方式 | +|--------|------------|---------| +| `REGISTRY_ID` | `RegistryId` / `InstanceId` | 步骤 1.1 `DescribeInstances` 返回 | +| `VPC_ID` | `VpcId` | 步骤 2.1 `DescribeClusters` 返回 | +| `ENI_LB_IP` | `EniLBIp` | 步骤 2.2 `DescribeInternalEndpoints` 返回 `AccessVpcSet[0].AccessIp` | +| `REGION` | `Region` | `tccli configure list` 查看 | + +> **注意:** 若 `ManageInternalEndpoint`(步骤 2.2)未执行或失败,则 `CreateInternalEndpointDns` 无可用 `ENI_LB_IP`,本步骤无法执行。需先确保步骤 2.2 成功。 + +验证内网 DNS 解析状态: + +```bash +tccli tcr DescribeInternalEndpointDnsStatus \ + --VpcSet '[{"InstanceId":"REGISTRY_ID","VpcId":"VPC_ID","EniLBIp":"ENI_LB_IP","UsePublicDomain":true}]' \ + --region +# expected: exit 0, VpcSet[0].Status == "ENABLED" +``` + +**预期输出**: + +```json +{ + "VpcSet": [ + { + "Status": "ENABLED" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +### 步骤 3:使用 TCR 镜像在 TKE Serverless 集群创建工作负载(数据面) + +> **需 EKS 集群:** 以下 kubectl 操作依赖已存在的 TKE Serverless(EKS)集群。若尚无可用集群,请先通过 [TKE 控制台](https://console.cloud.tencent.com/tke2/ecluster) 创建,或使用标准 TKE 集群 + TCR 插件方案(参见 [TKE 集群使用 TCR 插件内网免密拉取容器镜像](https://cloud.tencent.com/document/product/1141/48184))。 + +#### 3.1 创建 imagePullSecret + +TKE Serverless 集群**不支持** TCR 插件免密拉取,必须手动创建 `imagePullSecret`: + +```bash +kubectl create secret docker-registry image-pull-secret \ + --docker-server=REGISTRY_NAME.tencentcloudcr.com \ + --docker-username=USERNAME \ + --docker-password=TOKEN \ + --namespace=K8S_NAMESPACE +``` + +**预期输出**: + +```text +secret/image-pull-secret created +``` + +| 占位符 | 说明 | 获取方式 | +|--------|------|---------| +| `REGISTRY_NAME` | TCR 实例名称 | 步骤 1.1 `DescribeInstances` 返回的 `RegistryName` | +| `USERNAME` | 访问凭证用户名 | 步骤 1.4 `CreateInstanceToken` 返回的 `Username` | +| `TOKEN` | 访问凭证 Token | 步骤 1.4 `CreateInstanceToken` 返回的 `Token` | +| `K8S_NAMESPACE` | K8s 命名空间 | 自定义或使用已有 K8s 命名空间 | + +验证 Secret 已创建: + +```bash +kubectl get secret image-pull-secret -n K8S_NAMESPACE -o yaml +# expected: .data 字段含 .dockerconfigjson,非空 +``` + +#### 3.2 创建引用 TCR 镜像的工作负载 + +创建 Deployment 清单 `eks-deployment.yaml`: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: DEPLOYMENT_NAME + namespace: K8S_NAMESPACE +spec: + replicas: 1 + selector: + matchLabels: + app: APP_NAME + template: + metadata: + labels: + app: APP_NAME + spec: + imagePullSecrets: + - name: image-pull-secret + containers: + - name: CONTAINER_NAME + image: REGISTRY_NAME.tencentcloudcr.com/NAMESPACE_NAME/REPO_NAME:TAG + ports: + - containerPort: 80 +``` + +```bash +kubectl apply -f eks-deployment.yaml +``` + +**预期输出**: + +```text +deployment.apps/DEPLOYMENT_NAME created +``` + +| 占位符 | 说明 | 获取方式 | +|--------|------|---------| +| `DEPLOYMENT_NAME` | Deployment 名称 | 自定义 | +| `K8S_NAMESPACE` | K8s 命名空间 | 与步骤 3.1 一致 | +| `APP_NAME` | 应用标签名 | 自定义,如 `nginx` | +| `CONTAINER_NAME` | 容器名 | 自定义,如 `nginx` | +| `REGISTRY_NAME` | TCR 实例名称 | 步骤 1.1 `DescribeInstances` 返回 | +| `NAMESPACE_NAME` | TCR 命名空间 | 步骤 1.2 创建的命名空间 | +| `REPO_NAME` | TCR 仓库名 | 步骤 1.3 创建的仓库名 | +| `TAG` | 镜像标签 | 与 docker push 使用的标签一致 | + +> **关键提醒:** `spec.template.spec.imagePullSecrets` 在 TKE Serverless 中必须显式配置。Serverless 集群不支持 TCR 插件自动注入,若遗漏此字段 Pod 将因鉴权失败而 `ImagePullBackOff`。 + +#### 3.3 验证工作负载状态 + +```bash +kubectl get pods -n K8S_NAMESPACE -l app=APP_NAME +# expected: STATUS Running, READY 1/1 +``` + +**预期输出**: + +```text +NAME READY STATUS RESTARTS AGE +DEPLOYMENT_NAME-xxxxxxxxxx-xxxxx 1/1 Running 0 30s +``` + +```bash +kubectl describe pod -n K8S_NAMESPACE -l app=APP_NAME +``` + +确认 Events 中无 `Failed to pull image`、`ImagePullBackOff` 或 `ErrImagePull` 错误。 + +## 验证 + +### 控制面(tccli) + +```bash +# TCR 实例已就绪 +tccli tcr DescribeInstances \ + --Registryids '["REGISTRY_ID"]' \ + --region +# expected: exit 0, Registries[0].Status == "Running" +``` + +```bash +# 命名空间已创建 +tccli tcr DescribeNamespaces \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, NamespaceList 包含 NAMESPACE_NAME +``` + +```bash +# 访问凭证已就绪 +tccli tcr DescribeInstanceToken \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, Tokens 列表含目标凭证且 Enabled == true +``` + +```bash +# 内网访问链路已就绪 +tccli tcr DescribeInternalEndpoints \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, AccessVpcSet 包含目标 VPC,Status == "Running" +``` + +```bash +# 内网 DNS 已就绪 +tccli tcr DescribeInternalEndpointDnsStatus \ + --VpcSet '[{"InstanceId":"REGISTRY_ID","VpcId":"VPC_ID","EniLBIp":"ENI_LB_IP","UsePublicDomain":true}]' \ + --region +# expected: exit 0, VpcSet[0].Status == "ENABLED" +``` + +### 数据面(kubectl / docker) + +```bash +# 确认 Secret 存在 +kubectl get secret image-pull-secret -n K8S_NAMESPACE -o yaml +# expected: .data 含 .dockerconfigjson +``` + +```bash +# 确认 Pod 运行正常 +kubectl get pods -n K8S_NAMESPACE -l app=APP_NAME +# expected: STATUS Running, READY 1/1 +``` + +```bash +# 确认镜像可拉取(在可访问 TCR 的客户端上) +docker pull REGISTRY_NAME.tencentcloudcr.com/NAMESPACE_NAME/REPO_NAME:TAG +# expected: 拉取成功,返回 Downloaded +``` + +## 清理 + +> **计费警告**:TKE Serverless Pod 按 vCPU/内存按秒计费,删除工作负载后停止计费。TCR 企业版实例按规格持续收费,若不再使用请参考[销毁企业版实例](../../ops/instances/delete)释放。 + +### 数据面(kubectl) + +数据面资源需在控制面资源之前清理。 + +**删除工作负载:** + +> **副作用警告**:`kubectl delete deployment` 会级联删除该 Deployment 管理的所有 Pod。若为生产环境,请确保已迁移流量或做好备份。 + +清理前确认: + +```bash +kubectl get deployment DEPLOYMENT_NAME -n K8S_NAMESPACE +# expected: 确认 Deployment 存在且为待删目标 +``` + +```bash +kubectl delete deployment DEPLOYMENT_NAME -n K8S_NAMESPACE +``` + +**预期输出**: + +```text +deployment.apps "DEPLOYMENT_NAME" deleted +``` + +验证已删除: + +```bash +kubectl get deployment DEPLOYMENT_NAME -n K8S_NAMESPACE +# expected: Error from server (NotFound) +``` + +**删除 imagePullSecret:** + +> **副作用警告**:删除 Secret 后,任何引用此 Secret 的 Deployment 在 Pod 重启或重新调度时将因鉴权失败而 `ImagePullBackOff`。请确保已先删除所有引用此 Secret 的工作负载。 + +清理前确认: + +```bash +kubectl get secret image-pull-secret -n K8S_NAMESPACE +# expected: 确认 Secret 存在且为待删目标 +``` + +```bash +kubectl delete secret image-pull-secret -n K8S_NAMESPACE +``` + +**预期输出**: + +```text +secret "image-pull-secret" deleted +``` + +验证已删除: + +```bash +kubectl get secret image-pull-secret -n K8S_NAMESPACE +# expected: Error from server (NotFound) +``` + +**登出 TCR(如有 docker 登录):** + +```bash +docker logout REGISTRY_NAME.tencentcloudcr.com +``` + +### 控制面(tccli) + +**删除内网访问链路:** + +> **副作用警告**:删除内网访问链路后,VPC 内所有服务将无法通过内网访问 TCR 实例。关联的内网 DNS 解析记录也将失效。若集群中仍有运行中的工作负载依赖此链路拉取镜像,Pod 重启后将出现 `ImagePullBackOff`。 + +清理前确认链路存在: + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, 确认 AccessVpcSet 包含目标 VPC +``` + +`tcr-manage-internal-endpoint-delete.json`: + +```json +{ + "RegistryId": "REGISTRY_ID", + "Operation": "Delete", + "VpcId": "VPC_ID", + "SubnetId": "SUBNET_ID" +} +``` + +```bash +tccli tcr ManageInternalEndpoint \ + --cli-input-json file://tcr-manage-internal-endpoint-delete.json \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RegistryId": "REGISTRY_ID", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +验证已删除: + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, AccessVpcSet 为 null 或 TotalCount == 0 +``` + +**预期输出**: + +```json +{ + "AccessVpcSet": null, + "TotalCount": 0, + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +**删除访问凭证(可选,若仅需清理凭证而保留命名空间和仓库):** + +> **副作用警告**:删除凭证后,所有依赖此凭证的 `imagePullSecret` 将立即失效,对应工作负载在重启或重新调度时将出现 `ImagePullBackOff`。请确保已先删除所有引用此凭证的工作负载和 Secret。 + +清理前确认凭证存在: + +```bash +tccli tcr DescribeInstanceToken \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, 记录待删凭证的 TokenId +``` + +```bash +tccli tcr DeleteInstanceToken \ + --RegistryId REGISTRY_ID \ + --TokenId TOKEN_ID \ + --region +# expected: exit 0 +``` + +验证已删除: + +```bash +tccli tcr DescribeInstanceToken \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, Tokens 列表中不再包含已删凭证 +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ManageInternalEndpoint` 报 `LimitExceeded` | `tccli vpc DescribeVpcs --region ` 统计已有 VPC 数量 | 账号 VPC 配额已满,无法添加新 VPC 关联 | 此为环境限制,非命令错误。清理不再使用的 VPC 后重试:`tccli vpc DeleteVpc --VpcId VPC_ID`;或改用已有 VPC 关联 | +| `ManageInternalEndpoint` 报 `FailedOperation` | `tccli vpc DescribeVpcs --VpcIds '["VPC_ID"]' --region ` 和 `tccli vpc DescribeSubnets --SubnetIds '["SUBNET_ID"]' --region ` 确认 VPC 和子网均存在 | VPC ID 或子网 ID 不存在 / 不可用 / 子网 IP 耗尽 | 修正 `VpcId` 和 `SubnetId` 为有效值,确保子网有空闲 IP | +| `CreateInternalEndpointDns` 报 `InvalidParameter` | `tccli tcr DescribeInternalEndpoints --RegistryId REGISTRY_ID --region ` 确认 `AccessIp` 与返回值一致 | `EniLBIp` 填写错误或 `RegionName` 与实例地域不一致 | 从 `DescribeInternalEndpoints` 返回的 `AccessVpcSet[0].AccessIp` 获取 IP 值,填入 `EniLBIp` 参数;`RegionName` 与 `--region` 保持一致 | +| docker push 报 `unauthorized` | `tccli tcr DescribeInstanceToken --RegistryId REGISTRY_ID --region ` 确认凭证 `Enabled == true` | 未登录或凭证过期/无效 | `tccli tcr CreateInstanceToken --RegistryId REGISTRY_ID --TokenType longterm --Desc "push-token" --region ` 生成新凭证后 `docker login` 重新登录 | +| `VPC LimitExceeded` 且无法释放 VPC | `tccli vpc DescribeVpcs --region ` 确认所有 VPC 均为生产在用 | 当前账号 VPC 配额已达上限且无可用 VPC 可释放 | 此为**环境限制**(非命令错误)。联系售后提升配额,或改用公网入口:`ManageExternalEndpoint` + `CreateSecurityPolicy` | +| `CamNoAuth` 或权限拒绝 | `tccli tcr DescribeInstances --region ` 确认当前凭证有 TCR 读权限 | 子账号缺少所需 CAM 权限 | 此为环境限制,非命令错误。授予 `QcloudTCRFullAccess` 预设策略,或参考[企业版授权方案示例](https://cloud.tencent.com/document/product/1141/41417) | + +### 创建成功但状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| Pod 报 `ImagePullBackOff` | `kubectl get secret image-pull-secret -n K8S_NAMESPACE` 确认 Secret 存在;`kubectl describe deployment DEPLOYMENT_NAME -n K8S_NAMESPACE` 检查 `spec.template.spec.imagePullSecrets`;`tccli tcr DescribeInstanceToken --RegistryId REGISTRY_ID --region ` 确认凭证 `Enabled == true` | 未配置 `imagePullSecret` 或凭证无效/过期 | 确保 Secret 存在且 Deployment 的 `imagePullSecrets` 引用正确;若凭证过期,重新创建凭证并更新 Secret | +| Pod 报 `ErrImagePull` | `tccli tcr DescribeRepositories --RegistryId REGISTRY_ID --NamespaceName NAMESPACE_NAME --region ` 确认仓库存在 | 镜像地址格式错误或仓库/镜像不存在 | 确认格式:`REGISTRY_NAME.tencentcloudcr.com/NAMESPACE_NAME/REPO_NAME:TAG`;检查命名空间、仓库名和 Tag 均正确 | +| 内网 DNS 解析状态非 `ENABLED` | `tccli tcr DescribeInternalEndpointDnsStatus --VpcSet '[{"InstanceId":"REGISTRY_ID","VpcId":"VPC_ID","EniLBIp":"ENI_LB_IP","UsePublicDomain":true}]' --region ` 查看详细状态 | DNS 配置参数错误(`EniLBIp` 不匹配)或 VPC 内 DNS 服务未就绪 | 对比 `EniLBIp` 与 `DescribeInternalEndpoints` 返回的 `AccessVpcSet[0].AccessIp` 是否一致;等待 1-2 分钟后重试,若持续非 `ENABLED` 联系售后 | +| `DescribeInternalEndpoints` 返回 `TotalCount: 0` | `tccli tcr DescribeInternalEndpoints --RegistryId REGISTRY_ID --region ` 确认返回值 | 尚未创建任何内网访问链路 | 执行步骤 2.2 `ManageInternalEndpoint`(需 VPC 配额充足) | +| `CreateInstanceToken` 返回的 `Token` 丢失 | 无(Token 仅返回一次,API 不可恢复) | 创建时未妥善保存 Token 值 | `tccli tcr DeleteInstanceToken --RegistryId REGISTRY_ID --TokenId TOKEN_ID --region ` 删除旧凭证后重新 `CreateInstanceToken`,并立即保存 Token 值 | +| 工作负载正常运行但重启后 `ImagePullBackOff` | `tccli tcr DescribeInstanceToken --RegistryId REGISTRY_ID --region ` 确认凭证 `Enabled == true` | 凭证被删除或禁用,Secret 中的旧 Token 不再有效 | 重新创建凭证并更新 `imagePullSecret`:`kubectl delete secret image-pull-secret -n K8S_NAMESPACE` 后 `kubectl create secret docker-registry` 使用新 Token | + +## 下一步 + +- [TKE 集群使用 TCR 插件内网免密拉取容器镜像](https://cloud.tencent.com/document/product/1141/48184) — 标准 TKE 集群免密方案(page_id `48184`) +- [从自建 Harbor 同步镜像到 TCR 企业版](../harbor-migration) — 迁移镜像至 TCR(page_id `44970`) +- [配置内网访问控制](../../ops/access/network/private-access) — VPC 内网访问策略管理 +- [配置公网访问控制](../../ops/access/network/public-access) — 公网白名单管理(`CreateSecurityPolicy` / `DeleteSecurityPolicy`) +- [企业版快速入门](../../quickstart/enterprise) — TCR 完整操作流程 + +## 控制台替代 + +[容器服务控制台 → Serverless 集群 → 工作负载 → Deployment → 新建](https://console.cloud.tencent.com/tke2/cluster):在"实例内容器"中选择"容器镜像服务 企业版",浏览选择地域、实例和仓库,手动填写镜像访问凭证。控制台为 UI 交互式配置,不生成可复用 CLI 命令。 diff --git a/src/content/docs/cli/tcr/practices/tke-serverless-pull/tke-serverless-pull-tcrctl.md b/src/content/docs/cli/tcr/practices/tke-serverless-pull/tke-serverless-pull-tcrctl.md new file mode 100644 index 0000000..7583f3e --- /dev/null +++ b/src/content/docs/cli/tcr/practices/tke-serverless-pull/tke-serverless-pull-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "" +description: "# 产品 CLI 操作" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +# 产品 CLI 操作 + +> 页型 P1 · 使用 crctl 完成本页任务 · P0 仅提供 stub 占位,P1 阶段完成正文。 + diff --git a/src/content/docs/cli/tcr/practices/tke-serverless-pull/tke-serverless-pull.md b/src/content/docs/cli/tcr/practices/tke-serverless-pull/tke-serverless-pull.md new file mode 100644 index 0000000..d05c77e --- /dev/null +++ b/src/content/docs/cli/tcr/practices/tke-serverless-pull/tke-serverless-pull.md @@ -0,0 +1,976 @@ +--- +title: "TKE Serverless 集群拉取 TCR 容器镜像" +description: "· page_id `59029`" +--- + +> 对照官方:[TKE Serverless 集群拉取 TCR 容器镜像](https://cloud.tencent.com/document/product/1141/59029) · page_id `59029` + +## 概述 + +在 TKE Serverless(弹性容器实例 EKS)集群中拉取 TCR 企业版实例内的私有容器镜像,并创建工作负载。 + +整体链路分为三层:(1) 将镜像推送至 TCR 企业版(docker,数据面);(2) 打通 TKE Serverless 集群与 TCR 实例间的内网访问链路(tccli,控制面);(3) 在集群中创建引用 TCR 镜像的工作负载(kubectl,数据面)。 + +> **与 TKE 标准集群的关键区别:** TKE Serverless 集群**不支持** TCR 插件免密拉取。必须手动创建 `imagePullSecret` 并在工作负载 `spec.template.spec.imagePullSecrets` 中显式引用。标准 TKE 集群可通过安装 TCR 插件实现免密拉取,参见 [TKE 集群使用 TCR 插件内网免密拉取容器镜像](https://cloud.tencent.com/document/product/1141/48184)。 + +## 前置条件 + +- [环境准备](../../../index.md) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域 + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tcr:DescribeInstances, tcr:CreateNamespace, tcr:DescribeNamespaces, +# tcr:CreateRepository, tcr:DescribeRepositories, +# tcr:ManageInternalEndpoint, tcr:CreateInternalEndpointDns, +# tcr:DescribeInternalEndpoints, tcr:DescribeInternalEndpointDnsStatus, +# tcr:CreateInstanceToken, tcr:DescribeInstanceToken, tcr:DeleteInstanceToken, +# tke:DescribeClusters +# 建议授予 QcloudTCRFullAccess 预设策略 +# 验证:执行 DescribeInstances 确认 TCR 权限 +tccli tcr DescribeInstances --region +# expected: exit 0,返回实例列表(可为空) + +# 验证 TKE 权限 +tccli tke DescribeClusters --region +# expected: exit 0,返回集群列表(可为空) + +# 4. 检查 Docker 客户端 +docker --version +# expected: Docker version 20.10+ + +# 5. 检查 kubectl 并可连接到 TKE Serverless 集群 +kubectl cluster-info +# expected: Kubernetes control plane 可访问 +``` + +### 资源检查 + +```bash +# 6. 确认 TCR 企业版实例存在且状态正常 +tccli tcr DescribeInstances --region --Registryids '["REGISTRY_ID"]' +# expected: exit 0, Registries[0].Status == "Running" + +# 7. 确认 TKE Serverless 集群存在 +tccli tke DescribeClusters --region --ClusterIds '["CLUSTER_ID"]' +# expected: exit 0, Clusters[0].ClusterType 含 "SERVERLESS" 或 "EKS" +``` + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 查看 TCR 实例列表 | `DescribeInstances` | 是 | +| 创建命名空间 | `CreateNamespace` | 否(同名报错) | +| 查看命名空间列表 | `DescribeNamespaces` | 是 | +| 创建镜像仓库(可选) | `CreateRepository` | 否(同名报错) | +| 查看仓库列表 | `DescribeRepositories` | 是 | +| 创建长期访问凭证 | `CreateInstanceToken` | 否 | +| 管理内网访问链路 | `ManageInternalEndpoint` | 否(同 VPC 重复创建报错) | +| 查看内网访问链路 | `DescribeInternalEndpoints` | 是 | +| 创建内网 DNS 解析 | `CreateInternalEndpointDns` | 否 | +| 查看内网 DNS 状态 | `DescribeInternalEndpointDnsStatus` | 是 | + +## 关键字段说明 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryId` | String | 是 | TCR 实例 ID,由 `DescribeInstances` 返回 | `InvalidParameter.RegistryNotFound` — 实例不存在或地域错误 | +| `Region` | String | 是 | 地域名,如 `ap-guangzhou`、`ap-shanghai` | `InvalidParameter` — 地域与实例不匹配,命令无法路由 | +| `NamespaceName` | String | 是 | 1-63 字符,小写字母/数字/下划线/连字符,不能以连字符开头结尾 | `InvalidParameter` — 命名空间名格式不合法 | +| `IsPublic` | Boolean | 否 | `true` 公有,`false` 私有(默认 `false`) | 误设为 `true` 导致镜像被意外公开访问 | +| `RepositoryName` | String | 是 | 1-255 字符,小写字母/数字/下划线/连字符/点 | `InvalidParameter` — 仓库名格式不合法 | +| `BriefDescription` | String | 否 | 最多 100 字符,仓库简要描述 | 无严重后果,仅影响仓库列表可读性 | +| `TokenType` | String | 是 | `longterm`(长期凭证,无过期)或 `temp`(临时凭证,有过期时间) | 选 `temp` 导致凭证过期后 Pod 拉取镜像失败,需重建 Secret | +| `Desc` | String | 否 | 凭证描述,最多 255 字符 | 无严重后果,仅影响凭证可识别性 | +| `VpcId` | String | 是 | 已有 VPC ID,格式 `vpc-xxxxxxxx`,须为 TKE Serverless 集群所在 VPC | `FailedOperation` — VPC 不存在或不可用 | +| `SubnetId` | String | 是 | VPC 下已有子网 ID,格式 `subnet-xxxxxxxx`,须有空闲 IP | `FailedOperation` — 子网不存在或 IP 耗尽 | +| `RegionId` | Integer | 否 | 地域数字 ID(如广州=1、上海=4),部分接口自动推导 | `InvalidParameter` — RegionId 与 RegionName 不匹配 | +| `RegionName` | String | 否 | 地域名称,须与 `--region` 一致(如 `ap-guangzhou`) | `InvalidParameter` — 地域信息不一致 | +| `EniLBIp` | String | 是 | ENI LB IP 地址。`EniLBIp` 为 `CreateInternalEndpointDns` 的输入参数名,`AccessIp` 为 `DescribeInternalEndpoints` 的输出字段名,二者为同一值 | `InvalidParameter` — IP 地址无效,DNS 解析指向错误目标 | +| `UsePublicDomain` | Boolean | 否 | `true` 使用公网域名(`tencentcloudcr.com`)做内网解析;`false` 使用专有域名 | DNS 解析目标不一致,Pod 无法通过内网访问 TCR | +| `InstanceId` | String | 是 | TCR 实例 ID(同 `RegistryId`),用于 `CreateInternalEndpointDns` | `ResourceNotFound` — 实例不存在 | +| `Operation` | String | 是 | `"Create"` 或 `"Delete"`,大小写敏感 | `InvalidParameter` — 操作类型未知 | + +## 操作步骤 + +### 步骤 1:准备容器镜像(控制面 + 数据面) + +#### 1.1 查看 TCR 实例状态 + +确认企业版实例处于可用状态: + +```bash +tccli tcr DescribeInstances \ + --Registryids '["REGISTRY_ID"]' \ + --region +# expected: exit 0, Registries[0].Status == "Running" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "example", + "RegistryType": "premium", + "Status": "Running", + "PublicDomain": "example.tencentcloudcr.com" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +| 占位符 | 对应关键字段 | 获取方式 | +|--------|------------|---------| +| `REGISTRY_ID` | `RegistryId` | `tccli tcr DescribeInstances --region ` | +| `REGION` | `Region` | `tccli configure list` 查看已配置地域 | + +记录 `RegistryName`(后续 docker 登录用)和 `RegistryType`(`basic` 或 `premium`,影响部分操作限制)。 + +#### 1.2 创建命名空间 + +##### 选择依据 + +- **IsPublic**:选择 `false`(私有),因为本场景拉取的是私有容器镜像,设为 `true` 会导致镜像被意外公开访问。默认为 `false`,省略此参数效果相同。 +- **NamespaceName**:建议使用项目名或团队名作为命名空间前缀,便于在 `DescribeNamespaces` 中按组织维度管理。 + +新建的 TCR 企业版实例内无默认命名空间,需手动创建: + +```bash +tccli tcr CreateNamespace \ + --RegistryId REGISTRY_ID \ + --NamespaceName NAMESPACE_NAME \ + --IsPublic false \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +| 占位符 | 对应关键字段 | 获取方式 | +|--------|------------|---------| +| `REGISTRY_ID` | `RegistryId` | 步骤 1.1 `DescribeInstances` 返回 | +| `NAMESPACE_NAME` | `NamespaceName` | 自定义,如 `kerwinwjyan-test` | +| `REGION` | `Region` | `tccli configure list` 查看 | + +验证: + +```bash +tccli tcr DescribeNamespaces \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, NamespaceList 包含 NAMESPACE_NAME, Public == false +``` + +**预期输出**: + +```json +{ + "NamespaceList": [ + { + "Name": "kerwinwjyan-test", + "NamespaceId": 2, + "Public": false + } + ], + "TotalCount": 1, + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +#### 1.3 创建镜像仓库(可选) + +##### 选择依据 + +- **可选执行**:`docker push` 时若目标仓库不存在,TCR 会**自动创建**,无需提前手动执行。仅在需要预设 `BriefDescription` 或严格控制仓库名的场景下才手动创建。 +- **RepositoryName**:建议与镜像名一致,避免 `docker tag` 时路径混淆。 +- **BriefDescription**:可选但建议填写,便于在 `DescribeRepositories` 返回的仓库列表中快速识别用途。 + +**最小创建**(仅必填字段,参数 ≤3 个,无需 JSON 文件): + +```bash +tccli tcr CreateRepository \ + --RegistryId REGISTRY_ID \ + --NamespaceName NAMESPACE_NAME \ + --RepositoryName REPO_NAME \ + --region +# expected: exit 0 +``` + +**增强配置**(含可选字段): + +```bash +tccli tcr CreateRepository \ + --RegistryId REGISTRY_ID \ + --NamespaceName NAMESPACE_NAME \ + --RepositoryName REPO_NAME \ + --BriefDescription "仓库描述" \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +| 占位符 | 对应关键字段 | 获取方式 | +|--------|------------|---------| +| `REGISTRY_ID` | `RegistryId` | 步骤 1.1 `DescribeInstances` 返回 | +| `NAMESPACE_NAME` | `NamespaceName` | 步骤 1.2 创建的命名空间 | +| `REPO_NAME` | `RepositoryName` | 自定义,如 `nginx` | +| `REGION` | `Region` | `tccli configure list` 查看 | + +验证: + +```bash +tccli tcr DescribeRepositories \ + --RegistryId REGISTRY_ID \ + --NamespaceName NAMESPACE_NAME \ + --region +# expected: exit 0, RepositoryList 包含 REPOSITORY_NAME +``` + +**预期输出**: + +```json +{ + "RepositoryList": [ + { + "Name": "NAMESPACE_NAME/REPO_NAME", + "Namespace": "NAMESPACE_NAME", + "Public": true + } + ], + "TotalCount": 1, + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +#### 1.4 创建访问凭证并推送镜像至 TCR(数据面) + +##### 选择依据 + +- **TokenType**:选择 `longterm` 而非 `temp`,因为 TKE Serverless 工作负载需要持久有效的镜像拉取凭证。`temp` 凭证有过期时间,过期后需重建 `imagePullSecret` 并滚动更新所有引用该 Secret 的工作负载,运维成本高。 +- **Desc**:建议填写可识别的描述(如 "EKS 拉取专用"),方便通过 `DescribeInstanceToken` 识别凭证用途,便于后续轮换时精准定位。 +- **专用凭证**:建议为 EKS 拉取场景创建专用长期凭证,而非复用控制台临时凭证,以实现权限隔离和独立轮换。 + +创建长期访问凭证: + +```bash +tccli tcr CreateInstanceToken \ + --RegistryId REGISTRY_ID \ + --TokenType longterm \ + --Desc "EKS 拉取专用" \ + --region +# expected: exit 0, 返回 Username 和 Token +``` + +**预期输出**: + +```json +{ + "Username": "100012345678", + "Token": "d8prmfsth9mroppu4740", + "TokenId": "token-example", + "ExpTime": 1893456000000, + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +> `Token` 字段**仅此一次返回**,请妥善保存。 + +| 占位符 | 对应关键字段 | 获取方式 | +|--------|------------|---------| +| `REGISTRY_ID` | `RegistryId` | 步骤 1.1 `DescribeInstances` 返回 | +| `REGION` | `Region` | `tccli configure list` 查看 | + +确认凭证已就绪: + +```bash +tccli tcr DescribeInstanceToken \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, Tokens 列表含目标凭证且 Enabled == true +``` + +**预期输出**: + +```json +{ + "Tokens": [ + { + "Id": "token-example", + "Desc": "EKS 拉取专用", + "Enabled": true + } + ], + "TotalCount": 1, + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +登录并推送镜像: + +```bash +docker login REGISTRY_NAME.tencentcloudcr.com \ + --username=USERNAME \ + --password=TOKEN +``` + +**预期输出**: + +```text +Login Succeeded +``` + +```bash +docker tag LOCAL_IMAGE:TAG \ + REGISTRY_NAME.tencentcloudcr.com/NAMESPACE_NAME/REPO_NAME:TAG + +docker push REGISTRY_NAME.tencentcloudcr.com/NAMESPACE_NAME/REPO_NAME:TAG +``` + +**预期输出**: + +```text +The push refers to repository [REGISTRY_NAME.tencentcloudcr.com/NAMESPACE_NAME/REPO_NAME] +sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: Pushed +TAG: digest: sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx size: 1234 +``` + +| 占位符 | 说明 | 获取方式 | +|--------|------|---------| +| `REGISTRY_NAME` | TCR 实例名称 | 步骤 1.1 `DescribeInstances` 返回的 `RegistryName` | +| `USERNAME` | 访问凭证用户名 | 步骤 1.4 `CreateInstanceToken` 返回的 `Username` | +| `TOKEN` | 访问凭证 Token | 步骤 1.4 `CreateInstanceToken` 返回的 `Token` | +| `NAMESPACE_NAME` | 命名空间名 | 步骤 1.2 创建的命名空间 | +| `REPO_NAME` | 仓库名 | 步骤 1.3 创建的仓库名 | +| `LOCAL_IMAGE` | 本地已有镜像名 | `docker images` 查看 | +| `TAG` | 镜像标签 | 自定义,如 `latest`、`v1.0` | + +### 步骤 2:配置 TKE Serverless 集群访问 TCR 实例(控制面) + +TCR 企业版默认拒绝全部来源的 VPC 访问。需将 TKE Serverless 集群所在 VPC 关联至 TCR 实例并配置内网域名解析。 + +> 若 TKE Serverless 集群与 TCR 实例在同一地域,建议使用内网访问(免公网流量)。若需通过公网,参见[通过 NAT 网关访问外网](https://cloud.tencent.com/document/product/457/48710)(需开启 `ManageExternalEndpoint`)。 + +#### 2.1 获取 TKE Serverless 集群所在 VPC + +```bash +tccli tke DescribeClusters \ + --ClusterIds '["CLUSTER_ID"]' \ + --region +# expected: exit 0, Clusters[0].ClusterType 含 SERVERLESS/EKS +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Clusters": [ + { + "ClusterId": "cls-example", + "ClusterName": "eks-cluster", + "ClusterType": "SERVERLESS_CLUSTER", + "ClusterStatus": "Running", + "ClusterNetworkSettings": { + "VpcId": "vpc-example", + "SubnetIds": ["subnet-example"] + } + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +| 占位符 | 说明 | 获取方式 | +|--------|------|---------| +| `CLUSTER_ID` | TKE Serverless 集群 ID | 控制台或 `tccli tke DescribeClusters --region ` | +| `REGION` | 地域 | `tccli configure list` 查看 | + +从返回的 `Clusters[0].ClusterNetworkSettings.VpcId` 获取 VPC ID,记录 `VPC_ID` 和对应的 `SUBNET_ID`。 + +#### 2.2 在 TCR 实例中关联集群 VPC + +##### 选择依据 + +- **VpcId**:必须严格是 TKE Serverless 集群所在的 VPC(由步骤 2.1 获取),否则集群内 Pod 无法通过内网访问 TCR 实例。 +- **SubnetId**:选择同一 VPC 下有空闲 IP 的子网。若子网 IP 耗尽,ENI 分配失败,`ManageInternalEndpoint` 将报错。 +- **Operation=Create**:本场景是新建内网访问链路。`ManageInternalEndpoint` 同一 VPC 重复创建会报错,非幂等。 +- **RegionId / RegionName**:部分地域需显式提供,部分可自动推导。若省略后命令正常执行则无需填写;若报 `InvalidParameter` 再补充。 + +参数 >= 4 个,使用 `--cli-input-json file://`,分最小/增强两层。 + +**最小配置**(仅必填字段): + +`tcr-manage-internal-endpoint-minimal.json`: + +```json +{ + "RegistryId": "REGISTRY_ID", + "Operation": "Create", + "VpcId": "VPC_ID", + "SubnetId": "SUBNET_ID" +} +``` + +**增强配置**(含地域信息,当最小配置报 `InvalidParameter` 时使用): + +`tcr-manage-internal-endpoint-enhanced.json`: + +```json +{ + "RegistryId": "REGISTRY_ID", + "Operation": "Create", + "VpcId": "VPC_ID", + "SubnetId": "SUBNET_ID", + "RegionId": 1, + "RegionName": "ap-guangzhou" +} +``` + +```bash +# 执行创建(优先使用 minimal,若报 InvalidParameter 则改用 enhanced) +tccli tcr ManageInternalEndpoint \ + --cli-input-json file://tcr-manage-internal-endpoint-minimal.json \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RegistryId": "REGISTRY_ID", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +| 占位符 | 对应关键字段 | 获取方式 | +|--------|------------|---------| +| `REGISTRY_ID` | `RegistryId` | 步骤 1.1 `DescribeInstances` 返回 | +| `VPC_ID` | `VpcId` | 步骤 2.1 `DescribeClusters` 返回 `ClusterNetworkSettings.VpcId` | +| `SUBNET_ID` | `SubnetId` | 步骤 2.1 `DescribeClusters` 返回的 `SubnetIds[0]` | +| `REGION` | `Region` | `tccli configure list` 查看 | + +> **注意:** 若当前账号 VPC 数量已达上限(`LimitExceeded`),`ManageInternalEndpoint` 将失败。此为环境限制,非命令错误。需先清理不再使用的 VPC 后重试。正常环境下,VPC 配额充足可正常执行。 + +异步轮询等待链路就绪: + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, AccessVpcSet 不为 null +``` + +**预期输出**: + +```json +{ + "AccessVpcSet": [ + { + "VpcId": "vpc-of73262z", + "SubnetId": "subnet-rdmcho9m", + "AccessIp": "10.0.0.100", + "Status": "Running" + } + ], + "TotalCount": 1, + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +> 若内网链路尚未创建,`AccessVpcSet` 为 `null`、`TotalCount` 为 `0`。执行 `ManageInternalEndpoint` 创建链路后重试。 + +记录 `AccessVpcSet[0].AccessIp` 值(即 `ENI_LB_IP`),后续步骤 2.3 需要。 + +##### 多维度验证 + +创建内网链路是异步操作,仅检查 `Status=Running` 不够,需多维度确认: + +| 维度 | 检查内容 | 预期 | +|------|---------|------| +| 状态 | `AccessVpcSet[i].Status` | `Running` | +| 网络 | `AccessVpcSet[i].AccessIp` | 非空有效 IP 地址 | +| VPC 关联 | `AccessVpcSet[i].VpcId` | 等于目标 `VPC_ID` | +| 子网关联 | `AccessVpcSet[i].SubnetId` | 等于目标 `SUBNET_ID` | + +#### 2.3 配置域名内网解析 + +内网访问链路创建完成后,配置内网 DNS 解析使 VPC 内 Pod 可通过内网解析 TCR 域名。 + +##### 选择依据 + +- **UsePublicDomain**:推荐设为 `true`,使用 `tencentcloudcr.com` 公网域名进行内网解析。这样内网与公网使用同一域名,避免 DNS 分裂导致配置不一致。若设为 `false`,将使用专有内网域名,需同时修改工作负载中的镜像地址。 +- **EniLBIp**:必须严格来自步骤 2.2 `DescribeInternalEndpoints` 返回的 `AccessVpcSet[0].AccessIp`。任意填写会导致 DNS 解析指向错误 IP,Pod 无法拉取镜像。 +- **前置依赖**:本步骤依赖步骤 2.2 `ManageInternalEndpoint` 成功创建链路并提供 `AccessIp`。 + +参数 >= 4 个,使用 `--cli-input-json file://`,分最小/增强两层。 + +**最小配置**(仅必填字段): + +`tcr-create-internal-dns-minimal.json`: + +```json +{ + "InstanceId": "REGISTRY_ID", + "VpcId": "VPC_ID", + "EniLBIp": "ENI_LB_IP" +} +``` + +**增强配置**(含可选字段,推荐): + +`tcr-create-internal-dns-enhanced.json`: + +```json +{ + "InstanceId": "REGISTRY_ID", + "VpcId": "VPC_ID", + "EniLBIp": "ENI_LB_IP", + "UsePublicDomain": true, + "RegionName": "ap-guangzhou" +} +``` + +```bash +# 执行创建(推荐使用 enhanced) +tccli tcr CreateInternalEndpointDns \ + --cli-input-json file://tcr-create-internal-dns-enhanced.json \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +| 占位符 | 对应关键字段 | 获取方式 | +|--------|------------|---------| +| `REGISTRY_ID` | `RegistryId` / `InstanceId` | 步骤 1.1 `DescribeInstances` 返回 | +| `VPC_ID` | `VpcId` | 步骤 2.1 `DescribeClusters` 返回 | +| `ENI_LB_IP` | `EniLBIp` | 步骤 2.2 `DescribeInternalEndpoints` 返回 `AccessVpcSet[0].AccessIp` | +| `REGION` | `Region` | `tccli configure list` 查看 | + +> **注意:** 若 `ManageInternalEndpoint`(步骤 2.2)未执行或失败,则 `CreateInternalEndpointDns` 无可用 `ENI_LB_IP`,本步骤无法执行。需先确保步骤 2.2 成功。 + +验证内网 DNS 解析状态: + +```bash +tccli tcr DescribeInternalEndpointDnsStatus \ + --VpcSet '[{"InstanceId":"REGISTRY_ID","VpcId":"VPC_ID","EniLBIp":"ENI_LB_IP","UsePublicDomain":true}]' \ + --region +# expected: exit 0, VpcSet[0].Status == "ENABLED" +``` + +**预期输出**: + +```json +{ + "VpcSet": [ + { + "Status": "ENABLED" + } + ], + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +### 步骤 3:使用 TCR 镜像在 TKE Serverless 集群创建工作负载(数据面) + +> **需 EKS 集群:** 以下 kubectl 操作依赖已存在的 TKE Serverless(EKS)集群。若尚无可用集群,请先通过 [TKE 控制台](https://console.cloud.tencent.com/tke2/ecluster) 创建,或使用标准 TKE 集群 + TCR 插件方案(参见 [TKE 集群使用 TCR 插件内网免密拉取容器镜像](https://cloud.tencent.com/document/product/1141/48184))。 + +#### 3.1 创建 imagePullSecret + +TKE Serverless 集群**不支持** TCR 插件免密拉取,必须手动创建 `imagePullSecret`: + +```bash +kubectl create secret docker-registry image-pull-secret \ + --docker-server=REGISTRY_NAME.tencentcloudcr.com \ + --docker-username=USERNAME \ + --docker-password=TOKEN \ + --namespace=K8S_NAMESPACE +``` + +**预期输出**: + +```text +secret/image-pull-secret created +``` + +| 占位符 | 说明 | 获取方式 | +|--------|------|---------| +| `REGISTRY_NAME` | TCR 实例名称 | 步骤 1.1 `DescribeInstances` 返回的 `RegistryName` | +| `USERNAME` | 访问凭证用户名 | 步骤 1.4 `CreateInstanceToken` 返回的 `Username` | +| `TOKEN` | 访问凭证 Token | 步骤 1.4 `CreateInstanceToken` 返回的 `Token` | +| `K8S_NAMESPACE` | K8s 命名空间 | 自定义或使用已有 K8s 命名空间 | + +验证 Secret 已创建: + +```bash +kubectl get secret image-pull-secret -n K8S_NAMESPACE -o yaml +# expected: .data 字段含 .dockerconfigjson,非空 +``` + +#### 3.2 创建引用 TCR 镜像的工作负载 + +创建 Deployment 清单 `eks-deployment.yaml`: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: DEPLOYMENT_NAME + namespace: K8S_NAMESPACE +spec: + replicas: 1 + selector: + matchLabels: + app: APP_NAME + template: + metadata: + labels: + app: APP_NAME + spec: + imagePullSecrets: + - name: image-pull-secret + containers: + - name: CONTAINER_NAME + image: REGISTRY_NAME.tencentcloudcr.com/NAMESPACE_NAME/REPO_NAME:TAG + ports: + - containerPort: 80 +``` + +```bash +kubectl apply -f eks-deployment.yaml +``` + +**预期输出**: + +```text +deployment.apps/DEPLOYMENT_NAME created +``` + +| 占位符 | 说明 | 获取方式 | +|--------|------|---------| +| `DEPLOYMENT_NAME` | Deployment 名称 | 自定义 | +| `K8S_NAMESPACE` | K8s 命名空间 | 与步骤 3.1 一致 | +| `APP_NAME` | 应用标签名 | 自定义,如 `nginx` | +| `CONTAINER_NAME` | 容器名 | 自定义,如 `nginx` | +| `REGISTRY_NAME` | TCR 实例名称 | 步骤 1.1 `DescribeInstances` 返回 | +| `NAMESPACE_NAME` | TCR 命名空间 | 步骤 1.2 创建的命名空间 | +| `REPO_NAME` | TCR 仓库名 | 步骤 1.3 创建的仓库名 | +| `TAG` | 镜像标签 | 与 docker push 使用的标签一致 | + +> **关键提醒:** `spec.template.spec.imagePullSecrets` 在 TKE Serverless 中必须显式配置。Serverless 集群不支持 TCR 插件自动注入,若遗漏此字段 Pod 将因鉴权失败而 `ImagePullBackOff`。 + +#### 3.3 验证工作负载状态 + +```bash +kubectl get pods -n K8S_NAMESPACE -l app=APP_NAME +# expected: STATUS Running, READY 1/1 +``` + +**预期输出**: + +```text +NAME READY STATUS RESTARTS AGE +DEPLOYMENT_NAME-xxxxxxxxxx-xxxxx 1/1 Running 0 30s +``` + +```bash +kubectl describe pod -n K8S_NAMESPACE -l app=APP_NAME +``` + +确认 Events 中无 `Failed to pull image`、`ImagePullBackOff` 或 `ErrImagePull` 错误。 + +## 验证 + +### 控制面(tccli) + +```bash +# TCR 实例已就绪 +tccli tcr DescribeInstances \ + --Registryids '["REGISTRY_ID"]' \ + --region +# expected: exit 0, Registries[0].Status == "Running" +``` + +```bash +# 命名空间已创建 +tccli tcr DescribeNamespaces \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, NamespaceList 包含 NAMESPACE_NAME +``` + +```bash +# 访问凭证已就绪 +tccli tcr DescribeInstanceToken \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, Tokens 列表含目标凭证且 Enabled == true +``` + +```bash +# 内网访问链路已就绪 +tccli tcr DescribeInternalEndpoints \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, AccessVpcSet 包含目标 VPC,Status == "Running" +``` + +```bash +# 内网 DNS 已就绪 +tccli tcr DescribeInternalEndpointDnsStatus \ + --VpcSet '[{"InstanceId":"REGISTRY_ID","VpcId":"VPC_ID","EniLBIp":"ENI_LB_IP","UsePublicDomain":true}]' \ + --region +# expected: exit 0, VpcSet[0].Status == "ENABLED" +``` + +### 数据面(kubectl / docker) + +```bash +# 确认 Secret 存在 +kubectl get secret image-pull-secret -n K8S_NAMESPACE -o yaml +# expected: .data 含 .dockerconfigjson +``` + +```bash +# 确认 Pod 运行正常 +kubectl get pods -n K8S_NAMESPACE -l app=APP_NAME +# expected: STATUS Running, READY 1/1 +``` + +```bash +# 确认镜像可拉取(在可访问 TCR 的客户端上) +docker pull REGISTRY_NAME.tencentcloudcr.com/NAMESPACE_NAME/REPO_NAME:TAG +# expected: 拉取成功,返回 Downloaded +``` + +## 清理 + +> **计费警告**:TKE Serverless Pod 按 vCPU/内存按秒计费,删除工作负载后停止计费。TCR 企业版实例按规格持续收费,若不再使用请参考[销毁企业版实例](../../ops/instances/delete)释放。 + +### 数据面(kubectl) + +数据面资源需在控制面资源之前清理。 + +**删除工作负载:** + +> **副作用警告**:`kubectl delete deployment` 会级联删除该 Deployment 管理的所有 Pod。若为生产环境,请确保已迁移流量或做好备份。 + +清理前确认: + +```bash +kubectl get deployment DEPLOYMENT_NAME -n K8S_NAMESPACE +# expected: 确认 Deployment 存在且为待删目标 +``` + +```bash +kubectl delete deployment DEPLOYMENT_NAME -n K8S_NAMESPACE +``` + +**预期输出**: + +```text +deployment.apps "DEPLOYMENT_NAME" deleted +``` + +验证已删除: + +```bash +kubectl get deployment DEPLOYMENT_NAME -n K8S_NAMESPACE +# expected: Error from server (NotFound) +``` + +**删除 imagePullSecret:** + +> **副作用警告**:删除 Secret 后,任何引用此 Secret 的 Deployment 在 Pod 重启或重新调度时将因鉴权失败而 `ImagePullBackOff`。请确保已先删除所有引用此 Secret 的工作负载。 + +清理前确认: + +```bash +kubectl get secret image-pull-secret -n K8S_NAMESPACE +# expected: 确认 Secret 存在且为待删目标 +``` + +```bash +kubectl delete secret image-pull-secret -n K8S_NAMESPACE +``` + +**预期输出**: + +```text +secret "image-pull-secret" deleted +``` + +验证已删除: + +```bash +kubectl get secret image-pull-secret -n K8S_NAMESPACE +# expected: Error from server (NotFound) +``` + +**登出 TCR(如有 docker 登录):** + +```bash +docker logout REGISTRY_NAME.tencentcloudcr.com +``` + +### 控制面(tccli) + +**删除内网访问链路:** + +> **副作用警告**:删除内网访问链路后,VPC 内所有服务将无法通过内网访问 TCR 实例。关联的内网 DNS 解析记录也将失效。若集群中仍有运行中的工作负载依赖此链路拉取镜像,Pod 重启后将出现 `ImagePullBackOff`。 + +清理前确认链路存在: + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, 确认 AccessVpcSet 包含目标 VPC +``` + +`tcr-manage-internal-endpoint-delete.json`: + +```json +{ + "RegistryId": "REGISTRY_ID", + "Operation": "Delete", + "VpcId": "VPC_ID", + "SubnetId": "SUBNET_ID" +} +``` + +```bash +tccli tcr ManageInternalEndpoint \ + --cli-input-json file://tcr-manage-internal-endpoint-delete.json \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RegistryId": "REGISTRY_ID", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +验证已删除: + +```bash +tccli tcr DescribeInternalEndpoints \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, AccessVpcSet 为 null 或 TotalCount == 0 +``` + +**预期输出**: + +```json +{ + "AccessVpcSet": null, + "TotalCount": 0, + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +**删除访问凭证(可选,若仅需清理凭证而保留命名空间和仓库):** + +> **副作用警告**:删除凭证后,所有依赖此凭证的 `imagePullSecret` 将立即失效,对应工作负载在重启或重新调度时将出现 `ImagePullBackOff`。请确保已先删除所有引用此凭证的工作负载和 Secret。 + +清理前确认凭证存在: + +```bash +tccli tcr DescribeInstanceToken \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, 记录待删凭证的 TokenId +``` + +```bash +tccli tcr DeleteInstanceToken \ + --RegistryId REGISTRY_ID \ + --TokenId TOKEN_ID \ + --region +# expected: exit 0 +``` + +验证已删除: + +```bash +tccli tcr DescribeInstanceToken \ + --RegistryId REGISTRY_ID \ + --region +# expected: exit 0, Tokens 列表中不再包含已删凭证 +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ManageInternalEndpoint` 报 `LimitExceeded` | `tccli vpc DescribeVpcs --region ` 统计已有 VPC 数量 | 账号 VPC 配额已满,无法添加新 VPC 关联 | 此为环境限制,非命令错误。清理不再使用的 VPC 后重试:`tccli vpc DeleteVpc --VpcId VPC_ID`;或改用已有 VPC 关联 | +| `ManageInternalEndpoint` 报 `FailedOperation` | `tccli vpc DescribeVpcs --VpcIds '["VPC_ID"]' --region ` 和 `tccli vpc DescribeSubnets --SubnetIds '["SUBNET_ID"]' --region ` 确认 VPC 和子网均存在 | VPC ID 或子网 ID 不存在 / 不可用 / 子网 IP 耗尽 | 修正 `VpcId` 和 `SubnetId` 为有效值,确保子网有空闲 IP | +| `CreateInternalEndpointDns` 报 `InvalidParameter` | `tccli tcr DescribeInternalEndpoints --RegistryId REGISTRY_ID --region ` 确认 `AccessIp` 与返回值一致 | `EniLBIp` 填写错误或 `RegionName` 与实例地域不一致 | 从 `DescribeInternalEndpoints` 返回的 `AccessVpcSet[0].AccessIp` 获取 IP 值,填入 `EniLBIp` 参数;`RegionName` 与 `--region` 保持一致 | +| docker push 报 `unauthorized` | `tccli tcr DescribeInstanceToken --RegistryId REGISTRY_ID --region ` 确认凭证 `Enabled == true` | 未登录或凭证过期/无效 | `tccli tcr CreateInstanceToken --RegistryId REGISTRY_ID --TokenType longterm --Desc "push-token" --region ` 生成新凭证后 `docker login` 重新登录 | +| `VPC LimitExceeded` 且无法释放 VPC | `tccli vpc DescribeVpcs --region ` 确认所有 VPC 均为生产在用 | 当前账号 VPC 配额已达上限且无可用 VPC 可释放 | 此为**环境限制**(非命令错误)。联系售后提升配额,或改用公网入口:`ManageExternalEndpoint` + `CreateSecurityPolicy` | +| `CamNoAuth` 或权限拒绝 | `tccli tcr DescribeInstances --region ` 确认当前凭证有 TCR 读权限 | 子账号缺少所需 CAM 权限 | 此为环境限制,非命令错误。授予 `QcloudTCRFullAccess` 预设策略,或参考[企业版授权方案示例](https://cloud.tencent.com/document/product/1141/41417) | + +### 创建成功但状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| Pod 报 `ImagePullBackOff` | `kubectl get secret image-pull-secret -n K8S_NAMESPACE` 确认 Secret 存在;`kubectl describe deployment DEPLOYMENT_NAME -n K8S_NAMESPACE` 检查 `spec.template.spec.imagePullSecrets`;`tccli tcr DescribeInstanceToken --RegistryId REGISTRY_ID --region ` 确认凭证 `Enabled == true` | 未配置 `imagePullSecret` 或凭证无效/过期 | 确保 Secret 存在且 Deployment 的 `imagePullSecrets` 引用正确;若凭证过期,重新创建凭证并更新 Secret | +| Pod 报 `ErrImagePull` | `tccli tcr DescribeRepositories --RegistryId REGISTRY_ID --NamespaceName NAMESPACE_NAME --region ` 确认仓库存在 | 镜像地址格式错误或仓库/镜像不存在 | 确认格式:`REGISTRY_NAME.tencentcloudcr.com/NAMESPACE_NAME/REPO_NAME:TAG`;检查命名空间、仓库名和 Tag 均正确 | +| 内网 DNS 解析状态非 `ENABLED` | `tccli tcr DescribeInternalEndpointDnsStatus --VpcSet '[{"InstanceId":"REGISTRY_ID","VpcId":"VPC_ID","EniLBIp":"ENI_LB_IP","UsePublicDomain":true}]' --region ` 查看详细状态 | DNS 配置参数错误(`EniLBIp` 不匹配)或 VPC 内 DNS 服务未就绪 | 对比 `EniLBIp` 与 `DescribeInternalEndpoints` 返回的 `AccessVpcSet[0].AccessIp` 是否一致;等待 1-2 分钟后重试,若持续非 `ENABLED` 联系售后 | +| `DescribeInternalEndpoints` 返回 `TotalCount: 0` | `tccli tcr DescribeInternalEndpoints --RegistryId REGISTRY_ID --region ` 确认返回值 | 尚未创建任何内网访问链路 | 执行步骤 2.2 `ManageInternalEndpoint`(需 VPC 配额充足) | +| `CreateInstanceToken` 返回的 `Token` 丢失 | 无(Token 仅返回一次,API 不可恢复) | 创建时未妥善保存 Token 值 | `tccli tcr DeleteInstanceToken --RegistryId REGISTRY_ID --TokenId TOKEN_ID --region ` 删除旧凭证后重新 `CreateInstanceToken`,并立即保存 Token 值 | +| 工作负载正常运行但重启后 `ImagePullBackOff` | `tccli tcr DescribeInstanceToken --RegistryId REGISTRY_ID --region ` 确认凭证 `Enabled == true` | 凭证被删除或禁用,Secret 中的旧 Token 不再有效 | 重新创建凭证并更新 `imagePullSecret`:`kubectl delete secret image-pull-secret -n K8S_NAMESPACE` 后 `kubectl create secret docker-registry` 使用新 Token | + +## 下一步 + +- [TKE 集群使用 TCR 插件内网免密拉取容器镜像](https://cloud.tencent.com/document/product/1141/48184) — 标准 TKE 集群免密方案(page_id `48184`) +- [从自建 Harbor 同步镜像到 TCR 企业版](../harbor-migration) — 迁移镜像至 TCR(page_id `44970`) +- [配置内网访问控制](../../ops/access/network/private-access) — VPC 内网访问策略管理 +- [配置公网访问控制](../../ops/access/network/public-access) — 公网白名单管理(`CreateSecurityPolicy` / `DeleteSecurityPolicy`) +- [企业版快速入门](../../quickstart/enterprise) — TCR 完整操作流程 + +## 控制台替代 + +[容器服务控制台 → Serverless 集群 → 工作负载 → Deployment → 新建](https://console.cloud.tencent.com/tke2/cluster):在"实例内容器"中选择"容器镜像服务 企业版",浏览选择地域、实例和仓库,手动填写镜像访问凭证。控制台为 UI 交互式配置,不生成可复用 CLI 命令。 diff --git a/src/content/docs/cli/tcr/quickstart/enterprise-tcrctl.md b/src/content/docs/cli/tcr/quickstart/enterprise-tcrctl.md new file mode 100644 index 0000000..843d30b --- /dev/null +++ b/src/content/docs/cli/tcr/quickstart/enterprise-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "企业版快速入门(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[企业版快速入门](https://cloud.tencent.com/document/product/1141/39287) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/quickstart/enterprise.md b/src/content/docs/cli/tcr/quickstart/enterprise.md new file mode 100644 index 0000000..a1e349f --- /dev/null +++ b/src/content/docs/cli/tcr/quickstart/enterprise.md @@ -0,0 +1,638 @@ +--- +title: "企业版快速入门" +description: "· page_id `39287`" +--- + +> 对照官方:[企业版快速入门](https://cloud.tencent.com/document/product/1141/39287) · page_id `39287` + +## 概述 + +通过 `tccli tcr` 命令行完成 TCR 企业版从零到推送镜像的全流程。企业版实例提供独立的 Registry 域名(格式 `INSTANCE_NAME.tencentcloudcr.com`),支持容器镜像托管、安全扫描、跨地域同步和访问控制。 + +实例规格分三档,按需选择: + +| 规格 | RegistryType | 适用场景 | +|------|-------------|------| +| 基础版 | `basic` | 小团队或个人开发者,低并发拉取 | +| 标准版 | `standard` | 中型团队,日常 CI/CD,中等并发 | +| 高级版 | `premium` | 企业生产环境,高并发,跨地域同步 | + +本文为快速体验流程,各步骤的详细参数和高级配置请参见对应操作指南专页。 + +## 前置条件 + +- [环境准备](../../../index.md) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域 + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tcr:CreateInstance, tcr:DescribeInstances, tcr:CreateNamespace +# tcr:CreateRepository, tcr:CreateInstanceToken, tcr:CreateSecurityPolicy +# tcr:ManageExternalEndpoint, tcr:DescribeExternalEndpointStatus +# tcr:DescribeSecurityPolicies, tcr:DescribeNamespaces +# tcr:DescribeRepositories, tcr:DescribeInstanceToken +# 验证:执行 DescribeInstances 确认权限 +tccli tcr DescribeInstances --region +# expected: exit 0,返回实例列表(可为空) + +# 4. 检查 Docker 是否安装(用于推送/拉取镜像) +docker --version +# expected: Docker version 20.10 或更高 + +# 5. 确认已开通 COS 服务(企业版实例依赖 COS 存储镜像数据) +# 登录 https://console.cloud.tencent.com/cos5 确认 COS 已开通 +# 若未开通,访问控制台开通即可,无需额外 CLI 操作 +``` + +### 实例名预检 + +```bash +# 6. 确认目标实例名可用 +tccli tcr CheckInstanceName --RegistryName INSTANCE_NAME +# expected: IsValidated: true, DetailCode: 0,表示名称可用 +``` + +**预期输出**: + +```json +{ + "IsValidated": true, + "DetailCode": 0, + "RequestId": "xxxx" +} +``` + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 检查实例名可用性 | `CheckInstanceName` | 是 | +| 购买企业版实例 | `CreateInstance` | 否 | +| 查看实例状态 | `DescribeInstances` | 是 | +| 开启公网访问入口 | `ManageExternalEndpoint --Operation Create` | 否 | +| 关闭公网访问入口 | `ManageExternalEndpoint --Operation Delete` | 否 | +| 查看公网端点状态 | `DescribeExternalEndpointStatus` | 是 | +| 添加公网白名单 | `CreateSecurityPolicy` | 否 | +| 查看公网白名单 | `DescribeSecurityPolicies` | 是 | +| 创建命名空间 | `CreateNamespace` | 否 | +| 查看命名空间列表 | `DescribeNamespaces` | 是 | +| 创建镜像仓库 | `CreateRepository` | 否 | +| 查看镜像仓库列表 | `DescribeRepositories` | 是 | +| 生成访问凭证 | `CreateInstanceToken` | 否 | +| 查看访问凭证列表 | `DescribeInstanceToken` | 是 | + +## 关键字段说明 + +以下说明 `CreateInstance` 的主要参数。完整参数定义见 `tccli tcr CreateInstance --help`。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryName` | String | 是 | 全局唯一,长度 2-30 字符,字母开头。创建后不可修改,自动生成域名 `RegistryName.tencentcloudcr.com` | 重名 → `InstanceNameAlreadyExists` | +| `RegistryType` | String | 是 | `basic`(基础版)/ `standard`(标准版)/ `premium`(高级版)。创建后不可降级 | 无效值 → `InvalidParameter` | +| `RegistryChargeType` | Integer | 否 | `0` = 按量计费(后付费),`1` = 预付费(包年包月)。默认 `0` | 选错计费类型 → 计费方式与预期不符,创建后不可切换 | +| `EnableCosMAZ` | Boolean | 否 | 是否开启 COS 多 AZ 存储。默认 `false`。开启可提高数据可靠性,但存储费用较高 | 未开启 → 单 AZ 故障可能导致数据不可用 | +| `DeletionProtection` | Boolean | 否 | 默认 `false`。`true` 时需先关闭才能删除实例 | 忘开 → 可能误删生产实例及所有镜像数据 | +| `EnableCosVersioning` | Boolean | 否 | 是否开启 COS 版本控制。默认 `false` | 未开启 → 误删或覆盖镜像后无法恢复历史版本 | +| `SyncTag` | Boolean | 否 | 是否同步实例标签到 COS 桶。默认 `false` | — | + +## 操作步骤 + +### 步骤1:创建企业版实例 + +#### 选择依据 + +- **实例规格**:本文选 `basic`(基础版按量计费),适合快速体验。生产环境推荐 `premium`(高级版),支持更高并发和跨地域同步。 +- **多 AZ 存储**:开启 `EnableCosMAZ` 可跨可用区容灾,提高数据可靠性。体验场景可关闭以节省费用。 +- **删除保护**:体验场景建议 `false`,便于实验后清理。生产环境务必开启 `DeletionProtection: true`。 + +#### 最小创建(空实例,只含必填字段) + +`create-instance-minimal.json`: + +```json +{ + "RegistryName": "INSTANCE_NAME", + "RegistryType": "basic" +} +``` + +```bash +tccli tcr CreateInstance --region \ + --cli-input-json file://create-instance-minimal.json +# expected: exit 0,返回 RegistryId +``` + +**预期输出**: + +```json +{ + "RegistryId": "tcr-example", + "RequestId": "xxxx" +} +``` + +记录返回的 `RegistryId`(下文以 `REGISTRY_ID` 代指),后续所有实例操作均依赖此 ID。 + +#### 增强配置(加多 AZ、版本控制、删除保护) + +`create-instance-enhanced.json`: + +```json +{ + "RegistryName": "INSTANCE_NAME", + "RegistryType": "basic", + "RegistryChargeType": 0, + "SyncTag": false, + "EnableCosMAZ": true, + "DeletionProtection": true, + "EnableCosVersioning": true +} +``` + +```bash +tccli tcr CreateInstance --region \ + --cli-input-json file://create-instance-enhanced.json +# expected: exit 0,返回 RegistryId +``` + +#### 轮询实例状态 + +实例创建是异步操作。轮询直到状态为 `Running`: + +```bash +tccli tcr DescribeInstances --region \ + --Registryids '["REGISTRY_ID"]' +# expected: Status: "Running" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "INSTANCE_NAME", + "RegistryType": "basic", + "Status": "Running", + "PublicDomain": "INSTANCE_NAME.tencentcloudcr.com", + "RegionName": "ap-guangzhou", + "InternalEndpoint": "10.0.0.0", + "PayMod": 0, + "DeletionProtection": false, + "EnableCosMAZ": true, + "EnableCosVersioning": true, + "CreatedAt": "2025-01-01T00:00:00+08:00" + } + ] +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `INSTANCE_NAME` | 实例名称 | 全局唯一,2-30 字符,字母开头 | 自定义,建议组合公司、地域或项目缩写 | +| `REGISTRY_ID` | 实例 ID | 由 CreateInstance 返回 | 记录上一步输出中的 `RegistryId` 字段 | +| `REGION` | 地域 | 如 `ap-guangzhou` | `tccli configure list` 查看已配置的 region | + +> 最长等待约 2 分钟(基础版)。超时参见 [排障](#排障)。 + +### 步骤2:配置公网访问 + +实例创建后**默认拒绝全部公网及内网访问**,必须先配置访问策略才能推送/拉取镜像。 + +#### 开启公网访问端点 + +```bash +tccli tcr ManageExternalEndpoint --region \ + --RegistryId REGISTRY_ID \ + --Operation Create +# expected: exit 0,返回 RegistryId +``` + +**预期输出**: + +```json +{ + "RegistryId": "tcr-example", + "RequestId": "xxxx" +} +``` + +> `ManageExternalEndpoint` 的 `Operation` 参数取值为 `Create`(开启)或 `Delete`(关闭),非 `Open`/`Close`。 + +#### 轮询公网端点状态 + +```bash +tccli tcr DescribeExternalEndpointStatus --region \ + --RegistryId REGISTRY_ID +# expected: Status: "Opened" +``` + +**预期输出**: + +```json +{ + "Status": "Opened", + "Reason": "", + "RequestId": "xxxx" +} +``` + +状态变化:`Opening` 到 `Opened`,约需 1-3 分钟。 + +#### 添加公网访问白名单 + +公网端点 `Opened` 后,添加白名单放通访问来源 IP: + +```bash +tccli tcr CreateSecurityPolicy --region \ + --RegistryId REGISTRY_ID \ + --CidrBlock CIDR_BLOCK \ + --Description "POLICY_DESC" +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RegistryId": "tcr-example", + "RequestId": "xxxx" +} +``` + +> 公网端点未 `Opened` 时调用 `CreateSecurityPolicy` 会返回 `OperationDenied`。务必先确认 `DescribeExternalEndpointStatus` 返回 `Status: "Opened"` 再执行。排障参见 [排障](#排障)。 + +> 安全建议:不要直接使用 `0.0.0.0/0` 放通全部来源公网访问。完成内网配置后建议尽快关闭公网入口。 + +#### 查看白名单 + +```bash +tccli tcr DescribeSecurityPolicies --region \ + --RegistryId REGISTRY_ID +# expected: SecurityPolicySet 含目标策略 +``` + +**预期输出**: + +```json +{ + "SecurityPolicySet": [ + { + "PolicyIndex": 0, + "Description": "POLICY_DESC", + "CidrBlock": "CIDR_BLOCK", + "PolicyVersion": "1" + } + ], + "RequestId": "xxxx" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `CIDR_BLOCK` | 白名单 IP 或地址段 | 如 `1.2.3.4/32` 或 `10.0.0.0/8` | 当前机器的出口 IP | +| `POLICY_DESC` | 策略备注 | 任意字符串 | 自定义 | + +### 步骤3:创建命名空间 + +命名空间用于组织管理实例内的镜像仓库,不直接存储容器镜像。可映射为企业内团队、项目或其他自定义层级。 + +#### 选择依据 + +- **访问级别**:`IsPublic: true` = 公开(命名空间内仓库允许匿名拉取),`IsPublic: false` = 私有(需凭据访问)。本文选 `true` 方便体验。 + +```bash +tccli tcr CreateNamespace --region \ + --RegistryId REGISTRY_ID \ + --NamespaceName NAMESPACE_NAME \ + --IsPublic true +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RequestId": "xxxx" +} +``` + +#### 查看命名空间 + +```bash +tccli tcr DescribeNamespaces --region \ + --RegistryId REGISTRY_ID +# expected: NamespaceList 含目标命名空间 +``` + +**预期输出**: + +```json +{ + "NamespaceList": [ + { + "Name": "ns-example", + "NamespaceId": 1, + "Public": true, + "CreationTime": "2025-01-01T00:00:00.000Z" + } + ], + "TotalCount": 1, + "RequestId": "xxxx" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `NAMESPACE_NAME` | 命名空间名称 | 小写字母、数字、连字符,实例内唯一 | 自定义,建议用团队或项目命名 | + +### 步骤4:创建镜像仓库(可选) + +镜像仓库可手动创建,也可在 `docker push` 时自动创建。手动创建可预设描述和访问级别。 + +`create-repository.json`: + +```json +{ + "RegistryId": "REGISTRY_ID", + "NamespaceName": "NAMESPACE_NAME", + "RepositoryName": "REPO_NAME", + "BriefDescription": "BRIEF_DESC", + "Description": "DESCRIPTION" +} +``` + +```bash +tccli tcr CreateRepository --region \ + --cli-input-json file://create-repository.json +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RequestId": "xxxx" +} +``` + +#### 查看镜像仓库 + +```bash +tccli tcr DescribeRepositories --region \ + --RegistryId REGISTRY_ID \ + --NamespaceName NAMESPACE_NAME +# expected: RepositoryList 含目标仓库 +``` + +**预期输出**: + +```json +{ + "RepositoryList": [ + { + "Name": "ns-example/repo-example", + "Namespace": "ns-example", + "Public": true, + "CreationTime": "2025-01-01 00:00:00.000000 +0000 UTC" + } + ], + "TotalCount": 1, + "RequestId": "xxxx" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `REPO_NAME` | 仓库名称 | 小写字母、数字、连字符、下划线、正斜杠,支持多级路径如 `team/app` | 自定义 | +| `BRIEF_DESC` | 简短描述 | 任意字符串 | 自定义 | +| `DESCRIPTION` | 详细描述 | 支持 Markdown 语法 | 自定义 | + +### 步骤5:创建访问凭证 + +#### 选择依据 + +- **TokenType**:`longterm`(长期,有效期 87600 小时约 10 年)适用于 CI/CD 或长期开发环境;`temp`(临时,有效期 1 小时)适用于一次性操作。本文选 `longterm`。 + +```bash +tccli tcr CreateInstanceToken --region \ + --RegistryId REGISTRY_ID \ + --TokenType longterm \ + --Desc "TOKEN_DESC" +# expected: exit 0,返回 Username 和 Token +``` + +**预期输出**: + +```json +{ + "Username": "100000000000", + "Token": "eyJhbGciOiJSUzI1NiIs...", + "TokenId": "tkn-example", + "ExpTime": 2096954604521, + "RequestId": "xxxx" +} +``` + +> 立即记录 `Token` 字段——它只在创建时返回一次,关闭终端后将无法再次查看。若丢失,需重新创建新令牌。Docker 登录时用 `Token` 值作为密码。 + +#### 查看访问凭证列表 + +```bash +tccli tcr DescribeInstanceToken --region \ + --RegistryId REGISTRY_ID +# expected: Tokens 列表含目标令牌 +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Tokens": [ + { + "Id": "tkn-example", + "Desc": "TOKEN_DESC", + "Enabled": true, + "CreatedAt": "2025-01-01T00:00:00+08:00" + } + ], + "RequestId": "xxxx" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `TOKEN_DESC` | 令牌描述 | 任意字符串 | 自定义,如 `dev-token-2025` | + +### 步骤6:推送与拉取镜像 + +获取实例公网域名: + +```bash +tccli tcr DescribeInstances --region \ + --Registryids '["REGISTRY_ID"]' +# expected: PublicDomain 字段为 INSTANCE_NAME.tencentcloudcr.com +``` + +记下 `PublicDomain` 字段(下文以 `PUBLIC_DOMAIN` 代指),其格式为 `INSTANCE_NAME.tencentcloudcr.com`。 + +#### 登录 Registry + +```bash +docker login PUBLIC_DOMAIN --username USERNAME --password TOKEN +# expected: Login Succeeded +``` + +`USERNAME` 和 `TOKEN` 来自步骤5 `CreateInstanceToken` 的输出。 + +#### 推送镜像 + +```bash +docker pull nginx:latest +docker tag nginx:latest PUBLIC_DOMAIN/NAMESPACE_NAME/REPO_NAME:latest +docker push PUBLIC_DOMAIN/NAMESPACE_NAME/REPO_NAME:latest +# expected: push 进度条结束,显示 digest: sha256:xxxx +``` + +#### 拉取镜像 + +```bash +docker pull PUBLIC_DOMAIN/NAMESPACE_NAME/REPO_NAME:latest +# expected: pull 进度条结束,显示 Status: Downloaded newer image +``` + +| 占位符 | 说明 | 获取方式 | +|--------|------|---------| +| `PUBLIC_DOMAIN` | 实例公网域名 | `DescribeInstances` 输出的 `PublicDomain` 字段,格式 `INSTANCE_NAME.tencentcloudcr.com` | +| `USERNAME` | 登录用户名 | `CreateInstanceToken` 输出的 `Username` 字段 | +| `TOKEN` | 登录密码/令牌 | `CreateInstanceToken` 输出的 `Token` 字段 | + +## 验证 + +### 控制面(tccli) + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 实例状态 | `tccli tcr DescribeInstances --region --Registryids '["REGISTRY_ID"]'` | `Status: "Running"` | +| 实例域名 | 同上,检查 `PublicDomain` | `INSTANCE_NAME.tencentcloudcr.com` | +| 规格 | 同上,检查 `RegistryType` | `basic`(与创建参数一致) | +| 多 AZ | 同上,检查 `EnableCosMAZ` | 与创建参数一致 | +| 删除保护 | 同上,检查 `DeletionProtection` | 与创建参数一致 | +| 公网端点 | `tccli tcr DescribeExternalEndpointStatus --region --RegistryId REGISTRY_ID` | `Status: "Opened"` | +| 白名单 | `tccli tcr DescribeSecurityPolicies --region --RegistryId REGISTRY_ID` | `SecurityPolicySet` 含目标策略 | +| 命名空间 | `tccli tcr DescribeNamespaces --region --RegistryId REGISTRY_ID` | `NamespaceList` 含目标命名空间 | +| 镜像仓库 | `tccli tcr DescribeRepositories --region --RegistryId REGISTRY_ID --NamespaceName NAMESPACE_NAME` | `RepositoryList` 含目标仓库 | +| 访问凭证 | `tccli tcr DescribeInstanceToken --region --RegistryId REGISTRY_ID` | `Tokens` 含目标令牌 | + +### 数据面(Docker) + +```bash +# 验证登录 +docker login PUBLIC_DOMAIN --username USERNAME --password TOKEN +# expected: Login Succeeded + +# 验证推送 +docker pull alpine:latest +docker tag alpine:latest PUBLIC_DOMAIN/NAMESPACE_NAME/REPO_NAME:test +docker push PUBLIC_DOMAIN/NAMESPACE_NAME/REPO_NAME:test +# expected: 推送成功 + +# 验证拉取 +docker rmi PUBLIC_DOMAIN/NAMESPACE_NAME/REPO_NAME:test +docker pull PUBLIC_DOMAIN/NAMESPACE_NAME/REPO_NAME:test +# expected: 拉取成功 +``` + +## 清理 + +> **警告**:`DeleteInstance` 会不可逆清除实例下所有命名空间、镜像仓库、Helm Chart 及关联 COS 存储桶数据。所有数据将被清除且不可恢复。如果不删除,按量计费实例将持续产生费用。 + +### 清理前状态检查 + +```bash +tccli tcr DescribeInstances --region \ + --Registryids '["REGISTRY_ID"]' +# expected: 确认是待删除的目标实例,核对 RegistryId、RegistryName +``` + +### 1. 删除访问令牌 + +```bash +tccli tcr DeleteInstanceToken --region \ + --RegistryId REGISTRY_ID \ + --TokenId TOKEN_ID +# expected: exit 0 +``` + +`TOKEN_ID` 来自 `DescribeInstanceToken` 输出中的 `Id` 字段。 + +### 2. 关闭删除保护(如启用) + +```bash +tccli tcr ModifyInstance --region \ + --RegistryId REGISTRY_ID \ + --DeletionProtection false +# expected: exit 0 +``` + +如未开启删除保护,跳过此步。 + +### 3. 删除实例 + +```bash +tccli tcr DeleteInstance --region \ + --RegistryId REGISTRY_ID +# expected: exit 0 +``` + +### 4. 验证已删除 + +```bash +tccli tcr DescribeInstances --region \ + --Registryids '["REGISTRY_ID"]' +# expected: TotalCount: 0 或 ResourceNotFound +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `CreateInstance` 返回 `InstanceNameAlreadyExists` | `tccli tcr CheckInstanceName --RegistryName INSTANCE_NAME` | 实例名已被占用(全局唯一) | 更换 `RegistryName`,建议组合公司、地域或项目缩写以确保唯一 | +| `CreateSecurityPolicy` 返回 `OperationDenied`,错误信息含"check instance occur error" | `tccli tcr DescribeExternalEndpointStatus --region --RegistryId REGISTRY_ID` 检查公网端点状态 | 公网端点尚未 `Opened`(状态为 `Opening` 或 `Closed`) | 等待公网端点 `Opened`(1-3 分钟)后重试。先执行 `DescribeExternalEndpointStatus` 确认 `Status: "Opened"`,再执行 `CreateSecurityPolicy`。若 `Status` 为 `Closed`,先执行 `ManageExternalEndpoint --Operation Create` 开启 | +| `ManageExternalEndpoint` 返回 `OperationDenied` | 检查 `Operation` 参数值 | 填了 `Open` 或 `Close` 而非 API 枚举 `Create`/`Delete` | 用 `--Operation Create` 开启,`--Operation Delete` 关闭 | +| `CreateInstanceToken` 返回 `UnauthorizedOperation` | `tccli configure list` 检查当前凭据 | 子账号缺少 `tcr:CreateInstanceToken` 权限 | 联系主账号授予 `QcloudTCRFullAccess` 或对应的 CAM Action 权限 | +| `DeleteInstance` 返回 `OperationDenied` | `tccli tcr DescribeInstances --region --Registryids '["REGISTRY_ID"]'` 检查 `DeletionProtection` 字段 | 实例开启了删除保护 | 先执行 `tccli tcr ModifyInstance --region --RegistryId REGISTRY_ID --DeletionProtection false`,再重试删除 | +| `docker login` 返回 `401 Unauthorized` | 检查 `CreateInstanceToken` 返回的 `Token` 是否正确复制 | 令牌无效、已过期或复制错误 | 重新执行 `CreateInstanceToken` 获取新令牌。确认 `--password` 使用 `Token` 字段值而非 `TokenId` | + +### 创建成功但状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 返回了 `RegistryId` 但 `DescribeInstances` 5 分钟后状态仍非 `Running` | `tccli tcr DescribeInstances --region --Registryids '["REGISTRY_ID"]'` 查看 `Status` 字段 | 后端创建缓慢(COS/依赖初始化)或卡住。`Status` 可能为 `Deploying` | 继续等待至 10 分钟。超 15 分钟则保留 `region`、`RegistryId`、`RequestId`、创建 JSON → 登录 [TCR 控制台](https://console.cloud.tencent.com/tcr/instance) 查看详细状态 → 仍无法解决则 [提交工单](https://console.cloud.tencent.com/workorder) | +| 公网端点 `Status` 长时间 `Opening`(超 5 分钟) | `tccli tcr DescribeExternalEndpointStatus --region --RegistryId REGISTRY_ID` | 后端开启公网端点卡住 | 继续等待。超 10 分钟仍 `Opening` 则保留 `RegistryId` 和 `RequestId` → 提交工单 | + +## 下一步 + +- [创建企业版实例](https://cloud.tencent.com/document/product/1141/40716) -- 完整创建选项(多 AZ、版本控制、包年包月、标签等) +- [管理命名空间](https://cloud.tencent.com/document/product/1141/38432) -- 命名空间详细管理(公开/私有切换、权限控制) +- [管理镜像仓库](https://cloud.tencent.com/document/product/1141/38433) -- 仓库创建、描述和自动创建机制 +- [销毁退还实例](../../ops/instances/delete) -- 实例生命周期管理(包年包月退还、删除保护) +- [个人版快速入门](../personal) -- 个人版(免费)操作对比 + +## 控制台替代 + +[容器镜像服务控制台 → 实例列表 → 新建](https://console.cloud.tencent.com/tcr/instance):选择地域,填写实例名、规格,按需启用多 AZ 与版本控制,勾选协议后购买。实例运行后创建命名空间、仓库,在「访问凭证」获取登录命令。 diff --git a/src/content/docs/cli/tcr/quickstart/enterprise/enterprise-tcrctl.md b/src/content/docs/cli/tcr/quickstart/enterprise/enterprise-tcrctl.md new file mode 100644 index 0000000..843d30b --- /dev/null +++ b/src/content/docs/cli/tcr/quickstart/enterprise/enterprise-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "企业版快速入门(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[企业版快速入门](https://cloud.tencent.com/document/product/1141/39287) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/quickstart/enterprise/enterprise.md b/src/content/docs/cli/tcr/quickstart/enterprise/enterprise.md new file mode 100644 index 0000000..a1e349f --- /dev/null +++ b/src/content/docs/cli/tcr/quickstart/enterprise/enterprise.md @@ -0,0 +1,638 @@ +--- +title: "企业版快速入门" +description: "· page_id `39287`" +--- + +> 对照官方:[企业版快速入门](https://cloud.tencent.com/document/product/1141/39287) · page_id `39287` + +## 概述 + +通过 `tccli tcr` 命令行完成 TCR 企业版从零到推送镜像的全流程。企业版实例提供独立的 Registry 域名(格式 `INSTANCE_NAME.tencentcloudcr.com`),支持容器镜像托管、安全扫描、跨地域同步和访问控制。 + +实例规格分三档,按需选择: + +| 规格 | RegistryType | 适用场景 | +|------|-------------|------| +| 基础版 | `basic` | 小团队或个人开发者,低并发拉取 | +| 标准版 | `standard` | 中型团队,日常 CI/CD,中等并发 | +| 高级版 | `premium` | 企业生产环境,高并发,跨地域同步 | + +本文为快速体验流程,各步骤的详细参数和高级配置请参见对应操作指南专页。 + +## 前置条件 + +- [环境准备](../../../index.md) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域 + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tcr:CreateInstance, tcr:DescribeInstances, tcr:CreateNamespace +# tcr:CreateRepository, tcr:CreateInstanceToken, tcr:CreateSecurityPolicy +# tcr:ManageExternalEndpoint, tcr:DescribeExternalEndpointStatus +# tcr:DescribeSecurityPolicies, tcr:DescribeNamespaces +# tcr:DescribeRepositories, tcr:DescribeInstanceToken +# 验证:执行 DescribeInstances 确认权限 +tccli tcr DescribeInstances --region +# expected: exit 0,返回实例列表(可为空) + +# 4. 检查 Docker 是否安装(用于推送/拉取镜像) +docker --version +# expected: Docker version 20.10 或更高 + +# 5. 确认已开通 COS 服务(企业版实例依赖 COS 存储镜像数据) +# 登录 https://console.cloud.tencent.com/cos5 确认 COS 已开通 +# 若未开通,访问控制台开通即可,无需额外 CLI 操作 +``` + +### 实例名预检 + +```bash +# 6. 确认目标实例名可用 +tccli tcr CheckInstanceName --RegistryName INSTANCE_NAME +# expected: IsValidated: true, DetailCode: 0,表示名称可用 +``` + +**预期输出**: + +```json +{ + "IsValidated": true, + "DetailCode": 0, + "RequestId": "xxxx" +} +``` + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 检查实例名可用性 | `CheckInstanceName` | 是 | +| 购买企业版实例 | `CreateInstance` | 否 | +| 查看实例状态 | `DescribeInstances` | 是 | +| 开启公网访问入口 | `ManageExternalEndpoint --Operation Create` | 否 | +| 关闭公网访问入口 | `ManageExternalEndpoint --Operation Delete` | 否 | +| 查看公网端点状态 | `DescribeExternalEndpointStatus` | 是 | +| 添加公网白名单 | `CreateSecurityPolicy` | 否 | +| 查看公网白名单 | `DescribeSecurityPolicies` | 是 | +| 创建命名空间 | `CreateNamespace` | 否 | +| 查看命名空间列表 | `DescribeNamespaces` | 是 | +| 创建镜像仓库 | `CreateRepository` | 否 | +| 查看镜像仓库列表 | `DescribeRepositories` | 是 | +| 生成访问凭证 | `CreateInstanceToken` | 否 | +| 查看访问凭证列表 | `DescribeInstanceToken` | 是 | + +## 关键字段说明 + +以下说明 `CreateInstance` 的主要参数。完整参数定义见 `tccli tcr CreateInstance --help`。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `RegistryName` | String | 是 | 全局唯一,长度 2-30 字符,字母开头。创建后不可修改,自动生成域名 `RegistryName.tencentcloudcr.com` | 重名 → `InstanceNameAlreadyExists` | +| `RegistryType` | String | 是 | `basic`(基础版)/ `standard`(标准版)/ `premium`(高级版)。创建后不可降级 | 无效值 → `InvalidParameter` | +| `RegistryChargeType` | Integer | 否 | `0` = 按量计费(后付费),`1` = 预付费(包年包月)。默认 `0` | 选错计费类型 → 计费方式与预期不符,创建后不可切换 | +| `EnableCosMAZ` | Boolean | 否 | 是否开启 COS 多 AZ 存储。默认 `false`。开启可提高数据可靠性,但存储费用较高 | 未开启 → 单 AZ 故障可能导致数据不可用 | +| `DeletionProtection` | Boolean | 否 | 默认 `false`。`true` 时需先关闭才能删除实例 | 忘开 → 可能误删生产实例及所有镜像数据 | +| `EnableCosVersioning` | Boolean | 否 | 是否开启 COS 版本控制。默认 `false` | 未开启 → 误删或覆盖镜像后无法恢复历史版本 | +| `SyncTag` | Boolean | 否 | 是否同步实例标签到 COS 桶。默认 `false` | — | + +## 操作步骤 + +### 步骤1:创建企业版实例 + +#### 选择依据 + +- **实例规格**:本文选 `basic`(基础版按量计费),适合快速体验。生产环境推荐 `premium`(高级版),支持更高并发和跨地域同步。 +- **多 AZ 存储**:开启 `EnableCosMAZ` 可跨可用区容灾,提高数据可靠性。体验场景可关闭以节省费用。 +- **删除保护**:体验场景建议 `false`,便于实验后清理。生产环境务必开启 `DeletionProtection: true`。 + +#### 最小创建(空实例,只含必填字段) + +`create-instance-minimal.json`: + +```json +{ + "RegistryName": "INSTANCE_NAME", + "RegistryType": "basic" +} +``` + +```bash +tccli tcr CreateInstance --region \ + --cli-input-json file://create-instance-minimal.json +# expected: exit 0,返回 RegistryId +``` + +**预期输出**: + +```json +{ + "RegistryId": "tcr-example", + "RequestId": "xxxx" +} +``` + +记录返回的 `RegistryId`(下文以 `REGISTRY_ID` 代指),后续所有实例操作均依赖此 ID。 + +#### 增强配置(加多 AZ、版本控制、删除保护) + +`create-instance-enhanced.json`: + +```json +{ + "RegistryName": "INSTANCE_NAME", + "RegistryType": "basic", + "RegistryChargeType": 0, + "SyncTag": false, + "EnableCosMAZ": true, + "DeletionProtection": true, + "EnableCosVersioning": true +} +``` + +```bash +tccli tcr CreateInstance --region \ + --cli-input-json file://create-instance-enhanced.json +# expected: exit 0,返回 RegistryId +``` + +#### 轮询实例状态 + +实例创建是异步操作。轮询直到状态为 `Running`: + +```bash +tccli tcr DescribeInstances --region \ + --Registryids '["REGISTRY_ID"]' +# expected: Status: "Running" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Registries": [ + { + "RegistryId": "tcr-example", + "RegistryName": "INSTANCE_NAME", + "RegistryType": "basic", + "Status": "Running", + "PublicDomain": "INSTANCE_NAME.tencentcloudcr.com", + "RegionName": "ap-guangzhou", + "InternalEndpoint": "10.0.0.0", + "PayMod": 0, + "DeletionProtection": false, + "EnableCosMAZ": true, + "EnableCosVersioning": true, + "CreatedAt": "2025-01-01T00:00:00+08:00" + } + ] +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `INSTANCE_NAME` | 实例名称 | 全局唯一,2-30 字符,字母开头 | 自定义,建议组合公司、地域或项目缩写 | +| `REGISTRY_ID` | 实例 ID | 由 CreateInstance 返回 | 记录上一步输出中的 `RegistryId` 字段 | +| `REGION` | 地域 | 如 `ap-guangzhou` | `tccli configure list` 查看已配置的 region | + +> 最长等待约 2 分钟(基础版)。超时参见 [排障](#排障)。 + +### 步骤2:配置公网访问 + +实例创建后**默认拒绝全部公网及内网访问**,必须先配置访问策略才能推送/拉取镜像。 + +#### 开启公网访问端点 + +```bash +tccli tcr ManageExternalEndpoint --region \ + --RegistryId REGISTRY_ID \ + --Operation Create +# expected: exit 0,返回 RegistryId +``` + +**预期输出**: + +```json +{ + "RegistryId": "tcr-example", + "RequestId": "xxxx" +} +``` + +> `ManageExternalEndpoint` 的 `Operation` 参数取值为 `Create`(开启)或 `Delete`(关闭),非 `Open`/`Close`。 + +#### 轮询公网端点状态 + +```bash +tccli tcr DescribeExternalEndpointStatus --region \ + --RegistryId REGISTRY_ID +# expected: Status: "Opened" +``` + +**预期输出**: + +```json +{ + "Status": "Opened", + "Reason": "", + "RequestId": "xxxx" +} +``` + +状态变化:`Opening` 到 `Opened`,约需 1-3 分钟。 + +#### 添加公网访问白名单 + +公网端点 `Opened` 后,添加白名单放通访问来源 IP: + +```bash +tccli tcr CreateSecurityPolicy --region \ + --RegistryId REGISTRY_ID \ + --CidrBlock CIDR_BLOCK \ + --Description "POLICY_DESC" +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RegistryId": "tcr-example", + "RequestId": "xxxx" +} +``` + +> 公网端点未 `Opened` 时调用 `CreateSecurityPolicy` 会返回 `OperationDenied`。务必先确认 `DescribeExternalEndpointStatus` 返回 `Status: "Opened"` 再执行。排障参见 [排障](#排障)。 + +> 安全建议:不要直接使用 `0.0.0.0/0` 放通全部来源公网访问。完成内网配置后建议尽快关闭公网入口。 + +#### 查看白名单 + +```bash +tccli tcr DescribeSecurityPolicies --region \ + --RegistryId REGISTRY_ID +# expected: SecurityPolicySet 含目标策略 +``` + +**预期输出**: + +```json +{ + "SecurityPolicySet": [ + { + "PolicyIndex": 0, + "Description": "POLICY_DESC", + "CidrBlock": "CIDR_BLOCK", + "PolicyVersion": "1" + } + ], + "RequestId": "xxxx" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `CIDR_BLOCK` | 白名单 IP 或地址段 | 如 `1.2.3.4/32` 或 `10.0.0.0/8` | 当前机器的出口 IP | +| `POLICY_DESC` | 策略备注 | 任意字符串 | 自定义 | + +### 步骤3:创建命名空间 + +命名空间用于组织管理实例内的镜像仓库,不直接存储容器镜像。可映射为企业内团队、项目或其他自定义层级。 + +#### 选择依据 + +- **访问级别**:`IsPublic: true` = 公开(命名空间内仓库允许匿名拉取),`IsPublic: false` = 私有(需凭据访问)。本文选 `true` 方便体验。 + +```bash +tccli tcr CreateNamespace --region \ + --RegistryId REGISTRY_ID \ + --NamespaceName NAMESPACE_NAME \ + --IsPublic true +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RequestId": "xxxx" +} +``` + +#### 查看命名空间 + +```bash +tccli tcr DescribeNamespaces --region \ + --RegistryId REGISTRY_ID +# expected: NamespaceList 含目标命名空间 +``` + +**预期输出**: + +```json +{ + "NamespaceList": [ + { + "Name": "ns-example", + "NamespaceId": 1, + "Public": true, + "CreationTime": "2025-01-01T00:00:00.000Z" + } + ], + "TotalCount": 1, + "RequestId": "xxxx" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `NAMESPACE_NAME` | 命名空间名称 | 小写字母、数字、连字符,实例内唯一 | 自定义,建议用团队或项目命名 | + +### 步骤4:创建镜像仓库(可选) + +镜像仓库可手动创建,也可在 `docker push` 时自动创建。手动创建可预设描述和访问级别。 + +`create-repository.json`: + +```json +{ + "RegistryId": "REGISTRY_ID", + "NamespaceName": "NAMESPACE_NAME", + "RepositoryName": "REPO_NAME", + "BriefDescription": "BRIEF_DESC", + "Description": "DESCRIPTION" +} +``` + +```bash +tccli tcr CreateRepository --region \ + --cli-input-json file://create-repository.json +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RequestId": "xxxx" +} +``` + +#### 查看镜像仓库 + +```bash +tccli tcr DescribeRepositories --region \ + --RegistryId REGISTRY_ID \ + --NamespaceName NAMESPACE_NAME +# expected: RepositoryList 含目标仓库 +``` + +**预期输出**: + +```json +{ + "RepositoryList": [ + { + "Name": "ns-example/repo-example", + "Namespace": "ns-example", + "Public": true, + "CreationTime": "2025-01-01 00:00:00.000000 +0000 UTC" + } + ], + "TotalCount": 1, + "RequestId": "xxxx" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `REPO_NAME` | 仓库名称 | 小写字母、数字、连字符、下划线、正斜杠,支持多级路径如 `team/app` | 自定义 | +| `BRIEF_DESC` | 简短描述 | 任意字符串 | 自定义 | +| `DESCRIPTION` | 详细描述 | 支持 Markdown 语法 | 自定义 | + +### 步骤5:创建访问凭证 + +#### 选择依据 + +- **TokenType**:`longterm`(长期,有效期 87600 小时约 10 年)适用于 CI/CD 或长期开发环境;`temp`(临时,有效期 1 小时)适用于一次性操作。本文选 `longterm`。 + +```bash +tccli tcr CreateInstanceToken --region \ + --RegistryId REGISTRY_ID \ + --TokenType longterm \ + --Desc "TOKEN_DESC" +# expected: exit 0,返回 Username 和 Token +``` + +**预期输出**: + +```json +{ + "Username": "100000000000", + "Token": "eyJhbGciOiJSUzI1NiIs...", + "TokenId": "tkn-example", + "ExpTime": 2096954604521, + "RequestId": "xxxx" +} +``` + +> 立即记录 `Token` 字段——它只在创建时返回一次,关闭终端后将无法再次查看。若丢失,需重新创建新令牌。Docker 登录时用 `Token` 值作为密码。 + +#### 查看访问凭证列表 + +```bash +tccli tcr DescribeInstanceToken --region \ + --RegistryId REGISTRY_ID +# expected: Tokens 列表含目标令牌 +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Tokens": [ + { + "Id": "tkn-example", + "Desc": "TOKEN_DESC", + "Enabled": true, + "CreatedAt": "2025-01-01T00:00:00+08:00" + } + ], + "RequestId": "xxxx" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `TOKEN_DESC` | 令牌描述 | 任意字符串 | 自定义,如 `dev-token-2025` | + +### 步骤6:推送与拉取镜像 + +获取实例公网域名: + +```bash +tccli tcr DescribeInstances --region \ + --Registryids '["REGISTRY_ID"]' +# expected: PublicDomain 字段为 INSTANCE_NAME.tencentcloudcr.com +``` + +记下 `PublicDomain` 字段(下文以 `PUBLIC_DOMAIN` 代指),其格式为 `INSTANCE_NAME.tencentcloudcr.com`。 + +#### 登录 Registry + +```bash +docker login PUBLIC_DOMAIN --username USERNAME --password TOKEN +# expected: Login Succeeded +``` + +`USERNAME` 和 `TOKEN` 来自步骤5 `CreateInstanceToken` 的输出。 + +#### 推送镜像 + +```bash +docker pull nginx:latest +docker tag nginx:latest PUBLIC_DOMAIN/NAMESPACE_NAME/REPO_NAME:latest +docker push PUBLIC_DOMAIN/NAMESPACE_NAME/REPO_NAME:latest +# expected: push 进度条结束,显示 digest: sha256:xxxx +``` + +#### 拉取镜像 + +```bash +docker pull PUBLIC_DOMAIN/NAMESPACE_NAME/REPO_NAME:latest +# expected: pull 进度条结束,显示 Status: Downloaded newer image +``` + +| 占位符 | 说明 | 获取方式 | +|--------|------|---------| +| `PUBLIC_DOMAIN` | 实例公网域名 | `DescribeInstances` 输出的 `PublicDomain` 字段,格式 `INSTANCE_NAME.tencentcloudcr.com` | +| `USERNAME` | 登录用户名 | `CreateInstanceToken` 输出的 `Username` 字段 | +| `TOKEN` | 登录密码/令牌 | `CreateInstanceToken` 输出的 `Token` 字段 | + +## 验证 + +### 控制面(tccli) + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 实例状态 | `tccli tcr DescribeInstances --region --Registryids '["REGISTRY_ID"]'` | `Status: "Running"` | +| 实例域名 | 同上,检查 `PublicDomain` | `INSTANCE_NAME.tencentcloudcr.com` | +| 规格 | 同上,检查 `RegistryType` | `basic`(与创建参数一致) | +| 多 AZ | 同上,检查 `EnableCosMAZ` | 与创建参数一致 | +| 删除保护 | 同上,检查 `DeletionProtection` | 与创建参数一致 | +| 公网端点 | `tccli tcr DescribeExternalEndpointStatus --region --RegistryId REGISTRY_ID` | `Status: "Opened"` | +| 白名单 | `tccli tcr DescribeSecurityPolicies --region --RegistryId REGISTRY_ID` | `SecurityPolicySet` 含目标策略 | +| 命名空间 | `tccli tcr DescribeNamespaces --region --RegistryId REGISTRY_ID` | `NamespaceList` 含目标命名空间 | +| 镜像仓库 | `tccli tcr DescribeRepositories --region --RegistryId REGISTRY_ID --NamespaceName NAMESPACE_NAME` | `RepositoryList` 含目标仓库 | +| 访问凭证 | `tccli tcr DescribeInstanceToken --region --RegistryId REGISTRY_ID` | `Tokens` 含目标令牌 | + +### 数据面(Docker) + +```bash +# 验证登录 +docker login PUBLIC_DOMAIN --username USERNAME --password TOKEN +# expected: Login Succeeded + +# 验证推送 +docker pull alpine:latest +docker tag alpine:latest PUBLIC_DOMAIN/NAMESPACE_NAME/REPO_NAME:test +docker push PUBLIC_DOMAIN/NAMESPACE_NAME/REPO_NAME:test +# expected: 推送成功 + +# 验证拉取 +docker rmi PUBLIC_DOMAIN/NAMESPACE_NAME/REPO_NAME:test +docker pull PUBLIC_DOMAIN/NAMESPACE_NAME/REPO_NAME:test +# expected: 拉取成功 +``` + +## 清理 + +> **警告**:`DeleteInstance` 会不可逆清除实例下所有命名空间、镜像仓库、Helm Chart 及关联 COS 存储桶数据。所有数据将被清除且不可恢复。如果不删除,按量计费实例将持续产生费用。 + +### 清理前状态检查 + +```bash +tccli tcr DescribeInstances --region \ + --Registryids '["REGISTRY_ID"]' +# expected: 确认是待删除的目标实例,核对 RegistryId、RegistryName +``` + +### 1. 删除访问令牌 + +```bash +tccli tcr DeleteInstanceToken --region \ + --RegistryId REGISTRY_ID \ + --TokenId TOKEN_ID +# expected: exit 0 +``` + +`TOKEN_ID` 来自 `DescribeInstanceToken` 输出中的 `Id` 字段。 + +### 2. 关闭删除保护(如启用) + +```bash +tccli tcr ModifyInstance --region \ + --RegistryId REGISTRY_ID \ + --DeletionProtection false +# expected: exit 0 +``` + +如未开启删除保护,跳过此步。 + +### 3. 删除实例 + +```bash +tccli tcr DeleteInstance --region \ + --RegistryId REGISTRY_ID +# expected: exit 0 +``` + +### 4. 验证已删除 + +```bash +tccli tcr DescribeInstances --region \ + --Registryids '["REGISTRY_ID"]' +# expected: TotalCount: 0 或 ResourceNotFound +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `CreateInstance` 返回 `InstanceNameAlreadyExists` | `tccli tcr CheckInstanceName --RegistryName INSTANCE_NAME` | 实例名已被占用(全局唯一) | 更换 `RegistryName`,建议组合公司、地域或项目缩写以确保唯一 | +| `CreateSecurityPolicy` 返回 `OperationDenied`,错误信息含"check instance occur error" | `tccli tcr DescribeExternalEndpointStatus --region --RegistryId REGISTRY_ID` 检查公网端点状态 | 公网端点尚未 `Opened`(状态为 `Opening` 或 `Closed`) | 等待公网端点 `Opened`(1-3 分钟)后重试。先执行 `DescribeExternalEndpointStatus` 确认 `Status: "Opened"`,再执行 `CreateSecurityPolicy`。若 `Status` 为 `Closed`,先执行 `ManageExternalEndpoint --Operation Create` 开启 | +| `ManageExternalEndpoint` 返回 `OperationDenied` | 检查 `Operation` 参数值 | 填了 `Open` 或 `Close` 而非 API 枚举 `Create`/`Delete` | 用 `--Operation Create` 开启,`--Operation Delete` 关闭 | +| `CreateInstanceToken` 返回 `UnauthorizedOperation` | `tccli configure list` 检查当前凭据 | 子账号缺少 `tcr:CreateInstanceToken` 权限 | 联系主账号授予 `QcloudTCRFullAccess` 或对应的 CAM Action 权限 | +| `DeleteInstance` 返回 `OperationDenied` | `tccli tcr DescribeInstances --region --Registryids '["REGISTRY_ID"]'` 检查 `DeletionProtection` 字段 | 实例开启了删除保护 | 先执行 `tccli tcr ModifyInstance --region --RegistryId REGISTRY_ID --DeletionProtection false`,再重试删除 | +| `docker login` 返回 `401 Unauthorized` | 检查 `CreateInstanceToken` 返回的 `Token` 是否正确复制 | 令牌无效、已过期或复制错误 | 重新执行 `CreateInstanceToken` 获取新令牌。确认 `--password` 使用 `Token` 字段值而非 `TokenId` | + +### 创建成功但状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 返回了 `RegistryId` 但 `DescribeInstances` 5 分钟后状态仍非 `Running` | `tccli tcr DescribeInstances --region --Registryids '["REGISTRY_ID"]'` 查看 `Status` 字段 | 后端创建缓慢(COS/依赖初始化)或卡住。`Status` 可能为 `Deploying` | 继续等待至 10 分钟。超 15 分钟则保留 `region`、`RegistryId`、`RequestId`、创建 JSON → 登录 [TCR 控制台](https://console.cloud.tencent.com/tcr/instance) 查看详细状态 → 仍无法解决则 [提交工单](https://console.cloud.tencent.com/workorder) | +| 公网端点 `Status` 长时间 `Opening`(超 5 分钟) | `tccli tcr DescribeExternalEndpointStatus --region --RegistryId REGISTRY_ID` | 后端开启公网端点卡住 | 继续等待。超 10 分钟仍 `Opening` 则保留 `RegistryId` 和 `RequestId` → 提交工单 | + +## 下一步 + +- [创建企业版实例](https://cloud.tencent.com/document/product/1141/40716) -- 完整创建选项(多 AZ、版本控制、包年包月、标签等) +- [管理命名空间](https://cloud.tencent.com/document/product/1141/38432) -- 命名空间详细管理(公开/私有切换、权限控制) +- [管理镜像仓库](https://cloud.tencent.com/document/product/1141/38433) -- 仓库创建、描述和自动创建机制 +- [销毁退还实例](../../ops/instances/delete) -- 实例生命周期管理(包年包月退还、删除保护) +- [个人版快速入门](../personal) -- 个人版(免费)操作对比 + +## 控制台替代 + +[容器镜像服务控制台 → 实例列表 → 新建](https://console.cloud.tencent.com/tcr/instance):选择地域,填写实例名、规格,按需启用多 AZ 与版本控制,勾选协议后购买。实例运行后创建命名空间、仓库,在「访问凭证」获取登录命令。 diff --git a/src/content/docs/cli/tcr/quickstart/personal-tcrctl.md b/src/content/docs/cli/tcr/quickstart/personal-tcrctl.md new file mode 100644 index 0000000..be1ce3e --- /dev/null +++ b/src/content/docs/cli/tcr/quickstart/personal-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "个人版快速入门(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[个人版快速入门](https://cloud.tencent.com/document/product/1141/63910) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/quickstart/personal.md b/src/content/docs/cli/tcr/quickstart/personal.md new file mode 100644 index 0000000..27aad4d --- /dev/null +++ b/src/content/docs/cli/tcr/quickstart/personal.md @@ -0,0 +1,370 @@ +--- +title: "个人版快速入门" +description: "· page_id `63910`" +--- + +> 对照官方:[个人版快速入门](https://cloud.tencent.com/document/product/1141/63910) · page_id `63910` + +## 概述 + +通过 `tccli tcr` 命令行开通并使用 TCR 个人版。个人版为共享实例,开箱即用,无需创建实例。登录凭据为腾讯云账号 ID + 控制台设置的固定密码,域名固定为 `ccr.ccs.tencentyun.com`,在中国大陆仅广州地域部署。 + +个人版与[企业版](../enterprise)的核心区别: + +| 维度 | 个人版 | 企业版 | +|------|--------|--------| +| 资源层级 | 账号级共享实例,无需创建实例 | 实例级,需 `CreateInstance` 购买独立实例 | +| API 后缀 | `*Personal` 系列(如 `CreateNamespacePersonal`) | 通用 API(如 `CreateNamespace`) | +| `RegistryId` | 不需要 | 始终必填 | +| 命名空间唯一性 | 全局唯一(跨所有用户) | 实例内唯一 | +| 域名 | `ccr.ccs.tencentyun.com`(固定) | `.tencentcloudcr.com` | +| 登录用户 | 腾讯云账号 ID(数字串) | 实例访问凭证(临时令牌或长期 Token) | +| 登录密码 | 账号级固定密码,控制台初始化 | 按实例管理,支持临时令牌和长期凭证 | + +## 前置条件 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey 已配置,region 设为 ap-guangzhou + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tcr:CreateNamespacePersonal, tcr:DescribeNamespacePersonal +# tcr:CreateRepositoryPersonal, tcr:DescribeRepositoryPersonal +# tcr:DescribeUserQuotaPersonal +# 验证:执行 DescribeNamespacePersonal 确认权限 +tccli tcr DescribeNamespacePersonal --region \ + --Namespace "" --Limit 1 --Offset 0 +# expected: exit 0(空列表或已有命名空间数据) + +# 4. 检查 Docker 安装(推送/拉取镜像需要) +docker --version +# expected: Docker version XX.XX.x 或以上 +``` + +### 资源检查 + +```bash +# 5. 检查个人版配额 +tccli tcr DescribeUserQuotaPersonal --region +# expected: exit 0,Data.LimitInfo 中 namespace 和 repo 配额未达上限 +``` + +**预期输出**: + +```json +{ + "Data": { + "LimitInfo": [ + {"Username": "100012345678", "Type": "namespace", "Value": 2000}, + {"Type": "repo", "Value": 10000} + ] + }, + "RequestId": "eac6b301-a322-493a-8e36-83b295459397" +} +``` + +```bash +# 6. 确认个人版密码已在控制台初始化(无 CLI 命令) +# 登录 容器镜像服务控制台 → 实例管理 → 个人版实例卡片 → 确认密码已设置 +# 未设置密码时 docker login 会失败,参见排障节 +``` + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli / docker | 幂等 | +|-----------|-----------|:--:| +| 初始化密码 | 控制台操作,无 CLI 命令 | — | +| 登录实例 | `docker login ccr.ccs.tencentyun.com` | 是 | +| 创建命名空间 | `CreateNamespacePersonal` | 否 | +| 查看命名空间 | `DescribeNamespacePersonal` | 是 | +| 创建镜像仓库 | `CreateRepositoryPersonal` | 否 | +| 推送镜像 | `docker tag` + `docker push` | 是 | +| 拉取镜像 | `docker pull` | 是 | +| 查询配额 | `DescribeUserQuotaPersonal` | 是 | + +## 操作步骤 + +### 步骤 1:初始化密码 + +个人版密码必须在控制台初始化,无对应 CLI 命令。 + +> 登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr),进入 **实例管理** 页面,在个人版实例卡片上单击"初始化密码"并按提示设置。忘记密码后可通过 **更多 > 重置登录密码** 重置。 + +### 步骤 2:Docker 登录个人版 + +以腾讯云账号 ID 作为用户名登录。登录前需确保已在控制台初始化密码。 + +```bash +docker login ccr.ccs.tencentyun.com --username=TENCENT_CLOUD_ACCOUNT_ID +# expected: 提示输入密码,正确输入后显示 "Login Succeeded" +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `TENCENT_CLOUD_ACCOUNT_ID` | 腾讯云账号 ID(纯数字串) | 必须,与密码初始化时的账号一致 | [腾讯云控制台-账号信息](https://console.cloud.tencent.com/developer) 查看 账号 ID | + +### 步骤 3:创建命名空间 + +#### 选择依据 + +- **命名空间名称**:个人版为共享实例,命名空间名称**全局唯一**(跨所有用户)。建议以团队或项目命名。已被其他用户占用的名称无法创建,创建失败时更换一个未被占用的名称重试。 +- **地域**:个人版在中国大陆仅在广州部署,统一使用 `--region ap-guangzhou`。 + +#### 最小创建 + +`CreateNamespacePersonal` 仅含一个必填参数 `--Namespace`: + +```bash +tccli tcr CreateNamespacePersonal --region ap-guangzhou \ + --Namespace NAMESPACE_NAME +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "b2b28287-d220-482d-a9d1-fbc196158c52" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `NAMESPACE_NAME` | 命名空间名称 | 全局唯一,1-63 字符,支持小写字母、数字、`-`、`_` | 自定义,建议以团队/项目命名 | + +#### 增强配置 + +`CreateNamespacePersonal` 只有一个必填参数,无需增强配置。若需更新命名空间属性,可通过控制台操作。 + +### 步骤 4:创建镜像仓库(可选) + +> 此步骤为可选。命名空间创建后,直接通过 `docker push` 推送镜像时,若目标仓库不存在,Docker Registry 会自动创建对应仓库。 + +#### 选择依据 + +- **参数命名**:个人版 API 使用 `--RepoName`(区别于企业版的 `--RepositoryName`)。格式为 `命名空间/仓库名`(如 `NAMESPACE_NAME/REPO_NAME`)。注意这是**单个参数**,不是两个独立参数。 +- **仓库类型**:`--Public` 为 Integer 类型,`0` 表示私有(需登录后拉取),`1` 表示公有(任何人可匿名拉取)。注意区分企业版 API 的 `--IsPublic`(Boolean 类型)。 +- **描述**:`--Description` 为可选参数,支持 Markdown 格式。 + +#### 最小创建(只含必填字段) + +```bash +tccli tcr CreateRepositoryPersonal --region ap-guangzhou \ + --RepoName "NAMESPACE_NAME/REPO_NAME" \ + --Public 0 +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "66d972a4-a887-406b-b6b6-52d213271411" +} +``` + +#### 增强配置(加描述) + +```bash +tccli tcr CreateRepositoryPersonal --region ap-guangzhou \ + --RepoName "NAMESPACE_NAME/REPO_NAME" \ + --Public 0 \ + --Description "REPO_DESCRIPTION" +# expected: exit 0,返回 RequestId +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `NAMESPACE_NAME` | 命名空间名称 | 必须已存在(步骤 3 创建) | `DescribeNamespacePersonal` | +| `REPO_NAME` | 镜像仓库名称 | 1-200 字符,仅小写字母、数字、`.`、`_`、`-`,不能以分隔符开头/结尾,不支持多级路径 | 自定义 | +| `REPO_DESCRIPTION` | 仓库描述 | 可选,支持 Markdown | 自定义 | + +### 步骤 5:推送镜像 + +以 Docker Hub 官方 Nginx 镜像为例。先 `docker tag` 标记镜像地址,再 `docker push` 推送。若目标仓库不存在,推送时会自动创建。 + +```bash +docker pull nginx:latest +# expected: exit 0,拉取成功 + +docker tag nginx:latest ccr.ccs.tencentyun.com/NAMESPACE_NAME/REPO_NAME:latest +# expected: exit 0,无输出 + +docker push ccr.ccs.tencentyun.com/NAMESPACE_NAME/REPO_NAME:latest +# expected: exit 0,推送成功,显示各 layer 的 Pushed 状态 +``` + +**预期输出**(docker push): + +```text +The push refers to repository [ccr.ccs.tencentyun.com/NAMESPACE_NAME/REPO_NAME] +5f70bf18a086: Pushed +2b0d8e4cce8e: Pushed +latest: digest: sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx size: 527 +``` + +### 步骤 6:拉取镜像 + +```bash +docker pull ccr.ccs.tencentyun.com/NAMESPACE_NAME/REPO_NAME:latest +# expected: exit 0,拉取成功或显示 "Image is up to date" +``` + +**预期输出**(docker pull): + +```text +latest: Pulling from NAMESPACE_NAME/REPO_NAME +Digest: sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +Status: Image is up to date for ccr.ccs.tencentyun.com/NAMESPACE_NAME/REPO_NAME:latest +ccr.ccs.tencentyun.com/NAMESPACE_NAME/REPO_NAME:latest +``` + +## 验证 + +### 控制面(tccli) + +```bash +# 1. 确认命名空间已创建 +tccli tcr DescribeNamespacePersonal --region ap-guangzhou \ + --Namespace NAMESPACE_NAME --Limit 20 --Offset 0 +# expected: exit 0,Data.NamespaceInfo 中包含目标命名空间 +``` + +**预期输出**: + +```json +{ + "Data": { + "NamespaceCount": 1, + "NamespaceInfo": [ + { + "Namespace": "ns-example", + "CreationTime": "2026-06-16 15:23:34", + "RepoCount": 0 + } + ] + }, + "RequestId": "eac6b301-a322-493a-8e36-83b295459397" +} +``` + +| 维度 | 命令 | 预期 | +|------|------|------| +| 命名空间存在性 | `DescribeNamespacePersonal --Namespace NAMESPACE_NAME --Limit 20 --Offset 0` | `Data.NamespaceCount` >= 1,`NamespaceInfo` 中包含目标 `Namespace` | +| 命名空间唯一性 | 同上 | 返回成功即表示名称未被占用 | +| 配额剩余 | `DescribeUserQuotaPersonal` | `Type: namespace` 的 `Value` 小于配额上限 | + +> **注意**:`DescribeNamespacePersonal` 必须同时指定 `--Namespace`、`--Limit`、`--Offset` 三个参数,任一缺失会返回参数错误。 + +### 数据面(Docker) + +```bash +# 2. 确认登录有效 +docker login ccr.ccs.tencentyun.com --username=TENCENT_CLOUD_ACCOUNT_ID +# expected: 密码交互提示;输入密码后显示 "Login Succeeded" + +# 3. 确认可拉取已推送的镜像 +docker pull ccr.ccs.tencentyun.com/NAMESPACE_NAME/REPO_NAME:latest +# expected: 拉取成功或 "Image is up to date" +``` + +## 清理 + +> **注意**:删除命名空间会级联删除其下所有镜像仓库及镜像 Tag。个人版删除操作不可逆,执行前务必通过 `DescribeNamespacePersonal` 确认目标命名空间名称。 + +### 数据面(Docker) + +数据面清理在控制面之前,避免引用已删除的资源。 + +```bash +# 1. 删除本地镜像(可选,释放磁盘空间) +docker rmi ccr.ccs.tencentyun.com/NAMESPACE_NAME/REPO_NAME:latest +# expected: Untagged 或 Deleted + +# 2. 退出登录(可选) +docker logout ccr.ccs.tencentyun.com +# expected: Removing login credentials for ccr.ccs.tencentyun.com +``` + +### 控制面(tccli) + +#### 1. 清理前状态检查 + +```bash +tccli tcr DescribeNamespacePersonal --region ap-guangzhou \ + --Namespace NAMESPACE_NAME --Limit 20 --Offset 0 +# 确认是待删除的目标命名空间,记录命名空间名称和 RepoCount +``` + +#### 2. 删除镜像仓库 + +```bash +# 先查询命名空间下的仓库列表 +tccli tcr DescribeRepositoryPersonal --region ap-guangzhou \ + --RepoName "NAMESPACE_NAME/" --Limit 20 --Offset 0 +# expected: 返回命名空间下的仓库列表 + +# 逐个删除仓库 +tccli tcr DeleteRepositoryPersonal --region ap-guangzhou \ + --RepoName "NAMESPACE_NAME/REPO_NAME" +# expected: exit 0,返回 RequestId +``` + +#### 3. 删除命名空间 + +```bash +tccli tcr DeleteNamespacePersonal --region ap-guangzhou \ + --Namespace NAMESPACE_NAME +# ⚠️ 会级联删除命名空间下所有仓库和镜像 +# expected: exit 0,返回 RequestId +``` + +#### 4. 验证已删除 + +```bash +tccli tcr DescribeNamespacePersonal --region ap-guangzhou \ + --Namespace NAMESPACE_NAME --Limit 20 --Offset 0 +# expected: Data.NamespaceCount 为 0 或返回 InvalidParameter(命名空间不存在) +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `CreateNamespacePersonal` 返回命名空间已存在 | 更换一个 `--Namespace` 值重试,或用 `DescribeNamespacePersonal` 查询已有命名空间 | 命名空间名称已被其他用户占用(个人版全局唯一) | 更换一个新的 `--Namespace` 名称重试 | +| `CreateNamespacePersonal` 或 `CreateRepositoryPersonal` 返回 `AuthFailure` | `tccli configure list` 检查 secretId/secretKey 配置 | 密钥未配置或已过期 | 执行 `tccli configure` 重新配置密钥 | +| `CreateNamespacePersonal` 或 `CreateRepositoryPersonal` 返回 `UnauthorizedOperation` | `tccli tcr DescribeNamespacePersonal --region ap-guangzhou --Namespace "" --Limit 1 --Offset 0` 验证基本权限 | CAM 权限不足,缺少 `tcr:*Personal` Action | 联系 CAM 管理员授予 `tcr:CreateNamespacePersonal` 和 `tcr:CreateRepositoryPersonal` 权限 | +| `DescribeNamespacePersonal` 返回参数错误 | 检查命令是否同时指定了 `--Namespace`、`--Limit`、`--Offset` 三个参数 | 缺少必填参数。个人版此 API 的三个参数全为必填 | 同时指定三个参数:`--Namespace NAMESPACE_NAME --Limit 20 --Offset 0` | +| `CreateRepositoryPersonal` 返回 `InvalidParameter` | 检查 `--RepoName` 格式是否为 `NAMESPACE_NAME/REPO_NAME` | `--RepoName` 格式不正确或命名空间不存在 | 确保格式为 `命名空间/仓库名`,且命名空间已创建 | +| `CreateRepositoryPersonal` 返回 `InvalidParameter`,提示 Public 类型错误 | 检查 `--Public` 参数值 | 传入了非 Integer 值(如 "false" 或 "true"),个人版 `--Public` 为 Integer(`0`/`1`),不是 Boolean | 使用 `--Public 0`(私有)或 `--Public 1`(公有) | +| `docker login` 返回 `unauthorized: authentication required` | `tccli tcr DescribeUserQuotaPersonal --region ap-guangzhou` 确认服务已开通 | 密码未初始化或账号 ID 错误 | 登录 [TCR 控制台](https://console.cloud.tencent.com/tcr) 确认密码已初始化;核对中国站/国际站账号 ID | +| `docker push` 返回 `denied: requested access to the resource is denied` | 重新执行 `docker login` | 登录凭证过期或未登录 | 执行 `docker login ccr.ccs.tencentyun.com --username=TENCENT_CLOUD_ACCOUNT_ID` 重新登录后重试 | +| `DescribeUserQuotaPersonal` 返回配额不足 | 查看 `Data.LimitInfo` 中 `Type: namespace` 或 `Type: repo` 的 `Value` | 命名空间或仓库数量已达上限(此为环境限制,非命令错误) | 清理不再使用的命名空间或仓库后重试 | + +### 创建成功但状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 命名空间创建成功但 `DescribeNamespacePersonal` 查不到 | 检查是否用正确的 `--Namespace` 参数查询;确认指定了 `--Limit` 和 `--Offset` | 查询参数不正确或分页超出范围 | 同时指定 `--Namespace NAMESPACE_NAME --Limit 20 --Offset 0` 重试 | +| `docker push` 成功但 `docker pull` 拉不到 | 先 `docker login` 确认登录状态,检查是否为私有仓库 | 私有仓库(`--Public 0`)需登录后才能拉取;或未指定正确的 tag | 若为私有仓库,确保已 `docker login`;检查 tag 名称拼写 | + +## 下一步 + +- [企业版快速入门](../enterprise) — 创建独立 TCR 实例并使用高级功能 +- [更新登录密码](../../ops/personal-edition/update-password) — 管理个人版全局密码 +- [设置镜像清理](../../ops/personal-edition/image-cleanup) — 镜像版本自动清理策略 +- [TCR 常用 API 概览](https://cloud.tencent.com/document/api/1141/41570) — 查看完整 API 列表 +- [TKE 集群使用 TCR 推送拉取镜像](https://cloud.tencent.com/document/product/457/118319) — 配合 TKE 使用 + +## 控制台替代 + +[容器镜像服务控制台 → 实例管理](https://console.cloud.tencent.com/tcr):在个人版实例卡片上单击**初始化密码**设置登录密码 → [获取腾讯云账号 ID](https://console.cloud.tencent.com/developer) → 使用 `docker login ccr.ccs.tencentyun.com` 登录 → 在**命名空间**页面创建命名空间 → **镜像仓库**页面创建仓库或直接 `docker push` 自动创建 → 在**镜像版本**页面查看已推送的 Tag。 diff --git a/src/content/docs/cli/tcr/quickstart/personal/personal-tcrctl.md b/src/content/docs/cli/tcr/quickstart/personal/personal-tcrctl.md new file mode 100644 index 0000000..be1ce3e --- /dev/null +++ b/src/content/docs/cli/tcr/quickstart/personal/personal-tcrctl.md @@ -0,0 +1,11 @@ +--- +title: "个人版快速入门(产品 CLI)" +description: "产品 CLI 命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tcrctl` integration pending. See the [tccli operation page](.-tcrctl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[个人版快速入门](https://cloud.tencent.com/document/product/1141/63910) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +产品 CLI 命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tcr/quickstart/personal/personal.md b/src/content/docs/cli/tcr/quickstart/personal/personal.md new file mode 100644 index 0000000..27aad4d --- /dev/null +++ b/src/content/docs/cli/tcr/quickstart/personal/personal.md @@ -0,0 +1,370 @@ +--- +title: "个人版快速入门" +description: "· page_id `63910`" +--- + +> 对照官方:[个人版快速入门](https://cloud.tencent.com/document/product/1141/63910) · page_id `63910` + +## 概述 + +通过 `tccli tcr` 命令行开通并使用 TCR 个人版。个人版为共享实例,开箱即用,无需创建实例。登录凭据为腾讯云账号 ID + 控制台设置的固定密码,域名固定为 `ccr.ccs.tencentyun.com`,在中国大陆仅广州地域部署。 + +个人版与[企业版](../enterprise)的核心区别: + +| 维度 | 个人版 | 企业版 | +|------|--------|--------| +| 资源层级 | 账号级共享实例,无需创建实例 | 实例级,需 `CreateInstance` 购买独立实例 | +| API 后缀 | `*Personal` 系列(如 `CreateNamespacePersonal`) | 通用 API(如 `CreateNamespace`) | +| `RegistryId` | 不需要 | 始终必填 | +| 命名空间唯一性 | 全局唯一(跨所有用户) | 实例内唯一 | +| 域名 | `ccr.ccs.tencentyun.com`(固定) | `.tencentcloudcr.com` | +| 登录用户 | 腾讯云账号 ID(数字串) | 实例访问凭证(临时令牌或长期 Token) | +| 登录密码 | 账号级固定密码,控制台初始化 | 按实例管理,支持临时令牌和长期凭证 | + +## 前置条件 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey 已配置,region 设为 ap-guangzhou + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tcr:CreateNamespacePersonal, tcr:DescribeNamespacePersonal +# tcr:CreateRepositoryPersonal, tcr:DescribeRepositoryPersonal +# tcr:DescribeUserQuotaPersonal +# 验证:执行 DescribeNamespacePersonal 确认权限 +tccli tcr DescribeNamespacePersonal --region \ + --Namespace "" --Limit 1 --Offset 0 +# expected: exit 0(空列表或已有命名空间数据) + +# 4. 检查 Docker 安装(推送/拉取镜像需要) +docker --version +# expected: Docker version XX.XX.x 或以上 +``` + +### 资源检查 + +```bash +# 5. 检查个人版配额 +tccli tcr DescribeUserQuotaPersonal --region +# expected: exit 0,Data.LimitInfo 中 namespace 和 repo 配额未达上限 +``` + +**预期输出**: + +```json +{ + "Data": { + "LimitInfo": [ + {"Username": "100012345678", "Type": "namespace", "Value": 2000}, + {"Type": "repo", "Value": 10000} + ] + }, + "RequestId": "eac6b301-a322-493a-8e36-83b295459397" +} +``` + +```bash +# 6. 确认个人版密码已在控制台初始化(无 CLI 命令) +# 登录 容器镜像服务控制台 → 实例管理 → 个人版实例卡片 → 确认密码已设置 +# 未设置密码时 docker login 会失败,参见排障节 +``` + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli / docker | 幂等 | +|-----------|-----------|:--:| +| 初始化密码 | 控制台操作,无 CLI 命令 | — | +| 登录实例 | `docker login ccr.ccs.tencentyun.com` | 是 | +| 创建命名空间 | `CreateNamespacePersonal` | 否 | +| 查看命名空间 | `DescribeNamespacePersonal` | 是 | +| 创建镜像仓库 | `CreateRepositoryPersonal` | 否 | +| 推送镜像 | `docker tag` + `docker push` | 是 | +| 拉取镜像 | `docker pull` | 是 | +| 查询配额 | `DescribeUserQuotaPersonal` | 是 | + +## 操作步骤 + +### 步骤 1:初始化密码 + +个人版密码必须在控制台初始化,无对应 CLI 命令。 + +> 登录 [容器镜像服务控制台](https://console.cloud.tencent.com/tcr),进入 **实例管理** 页面,在个人版实例卡片上单击"初始化密码"并按提示设置。忘记密码后可通过 **更多 > 重置登录密码** 重置。 + +### 步骤 2:Docker 登录个人版 + +以腾讯云账号 ID 作为用户名登录。登录前需确保已在控制台初始化密码。 + +```bash +docker login ccr.ccs.tencentyun.com --username=TENCENT_CLOUD_ACCOUNT_ID +# expected: 提示输入密码,正确输入后显示 "Login Succeeded" +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `TENCENT_CLOUD_ACCOUNT_ID` | 腾讯云账号 ID(纯数字串) | 必须,与密码初始化时的账号一致 | [腾讯云控制台-账号信息](https://console.cloud.tencent.com/developer) 查看 账号 ID | + +### 步骤 3:创建命名空间 + +#### 选择依据 + +- **命名空间名称**:个人版为共享实例,命名空间名称**全局唯一**(跨所有用户)。建议以团队或项目命名。已被其他用户占用的名称无法创建,创建失败时更换一个未被占用的名称重试。 +- **地域**:个人版在中国大陆仅在广州部署,统一使用 `--region ap-guangzhou`。 + +#### 最小创建 + +`CreateNamespacePersonal` 仅含一个必填参数 `--Namespace`: + +```bash +tccli tcr CreateNamespacePersonal --region ap-guangzhou \ + --Namespace NAMESPACE_NAME +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "b2b28287-d220-482d-a9d1-fbc196158c52" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `NAMESPACE_NAME` | 命名空间名称 | 全局唯一,1-63 字符,支持小写字母、数字、`-`、`_` | 自定义,建议以团队/项目命名 | + +#### 增强配置 + +`CreateNamespacePersonal` 只有一个必填参数,无需增强配置。若需更新命名空间属性,可通过控制台操作。 + +### 步骤 4:创建镜像仓库(可选) + +> 此步骤为可选。命名空间创建后,直接通过 `docker push` 推送镜像时,若目标仓库不存在,Docker Registry 会自动创建对应仓库。 + +#### 选择依据 + +- **参数命名**:个人版 API 使用 `--RepoName`(区别于企业版的 `--RepositoryName`)。格式为 `命名空间/仓库名`(如 `NAMESPACE_NAME/REPO_NAME`)。注意这是**单个参数**,不是两个独立参数。 +- **仓库类型**:`--Public` 为 Integer 类型,`0` 表示私有(需登录后拉取),`1` 表示公有(任何人可匿名拉取)。注意区分企业版 API 的 `--IsPublic`(Boolean 类型)。 +- **描述**:`--Description` 为可选参数,支持 Markdown 格式。 + +#### 最小创建(只含必填字段) + +```bash +tccli tcr CreateRepositoryPersonal --region ap-guangzhou \ + --RepoName "NAMESPACE_NAME/REPO_NAME" \ + --Public 0 +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "66d972a4-a887-406b-b6b6-52d213271411" +} +``` + +#### 增强配置(加描述) + +```bash +tccli tcr CreateRepositoryPersonal --region ap-guangzhou \ + --RepoName "NAMESPACE_NAME/REPO_NAME" \ + --Public 0 \ + --Description "REPO_DESCRIPTION" +# expected: exit 0,返回 RequestId +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `NAMESPACE_NAME` | 命名空间名称 | 必须已存在(步骤 3 创建) | `DescribeNamespacePersonal` | +| `REPO_NAME` | 镜像仓库名称 | 1-200 字符,仅小写字母、数字、`.`、`_`、`-`,不能以分隔符开头/结尾,不支持多级路径 | 自定义 | +| `REPO_DESCRIPTION` | 仓库描述 | 可选,支持 Markdown | 自定义 | + +### 步骤 5:推送镜像 + +以 Docker Hub 官方 Nginx 镜像为例。先 `docker tag` 标记镜像地址,再 `docker push` 推送。若目标仓库不存在,推送时会自动创建。 + +```bash +docker pull nginx:latest +# expected: exit 0,拉取成功 + +docker tag nginx:latest ccr.ccs.tencentyun.com/NAMESPACE_NAME/REPO_NAME:latest +# expected: exit 0,无输出 + +docker push ccr.ccs.tencentyun.com/NAMESPACE_NAME/REPO_NAME:latest +# expected: exit 0,推送成功,显示各 layer 的 Pushed 状态 +``` + +**预期输出**(docker push): + +```text +The push refers to repository [ccr.ccs.tencentyun.com/NAMESPACE_NAME/REPO_NAME] +5f70bf18a086: Pushed +2b0d8e4cce8e: Pushed +latest: digest: sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx size: 527 +``` + +### 步骤 6:拉取镜像 + +```bash +docker pull ccr.ccs.tencentyun.com/NAMESPACE_NAME/REPO_NAME:latest +# expected: exit 0,拉取成功或显示 "Image is up to date" +``` + +**预期输出**(docker pull): + +```text +latest: Pulling from NAMESPACE_NAME/REPO_NAME +Digest: sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +Status: Image is up to date for ccr.ccs.tencentyun.com/NAMESPACE_NAME/REPO_NAME:latest +ccr.ccs.tencentyun.com/NAMESPACE_NAME/REPO_NAME:latest +``` + +## 验证 + +### 控制面(tccli) + +```bash +# 1. 确认命名空间已创建 +tccli tcr DescribeNamespacePersonal --region ap-guangzhou \ + --Namespace NAMESPACE_NAME --Limit 20 --Offset 0 +# expected: exit 0,Data.NamespaceInfo 中包含目标命名空间 +``` + +**预期输出**: + +```json +{ + "Data": { + "NamespaceCount": 1, + "NamespaceInfo": [ + { + "Namespace": "ns-example", + "CreationTime": "2026-06-16 15:23:34", + "RepoCount": 0 + } + ] + }, + "RequestId": "eac6b301-a322-493a-8e36-83b295459397" +} +``` + +| 维度 | 命令 | 预期 | +|------|------|------| +| 命名空间存在性 | `DescribeNamespacePersonal --Namespace NAMESPACE_NAME --Limit 20 --Offset 0` | `Data.NamespaceCount` >= 1,`NamespaceInfo` 中包含目标 `Namespace` | +| 命名空间唯一性 | 同上 | 返回成功即表示名称未被占用 | +| 配额剩余 | `DescribeUserQuotaPersonal` | `Type: namespace` 的 `Value` 小于配额上限 | + +> **注意**:`DescribeNamespacePersonal` 必须同时指定 `--Namespace`、`--Limit`、`--Offset` 三个参数,任一缺失会返回参数错误。 + +### 数据面(Docker) + +```bash +# 2. 确认登录有效 +docker login ccr.ccs.tencentyun.com --username=TENCENT_CLOUD_ACCOUNT_ID +# expected: 密码交互提示;输入密码后显示 "Login Succeeded" + +# 3. 确认可拉取已推送的镜像 +docker pull ccr.ccs.tencentyun.com/NAMESPACE_NAME/REPO_NAME:latest +# expected: 拉取成功或 "Image is up to date" +``` + +## 清理 + +> **注意**:删除命名空间会级联删除其下所有镜像仓库及镜像 Tag。个人版删除操作不可逆,执行前务必通过 `DescribeNamespacePersonal` 确认目标命名空间名称。 + +### 数据面(Docker) + +数据面清理在控制面之前,避免引用已删除的资源。 + +```bash +# 1. 删除本地镜像(可选,释放磁盘空间) +docker rmi ccr.ccs.tencentyun.com/NAMESPACE_NAME/REPO_NAME:latest +# expected: Untagged 或 Deleted + +# 2. 退出登录(可选) +docker logout ccr.ccs.tencentyun.com +# expected: Removing login credentials for ccr.ccs.tencentyun.com +``` + +### 控制面(tccli) + +#### 1. 清理前状态检查 + +```bash +tccli tcr DescribeNamespacePersonal --region ap-guangzhou \ + --Namespace NAMESPACE_NAME --Limit 20 --Offset 0 +# 确认是待删除的目标命名空间,记录命名空间名称和 RepoCount +``` + +#### 2. 删除镜像仓库 + +```bash +# 先查询命名空间下的仓库列表 +tccli tcr DescribeRepositoryPersonal --region ap-guangzhou \ + --RepoName "NAMESPACE_NAME/" --Limit 20 --Offset 0 +# expected: 返回命名空间下的仓库列表 + +# 逐个删除仓库 +tccli tcr DeleteRepositoryPersonal --region ap-guangzhou \ + --RepoName "NAMESPACE_NAME/REPO_NAME" +# expected: exit 0,返回 RequestId +``` + +#### 3. 删除命名空间 + +```bash +tccli tcr DeleteNamespacePersonal --region ap-guangzhou \ + --Namespace NAMESPACE_NAME +# ⚠️ 会级联删除命名空间下所有仓库和镜像 +# expected: exit 0,返回 RequestId +``` + +#### 4. 验证已删除 + +```bash +tccli tcr DescribeNamespacePersonal --region ap-guangzhou \ + --Namespace NAMESPACE_NAME --Limit 20 --Offset 0 +# expected: Data.NamespaceCount 为 0 或返回 InvalidParameter(命名空间不存在) +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `CreateNamespacePersonal` 返回命名空间已存在 | 更换一个 `--Namespace` 值重试,或用 `DescribeNamespacePersonal` 查询已有命名空间 | 命名空间名称已被其他用户占用(个人版全局唯一) | 更换一个新的 `--Namespace` 名称重试 | +| `CreateNamespacePersonal` 或 `CreateRepositoryPersonal` 返回 `AuthFailure` | `tccli configure list` 检查 secretId/secretKey 配置 | 密钥未配置或已过期 | 执行 `tccli configure` 重新配置密钥 | +| `CreateNamespacePersonal` 或 `CreateRepositoryPersonal` 返回 `UnauthorizedOperation` | `tccli tcr DescribeNamespacePersonal --region ap-guangzhou --Namespace "" --Limit 1 --Offset 0` 验证基本权限 | CAM 权限不足,缺少 `tcr:*Personal` Action | 联系 CAM 管理员授予 `tcr:CreateNamespacePersonal` 和 `tcr:CreateRepositoryPersonal` 权限 | +| `DescribeNamespacePersonal` 返回参数错误 | 检查命令是否同时指定了 `--Namespace`、`--Limit`、`--Offset` 三个参数 | 缺少必填参数。个人版此 API 的三个参数全为必填 | 同时指定三个参数:`--Namespace NAMESPACE_NAME --Limit 20 --Offset 0` | +| `CreateRepositoryPersonal` 返回 `InvalidParameter` | 检查 `--RepoName` 格式是否为 `NAMESPACE_NAME/REPO_NAME` | `--RepoName` 格式不正确或命名空间不存在 | 确保格式为 `命名空间/仓库名`,且命名空间已创建 | +| `CreateRepositoryPersonal` 返回 `InvalidParameter`,提示 Public 类型错误 | 检查 `--Public` 参数值 | 传入了非 Integer 值(如 "false" 或 "true"),个人版 `--Public` 为 Integer(`0`/`1`),不是 Boolean | 使用 `--Public 0`(私有)或 `--Public 1`(公有) | +| `docker login` 返回 `unauthorized: authentication required` | `tccli tcr DescribeUserQuotaPersonal --region ap-guangzhou` 确认服务已开通 | 密码未初始化或账号 ID 错误 | 登录 [TCR 控制台](https://console.cloud.tencent.com/tcr) 确认密码已初始化;核对中国站/国际站账号 ID | +| `docker push` 返回 `denied: requested access to the resource is denied` | 重新执行 `docker login` | 登录凭证过期或未登录 | 执行 `docker login ccr.ccs.tencentyun.com --username=TENCENT_CLOUD_ACCOUNT_ID` 重新登录后重试 | +| `DescribeUserQuotaPersonal` 返回配额不足 | 查看 `Data.LimitInfo` 中 `Type: namespace` 或 `Type: repo` 的 `Value` | 命名空间或仓库数量已达上限(此为环境限制,非命令错误) | 清理不再使用的命名空间或仓库后重试 | + +### 创建成功但状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 命名空间创建成功但 `DescribeNamespacePersonal` 查不到 | 检查是否用正确的 `--Namespace` 参数查询;确认指定了 `--Limit` 和 `--Offset` | 查询参数不正确或分页超出范围 | 同时指定 `--Namespace NAMESPACE_NAME --Limit 20 --Offset 0` 重试 | +| `docker push` 成功但 `docker pull` 拉不到 | 先 `docker login` 确认登录状态,检查是否为私有仓库 | 私有仓库(`--Public 0`)需登录后才能拉取;或未指定正确的 tag | 若为私有仓库,确保已 `docker login`;检查 tag 名称拼写 | + +## 下一步 + +- [企业版快速入门](../enterprise) — 创建独立 TCR 实例并使用高级功能 +- [更新登录密码](../../ops/personal-edition/update-password) — 管理个人版全局密码 +- [设置镜像清理](../../ops/personal-edition/image-cleanup) — 镜像版本自动清理策略 +- [TCR 常用 API 概览](https://cloud.tencent.com/document/api/1141/41570) — 查看完整 API 列表 +- [TKE 集群使用 TCR 推送拉取镜像](https://cloud.tencent.com/document/product/457/118319) — 配合 TKE 使用 + +## 控制台替代 + +[容器镜像服务控制台 → 实例管理](https://console.cloud.tencent.com/tcr):在个人版实例卡片上单击**初始化密码**设置登录密码 → [获取腾讯云账号 ID](https://console.cloud.tencent.com/developer) → 使用 `docker login ccr.ccs.tencentyun.com` 登录 → 在**命名空间**页面创建命名空间 → **镜像仓库**页面创建仓库或直接 `docker push` 自动创建 → 在**镜像版本**页面查看已推送的 Tag。 diff --git a/src/content/docs/cli/tke/cluster-config/cluster-management/change-os-tkectl.md b/src/content/docs/cli/tke/cluster-config/cluster-management/change-os-tkectl.md new file mode 100644 index 0000000..2e63a1f --- /dev/null +++ b/src/content/docs/cli/tke/cluster-config/cluster-management/change-os-tkectl.md @@ -0,0 +1,9 @@ +--- +title: "更改集群操作系统(产品 CLI)" +description: "更改集群操作系统(产品 CLI)" +--- + +> ⚠️ **P1 placeholder** — `tkectl` integration pending. See the [tccli operation page](.-tkectl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[更改集群操作系统](https://cloud.tencent.com/document/product/457/58059) +> P0 请使用同目录 [`tccli 操作.md`](.)。 diff --git a/src/content/docs/cli/tke/cluster-config/cluster-management/change-os.md b/src/content/docs/cli/tke/cluster-config/cluster-management/change-os.md new file mode 100644 index 0000000..e6e3167 --- /dev/null +++ b/src/content/docs/cli/tke/cluster-config/cluster-management/change-os.md @@ -0,0 +1,252 @@ +--- +title: "更改集群操作系统" +description: "· page_id `58059` · tccli ≥3.1.107 · API 2018-05-25" +--- + +> 对照官方:[更改集群操作系统](https://cloud.tencent.com/document/product/457/58059) · page_id `58059` · tccli ≥3.1.107 · API 2018-05-25 + +## 概述 + +调用 `ModifyClusterImage` API 修改集群的**默认节点 OS 镜像**,仅对新加入集群的节点生效,**不会变更已有存量节点的 OS**。 + +| 概念 | 含义 | 影响范围 | +|------|------|---------| +| 集群默认镜像(`ImageId`) | `ModifyClusterImage` 设置的默认镜像 | 仅后续新建/重装节点 | +| 存量节点 OS | 已在集群中运行的节点的操作系统 | 不受 `ModifyClusterImage` 影响 | + +## 前置条件 + +- [环境准备](../../../../index.md) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域 + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tke:DescribeClusters, tke:DescribeOSImages, tke:ModifyClusterImage +# 验证:执行 DescribeClusters 确认权限 +tccli tke DescribeClusters --region +# expected: exit 0,返回集群列表(可为空) +``` + +### 资源检查 + +```bash +# 4. 确认目标集群存在且状态为 Running +tccli tke DescribeClusters --region --ClusterIds '[""]' +# expected: ClusterStatus: "Running" + +# 5. 查看集群当前默认镜像 +tccli tke DescribeClusters --region --ClusterIds '[""]' \ + | jq '.Clusters[0] | {ClusterOs, ImageId, OsCustomizeType}' +# expected: exit 0,返回当前 OS 配置 +``` + +**预期输出**: + +```json +{ + "ClusterOs": "ubuntu16.04.1 LTSx86_64", + "ImageId": "", + "OsCustomizeType": "" +} +``` + +> `ImageId` 为空(`""`)表示集群未显式设置默认镜像,使用公共镜像。`OsCustomizeType` 为空(`""`)表示未自定义 OS 类型。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 查看集群当前 OS 镜像 | `DescribeClusters` | 是 | +| 查询可用 OS 镜像列表 | `DescribeOSImages` | 是 | +| 更改集群操作系统 | `ModifyClusterImage` | 是 | + +## 关键字段说明 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `ClusterId` | String | 是 | 目标集群 ID,格式 `cls-xxxxxxxx`。通过 `DescribeClusters` 获取 | 集群不存在 → `ResourceNotFound.ClusterNotFound` | +| `ImageId` | String | 是 | 必须为 `DescribeOSImages` 返回的在线镜像 ID(`Status: "online"`),格式 `img-xxxxxxxx`。**一旦设置无法恢复为空** | 镜像不在线或不存在 → `InvalidParameter.ImageId`;传空字符串 → `InvalidParameterValue.ImageId` | +| `ClusterOs` | String | — | 只读字段。标识集群当前默认 OS。**执行 `ModifyClusterImage` 后该字段会同步更新为新镜像的 OsName**(如 `ubuntu16.04.1 LTSx86_64` → `ubuntu22.04x86_64`) | — | +| `OsCustomizeType` | String | — | 只读字段。集群 OS 自定义类型。未显式设置镜像时为 `""`,设置后变为 `GENERAL`(通用类型) | — | + +> **注意**:`ClusterOs` 和 `OsCustomizeType` 是 `DescribeClusters` 返回的只读字段,不是 `ModifyClusterImage` 的入参。它们随 `ImageId` 设置自动更新。`ImageId` 一旦通过 `ModifyClusterImage` 设为显式值后,无法恢复为空——只能设为另一个有效 `ImageId`。 + +## 操作步骤 + +### 步骤 1:查询可用 OS 镜像列表 + +```bash +tccli tke DescribeOSImages --region +# expected: exit 0,返回可用镜像列表 +``` + +**预期输出**: + +```json +{ + "TotalCount": 60, + "OSImageSeriesSet": [ + {"OsName": "ubuntu22.04x86_64", "ImageId": "img-487zeit5", "Status": "online"}, + {"OsName": "ubuntu24.04x86_64", "ImageId": "img-mmytdhbn", "Status": "online"}, + {"OsName": "tlinux3.1x86_64", "ImageId": "img-eb30mz89", "Status": "online"}, + {"OsName": "tlinux4_x86_64_public", "ImageId": "img-6n21msk1", "Status": "online"}, + {"OsName": "debian12.8x86_64", "ImageId": "img-541bm08j", "Status": "online"}, + {"OsName": "debian11.11x86_64", "ImageId": "img-mn4o8ymp", "Status": "online"} + ], + "RequestId": "..." +} +``` + +> 以上仅展示代表性镜像。过滤在线镜像:`tccli tke DescribeOSImages --region | jq '.OSImageSeriesSet[] | select(.Status=="online") | {OsName, ImageId}'`。不要选择 `Status` 为 `offline` 的镜像——会导致 `InvalidParameter.ImageId`。 + +### 步骤 2:修改集群默认 OS 镜像 + +#### 选择依据 + +- **ubuntu22.04x86_64**(`img-487zeit5`):广泛使用的 LTS 版本,社区成熟,与 TKE 兼容性良好。TKE 当前默认集群使用 ubuntu 系列,选择同系列便于后续节点管理。 +- **tlinux3.1x86_64**(`img-eb30mz89`):腾讯云优化版,与 TKE 兼容性最佳。 +- **影响范围**:`ModifyClusterImage` 仅影响后续新建节点(含手动添加、节点池扩容、重装节点),**存量节点 OS 不变**。空集群(0 节点)也可正常执行——该 API 仅设置集群默认镜像属性,不触发节点操作。 +- **存量节点迁移**:存量节点需通过节点池滚动更新(删除旧节点 → 新节点自动用新镜像)或手动重装。 + +```bash +tccli tke ModifyClusterImage --region \ + --ClusterId \ + --ImageId +# expected: exit 0,返回 RequestId 确认修改已提交 +``` + +**预期输出**: + +```json +{ + "RequestId": "..." +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | 集群 ID | 格式 `cls-xxxxxxxx` | `tccli tke DescribeClusters --region ` | +| `` | 地域 | 如 `ap-guangzhou` | `tccli configure list` | +| `` | 目标镜像 ID | 必须来自 `DescribeOSImages` 且 `Status` 为 `online` | `tccli tke DescribeOSImages --region ` | + +> **注意**:修改完成后,`DescribeClusters` 的 `ClusterOs` 字段**会同步更新**为新镜像的 OsName(如 `ubuntu22.04x86_64`),同时 `ImageId` 从空字符串变为显式镜像 ID,`OsCustomizeType` 变为 `GENERAL`。验证修改是否生效应同时检查 `ClusterOs`、`ImageId`、`OsCustomizeType` 三个字段。 + +## 验证 + +### 控制面(tccli) + +```bash +# 确认默认镜像已更新 +tccli tke DescribeClusters --region --ClusterIds '[""]' \ + | jq '.Clusters[0] | {ClusterOs, ImageId, OsCustomizeType, ClusterStatus}' +# expected: ImageId 为目标镜像 ID,ClusterOs 已更新为新 OS 名称 +``` + +**预期输出**: + +```json +{ + "ClusterOs": "ubuntu22.04x86_64", + "ImageId": "img-487zeit5", + "OsCustomizeType": "GENERAL", + "ClusterStatus": "Running" +} +``` + +| 验证维度 | 命令 | 预期 | +|---------|------|------| +| 默认镜像 | `DescribeClusters` → `ImageId` | 值为步骤 2 设置的 `` | +| 集群 OS | `DescribeClusters` → `ClusterOs` | 已更新为新镜像 OsName(如 `ubuntu22.04x86_64`) | +| OS 自定义类型 | `DescribeClusters` → `OsCustomizeType` | `GENERAL`(设置显式镜像后的默认标记) | +| 集群状态 | `DescribeClusters` → `ClusterStatus` | `Running` | + +> `ModifyClusterImage` 是同步 API(返回 RequestId 即生效),无需轮询等待状态变更。 + +## 清理 + +> **⚠️ 警告**:OS 变更不可逆——修改后新节点使用新 OS,无法自动回退存量节点。若要完全还原:先用 `ModifyClusterImage` 设回原 `ImageId`,再通过节点池滚动更新替换存量节点。 + +### 恢复原始集群 OS 镜像 + +```bash +# 先查询原始 OS 对应的 ImageId +tccli tke DescribeOSImages --region | jq '.OSImageSeriesSet[] | select(.OsName | test("ubuntu16.04")) | {OsName, ImageId, Status}' +# expected: 返回 ubuntu16.04 的 ImageId +``` + +```bash +# 恢复为原始 ImageId +tccli tke ModifyClusterImage --region \ + --ClusterId \ + --ImageId +# expected: exit 0,返回 RequestId +``` + +**预期输出**: + +```json +{ + "RequestId": "..." +} +``` + +### 验证已恢复 + +```bash +tccli tke DescribeClusters --region --ClusterIds '[""]' \ + | jq '.Clusters[0] | {ClusterOs, ImageId, OsCustomizeType, ClusterStatus}' +# expected: ClusterOs 恢复为原始 OS,ImageId 恢复为原始 ImageId +``` + +**预期输出**: + +```json +{ + "ClusterOs": "ubuntu16.04.1 LTSx86_64", + "ImageId": "img-4wpaazux", + "OsCustomizeType": "GENERAL", + "ClusterStatus": "Running" +} +``` + +> **注意**:恢复后 `ImageId` 为 `img-4wpaazux`(原为 `""`),`OsCustomizeType` 为 `GENERAL`(原为 `""`)。`ModifyClusterImage` 不支持将 `ImageId` 设回空值——一旦设置即为显式指定。`ModifyClusterImage` 操作本身不创建/删除任何资源,无需清理计费资源。 + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ModifyClusterImage` 返回 `InvalidParameter.ImageId` | 检查 `` 与 `DescribeOSImages` 返回值:`tccli tke DescribeOSImages --region \| jq '.OSImageSeriesSet[] \| select(.Status=="online")'` | ImageId 不在可用镜像列表中,或所选镜像 `Status` 为 `offline` | 只选择 `Status` 为 `online` 的镜像。用 `DescribeOSImages` 重新获取合法 ImageId | +| `ModifyClusterImage` 返回 `InvalidParameterValue.ImageId` | 检查传入的 `--ImageId` 参数 | 传入了空字符串——`ImageId` 一旦设为显式值后无法恢复为空 | 始终指定有效 ImageId。如需"还原",用 `DescribeOSImages` 找到原 OS 对应的实际 ImageId | +| `ModifyClusterImage` 返回 `UnauthorizedOperation` | `tccli configure list` 检查凭据 | 子账号缺少 `tke:ModifyClusterImage` 权限(此为环境限制,非命令错误) | 联系主账号授予 `QcloudTKEFullAccess` 或添加自定义策略含 `tke:ModifyClusterImage` Action | +| `ModifyClusterImage` 返回 `ResourceNotFound.ClusterNotFound` | `tccli tke DescribeClusters --region --ClusterIds '[""]'` 确认集群存在 | 集群 ID 不存在或 region 不匹配 | 检查 `` 拼写和 `` 是否正确。保留 RequestId 以备查询 | + +### 修改后状态与预期不一致 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ClusterOs` 字段未更新 | `tccli tke DescribeClusters --region --ClusterIds '[""]' \| jq '.Clusters[0] \| {ClusterOs, ImageId, OsCustomizeType}'` | 可能未实际执行 `ModifyClusterImage` 或执行的是另一个集群 | 确认 `` 为目标集群。重新执行步骤 2 并核对 RequestId | +| `ClusterOs` 与预期不一致 | 交叉验证 `ImageId` 对应的 OsName:`tccli tke DescribeOSImages --region \| jq '.OSImageSeriesSet[] \| select(.ImageId=="") \| {OsName, Status}'` | `ModifyClusterImage` 会将 `ClusterOs` 同步更新为新 ImageId 对应的 OsName——这是实测行为 | `ClusterOs` 的变更是正常的。如果发现 `ClusterOs` 与 `ImageId` 不匹配,可能是 API 缓存延迟——稍等片刻后重新查询 | +| `ModifyClusterImage` 后存量节点 OS 未变 | `kubectl get nodes -o wide`(如有节点) | `ModifyClusterImage` 只影响后续新建节点,存量节点 OS 不受影响(预期行为) | 存量节点需通过节点池滚动更新(删除旧节点 → 新节点自动用新镜像)或手动重装 | +| 新建节点使用了非预期 OS | 检查节点池的 `ImageId` 配置 | 节点池可能配置了独立的 `ImageId` 覆盖集群默认值 | 修改节点池 `ImageId` 使其与集群默认一致 | +| `ClusterOs` 和 `ImageId` 不匹配 | `DescribeOSImages` 交叉验证:`tccli tke DescribeOSImages --region \| jq '.OSImageSeriesSet[] \| select(.ImageId=="")'` | 可能 `ImageId` 已在平台侧下线但字段未即时同步 | 记录 `ClusterOs`、`ImageId`、`ClusterId`、`RequestId`(最后一次 `ModifyClusterImage` 的),登录 [TKE 控制台](https://console.cloud.tencent.com/tke2/cluster) 查看详细状态。仍无法解决则 [提交工单](https://console.cloud.tencent.com/workorder) | + +## 下一步 + +- [集群扩缩容](../scale) — 扩缩节点,验证新 OS +- [创建集群](../create) — 了解创建时 OS 配置 +- [连接集群](../connect) — 获取 kubeconfig 验证新节点 + +## 控制台替代 + +[控制台 → 集群 → 基本信息 → 更改集群操作系统](https://console.cloud.tencent.com/tke2/cluster) diff --git a/src/content/docs/cli/tke/cluster-config/cluster-management/connect-tkectl.md b/src/content/docs/cli/tke/cluster-config/cluster-management/connect-tkectl.md new file mode 100644 index 0000000..872f495 --- /dev/null +++ b/src/content/docs/cli/tke/cluster-config/cluster-management/connect-tkectl.md @@ -0,0 +1,9 @@ +--- +title: "连接集群(产品 CLI)" +description: "连接集群(产品 CLI)" +--- + +> ⚠️ **P1 placeholder** — `tkectl` integration pending. See the [tccli operation page](.-tkectl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[连接集群](https://cloud.tencent.com/document/product/457/32191) +> P0 请使用同目录 [`tccli 操作.md`](.)。 diff --git a/src/content/docs/cli/tke/cluster-config/cluster-management/connect.md b/src/content/docs/cli/tke/cluster-config/cluster-management/connect.md new file mode 100644 index 0000000..d1b095d --- /dev/null +++ b/src/content/docs/cli/tke/cluster-config/cluster-management/connect.md @@ -0,0 +1,325 @@ +--- +title: "连接集群" +description: "· page_id `32191`" +--- + +> 对照官方:[连接集群](https://cloud.tencent.com/document/product/457/32191) · page_id `32191` + +## 概述 + +通过 tccli 获取 TKE 集群的 kubeconfig 并连接集群。提供两条路径:**直接获取 kubeconfig** 或**创建集群端点**(内网/公网)。 + +| 方案 | 适用场景 | 前提条件 | +|------|---------|---------| +| 直接获取 kubeconfig | 已有内网/VPN 可达,快速获取凭证 | 无额外权限要求 | +| 创建内网端点 | 需要 VPC 内 LB 固定入口 | 集群需有运行中的 service-controller | +| 创建公网端点 | 需要从公网访问 API server | CAM 策略可能硬拒绝(见排障) | + +> **建议**:若仅需获取 kubeconfig,直接使用 `DescribeClusterKubeconfig`。若需持久化入口地址,再创建内网端点。 + +## 前置条件 + +- [环境准备](../../../../index.md) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置 + +# 3. 检查 kubectl 已安装 +kubectl version --client --output yaml +# expected: clientVersion 存在,建议 >= 1.28.0 + +# 4. 检查 CAM 权限 +# 需要: tke:DescribeClusterEndpoints, tke:DescribeClusterKubeconfig, +# tke:DescribeClusterEndpointStatus, tke:DescribeClusterStatus, +# tke:CreateClusterEndpoint, tke:DeleteClusterEndpoint +tccli tke DescribeClusters --region +# expected: exit 0 +``` + +```json +{ + "TotalCount": "", + "Clusters": "", + "ClusterId": "", + "ClusterName": "", + "ClusterDescription": "", + "ClusterVersion": "" +} +``` + +### 资源检查 + +```bash +# 5. 确认目标集群存在且状态正常 +tccli tke DescribeClusters --region --ClusterIds '[""]' +# expected: TotalCount >= 1,ClusterStatus: "Running" +``` + +```json +{ + "TotalCount": "", + "Clusters": "", + "ClusterId": "", + "ClusterName": "", + "ClusterDescription": "", + "ClusterVersion": "" +} +``` + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 查看集群访问入口 | `DescribeClusterEndpoints` | 是 | +| 获取 kubeconfig | `DescribeClusterKubeconfig` | 是 | +| 查看端点开通状态 | `DescribeClusterEndpointStatus` | 是 | +| 查看认证配置 | `DescribeClusterAuthenticationOptions` | 是 | +| 创建内网访问入口 | `CreateClusterEndpoint --IsExtranet false` | 否 | +| 创建公网访问入口 | `CreateClusterEndpoint --IsExtranet true` | 否 | +| 删除访问入口 | `DeleteClusterEndpoint` | 是 | + +## 关键字段说明 + +| 字段 | 类型 | 说明 | 错误后果 | +|------|------|------|------| +| `ClusterId` | String | 格式 `cls-xxxxxxxx` | 不存在 → `InvalidParameter.ClusterId` | +| `IsExtranet` | Boolean | `true`(公网)/ `false`(内网) | 公网可能被 CAM 策略拒绝 → `InvalidParameter.Param`(strategyId:240463971, condition: tke:clusterExtranetEndpoint=true) | +| `SubnetId` | String | 内网端点必填,须与集群同 VPC | 子网不存在 → 创建失败 | +| `ClusterDomain` | String | 集群访问域名,仅在腾讯云内网可解析 | 本地 PC 无法 DNS 解析(预期) | +| `Kubeconfig` | String | 完整 kubeconfig 内容(YAML 格式) | 需通过 `jq -r` 提取并写入文件 | + +## 操作步骤 + +### 步骤 1:查询集群访问入口信息 + +```bash +tccli tke DescribeClusterEndpoints --region --ClusterId +# expected: exit 0,返回集群域名和 CA 证书 +``` + +**预期输出**: + +```json +{ + "ClusterExternalEndpoint": "", + "ClusterIntranetEndpoint": "", + "ClusterDomain": "cls-example.ccs.tencent-cloud.com", + "ClusterExternalDomain": "cls-example.ccs.tencent-cloud.com", + "ClusterIntranetDomain": "cls-example.ccs.tencent-cloud.com", + "SecurityGroup": "", + "CertificationAuthority": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----", + "RequestId": "..." +} +``` + +### 步骤 2:检查端点状态 + +```bash +# 内网端点 +tccli tke DescribeClusterEndpointStatus --region \ + --ClusterId \ + --IsExtranet false +# expected: exit 0,Status 可能为 Created / Creating / CreateFailed / NotFound + +# 公网端点 +tccli tke DescribeClusterEndpointStatus --region \ + --ClusterId \ + --IsExtranet true +# expected: exit 0,Status 可能为 NotFound(未创建或被 CAM 拒绝) +``` + +**预期输出**(内网端点创建失败): + +```json +{ + "Status": "CreateFailed", + "ErrorMsg": "ensure extranet service: endpoints default/kubernetes-intranet-loadbalancer address sync timed out, check if service-controller is running", + "RequestId": "..." +} +``` + +| `Status` | 含义 | 处理 | +|---------|------|------| +| `Created` | 端点已就绪 | 直接获取 kubeconfig | +| `Creating` | 创建中 | 等待 1-3 分钟再查 | +| `CreateFailed` | 创建失败 | 查看 `ErrorMsg`。内网端点失败通常因 service-controller 未运行 | +| `NotFound` | 端点不存在(未创建或被 CAM 拒绝) | 公网被 CAM 拒绝时无日志 | + +### 步骤 3:获取 kubeconfig + +无论端点是否创建成功,都可通过 `DescribeClusterKubeconfig` 直接获取完整 kubeconfig(含客户端证书和私钥)。 + +```bash +tccli tke DescribeClusterKubeconfig --region --ClusterId +# expected: exit 0,Kubeconfig 字段非空(约 5.7KB) +``` + +**预期输出**: + +```json +{ + "Kubeconfig": "apiVersion: v1\nclusters:\n- cluster:\n certificate-authority-data: ...\n server: https://cls-example.ccs.tencent-cloud.com\n name: cls-example\ncontexts:\n- context:\n cluster: cls-example\n user: admin\n name: cls-example\ncurrent-context: cls-example\nkind: Config\npreferences: {}\nusers:\n- name: admin\n user:\n client-certificate-data: ...\n client-key-data: ...", + "RequestId": "..." +} +``` + +**保存 kubeconfig**: + +```bash +tccli tke DescribeClusterKubeconfig --region --ClusterId \ + | jq -r '.Kubeconfig' > ~/.kube/config-tke- +# expected: 文件已创建 +``` + +```json +{ + "Kubeconfig": "", + "RequestId": "" +} +``` + +| 占位符 | 说明 | 获取方式 | +|--------|------|---------| +| `` | 目标集群 ID | `tccli tke DescribeClusters --region ` | +| `` | 地域 | `tccli configure list` | + +### 步骤 4:查询集群认证选项和状态 + +```bash +# 认证配置 +tccli tke DescribeClusterAuthenticationOptions --region --ClusterId +# expected: exit 0 + +# 集群状态确认 +tccli tke DescribeClusterStatus --region --ClusterIds '[""]' +# expected: ClusterState: "Running" +``` + +```json +{ + "ServiceAccounts": "", + "UseTKEDefault": "", + "Issuer": "", + "JWKSURI": "", + "AutoCreateDiscoveryAnonymousAuth": "", + "LatestOperationState": "" +} +``` + +## 验证 + +### 控制面(tccli) + +```bash +# 确认集群状态 +tccli tke DescribeClusterStatus --region --ClusterIds '[""]' \ + | jq '.ClusterStatusSet[0].ClusterState' +# expected: "Running" + +# 检查 kubeconfig 完整性 +wc -c < ~/.kube/config-tke- +# expected: > 1000 +grep "server:" ~/.kube/config-tke- +# expected: server: https://cls-example.ccs.tencent-cloud.com +``` + +```json +{ + "ClusterStatusSet": "", + "ClusterId": "", + "ClusterState": "", + "ClusterInstanceState": "", + "ClusterBMonitor": "", + "ClusterInitNodeNum": "" +} +``` + +### 数据面(需内网/VPN 可达环境) + +```bash +kubectl --kubeconfig ~/.kube/config-tke- cluster-info +# expected: Kubernetes control plane is running at... + +kubectl --kubeconfig ~/.kube/config-tke- get ns +# expected: 返回 default、kube-system 等命名空间 +``` + +| 验证维度 | 预期 | +|---------|------| +| 控制面状态 | `ClusterState: "Running"` | +| kubeconfig 完整性 | server 地址为 `https://cls-example.ccs.tencent-cloud.com` | +| kubectl 连通性 | 返回 Kubernetes control plane 地址(仅内网环境) | + +## 清理 + +> **警告**:`DeleteClusterEndpoint` 删除端点后将导致所有 kubectl 连接中断。kubeconfig 文件删除后不可恢复——客户端证书随文件删除而丢失,需重新获取。 + +```bash +# 清理前检查 +tccli tke DescribeClusterEndpoints --region --ClusterId + +# 删除内网端点 +tccli tke DeleteClusterEndpoint --region \ + --ClusterId --IsExtranet false + +# 删除公网端点 +tccli tke DeleteClusterEndpoint --region \ + --ClusterId --IsExtranet true + +# 验证已删除 +tccli tke DescribeClusterEndpointStatus --region \ + --ClusterId --IsExtranet false +# expected: Status: "NotFound" + +# 清理本地 kubeconfig +rm ~/.kube/config-tke- +``` + +```json +{ + "CertificationAuthority": "", + "ClusterExternalEndpoint": "", + "ClusterIntranetEndpoint": "", + "ClusterDomain": "", + "ClusterExternalACL": "", + "ClusterExternalDomain": "" +} +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `CreateClusterEndpoint --IsExtranet true` 返回 `InvalidParameter.Param`:`ACTION_NO_AUTH: you are not authorized to perform operation (tke:CreateClusterEndpoint). condition: [{"key":"tke:clusterExtranetEndpoint","value":"true","ope":"string_equal"}], effect: deny, strategyId:240463971` | 登录 [CAM 控制台](https://console.cloud.tencent.com/cam/policy) 查看策略 240463971 | 组织级 CAM 策略以 `tke:clusterExtranetEndpoint=true` 条件硬拒绝公网端点创建(环境限制)。即使自建安全组也无法绕过 | 回退到内网端点:`--IsExtranet false`。通过 IOA/VPN/专线或同 VPC CVM 访问。如需公网访问,联系管理员修改 CAM 策略 | +| `CreateClusterEndpoint --IsExtranet false` 返回 `OperationDenied` | 等待 30 秒后重试 | 同一类型端点已有创建任务执行中 | 等待当前任务完成 | +| `DescribeClusterEndpointStatus --IsExtranet false` 返回 `CreateFailed`:`address sync timed out, check if service-controller is running` | 空集群(0 工作节点)时 service-controller 无法调度 | service-controller 未运行 | 先创建工作节点使 service-controller 运行,或直接 `DescribeClusterKubeconfig` 获取凭证绕过端点 | +| `DescribeClusterKubeconfig` 返回空或无效 | 检查 `Kubeconfig` 字段长度 | API server 可能在重启 | 等待数分钟重试,确认集群状态为 `Running` | +| `DescribeClusters` 返回 `InvalidParameter.ClusterId` | 检查 ID 格式和 region | ID 格式错误或不属于当前账号/地域 | `DescribeClusters --region ` 列出全部集群 | + +### kubectl 不通 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `Unable to connect to the server: dial tcp: lookup cls-example.ccs.tencent-cloud.com: no such host` | `nslookup cls-example.ccs.tencent-cloud.com` | 本地 PC 不在腾讯云内网,无法 DNS 解析集群域名(预期行为,非配置错误) | 切换到同 VPC CVM 或通过 IOA/VPN/专线接入后重试 | +| `x509: certificate signed by unknown authority` | 检查 kubeconfig 的 `certificate-authority-data` | CA 证书缺失或不完整 | 重新获取 kubeconfig | +| `dial tcp ...: i/o timeout` | 检查端口连通性 | 安全组或防火墙拦截,或端点未创建/已删除 | 检查安全组入站规则放行 TCP 443 | + +## 下一步 + +- [创建集群](../create) — 新建托管集群 +- [升级集群](../upgrade) — 升级 K8s 版本 +- [集群生命周期](../lifecycle) — 状态流转管理 + +## 控制台替代 + +[TKE 控制台 → 集群 → 基本信息](https://console.cloud.tencent.com/tke2/cluster):查看访问入口、获取 kubeconfig、开启/关闭内网和公网访问。 diff --git a/src/content/docs/cli/tke/cluster-config/cluster-management/create-tkectl.md b/src/content/docs/cli/tke/cluster-config/cluster-management/create-tkectl.md new file mode 100644 index 0000000..d60a582 --- /dev/null +++ b/src/content/docs/cli/tke/cluster-config/cluster-management/create-tkectl.md @@ -0,0 +1,11 @@ +--- +title: "创建集群(产品 CLI)" +description: "tkectl cluster create 等产品命令将在 P0 全部 tccli 操作 完成后编写。" +--- + +> ⚠️ **P1 placeholder** — `tkectl` integration pending. See the [tccli operation page](.-tkectl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[创建集群](https://cloud.tencent.com/document/product/457/103981) +> P0 请使用同目录 [`tccli 操作.md`](.)。 + +`tkectl cluster create` 等产品命令将在 P0 全部 `tccli 操作` 完成后编写。 diff --git a/src/content/docs/cli/tke/cluster-config/cluster-management/create.md b/src/content/docs/cli/tke/cluster-config/cluster-management/create.md new file mode 100644 index 0000000..99777e1 --- /dev/null +++ b/src/content/docs/cli/tke/cluster-config/cluster-management/create.md @@ -0,0 +1,719 @@ +--- +title: "创建集群" +description: "· page_id `103981` · tccli ≥3.1.107 · API 2018-05-25" +--- + +> 对照官方:[创建集群](https://cloud.tencent.com/document/product/457/103981) · page_id `103981` · tccli ≥3.1.107 · API 2018-05-25 + +## 概述 + +通过 `CreateCluster` API 创建 TKE 标准集群。集群创建后**不包含工作节点**(`ClusterNodeNum=0`),节点通过节点池后续添加。创建集群属异步操作,需轮询直至状态为 `Running`。 + +| 维度 | 托管集群 | 独立集群 | +|------|---------|---------| +| `ClusterType` 枚举 | `MANAGED_CLUSTER` | `INDEPENDENT_CLUSTER` | +| 控制面归属 | 腾讯云运维 | 用户自购 CVM 部署 | +| Master/Etcd 管理 | 自动运维,不可修改 | 用户自行管理 | +| 运行时 | 强制 `containerd` | 可选 `containerd` / `docker` | +| 新集群支持 | 活跃支持 | **已停止新建** | + +**建议:新集群一律选托管集群(`MANAGED_CLUSTER`)。** + +## 前置条件 + +- [环境准备](../../../../index.md) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置 + +# 3. 检查 CAM 权限 +# 需要: tke:CreateCluster, tke:DescribeClusters, tke:DescribeVersions, +# tke:CreateClusterEndpoint, tke:DescribeClusterEndpoints, +# tke:DescribeClusterKubeconfig, tke:DescribeClusterSecurity, +# tke:DescribeClusterStatus, tke:DescribeClusterEndpointStatus, +# tke:DescribeClusterLevelAttribute, tke:DeleteCluster, +# vpc:DescribeVpcs, vpc:DescribeSubnets, vpc:DescribeVpcLimits, +# vpc:CreateSecurityGroup, vpc:CreateSecurityGroupPolicies, +# vpc:DeleteSecurityGroup +tccli tke DescribeClusters --region +# expected: exit 0 +tccli vpc DescribeVpcs --region +# expected: exit 0 +``` + +### 资源检查 + +```bash +# 4. 查询可用 K8s 版本 +tccli tke DescribeVersions --region +# expected: TotalCount >= 1,推荐 ≥ 1.30.0 +``` + +**预期输出**: + +```json +{ + "TotalCount": 14, + "VersionInstanceSet": [ + {"Name": "k8s", "Version": "1.10.5", "Remark": "SUITABLE_FOR_INDEPENDENT_CLUSTER"}, + {"Name": "k8s", "Version": "1.12.4", "Remark": "SUITABLE_FOR_INDEPENDENT_CLUSTER"}, + {"Name": "k8s", "Version": "1.14.3", "Remark": "SUITABLE_FOR_INDEPENDENT_CLUSTER"}, + {"Name": "k8s", "Version": "1.14.3-tk8s", "Remark": "SUITABLE_FOR_INDEPENDENT_CLUSTER"}, + {"Name": "k8s", "Version": "1.16.3", "Remark": "SUITABLE_FOR_INDEPENDENT_CLUSTER"}, + {"Name": "k8s", "Version": "1.18.4", "Remark": "SUITABLE_FOR_INDEPENDENT_CLUSTER"}, + {"Name": "k8s", "Version": "1.20.6", "Remark": "SUITABLE_FOR_INDEPENDENT_CLUSTER"}, + {"Name": "k8s", "Version": "1.22.5", "Remark": "SUITABLE_FOR_INDEPENDENT_CLUSTER"}, + {"Name": "k8s", "Version": "1.24.4", "Remark": "SUITABLE_FOR_INDEPENDENT_CLUSTER"}, + {"Name": "k8s", "Version": "1.26.1", "Remark": "SUITABLE_FOR_INDEPENDENT_CLUSTER"}, + {"Name": "k8s", "Version": "1.28.3", "Remark": "SUITABLE_FOR_INDEPENDENT_CLUSTER"}, + {"Name": "k8s", "Version": "1.30.0", "Remark": "SUITABLE_FOR_INDEPENDENT_CLUSTER"}, + {"Name": "k8s", "Version": "1.32.2", "Remark": "SUITABLE_FOR_INDEPENDENT_CLUSTER"}, + {"Name": "k8s", "Version": "1.34.1", "Remark": "SUITABLE_FOR_INDEPENDENT_CLUSTER"} + ] +} +``` + +```bash +# 5. 查询集群级别(规格与配额) +tccli tke DescribeClusterLevelAttribute --region +# expected: TotalCount >= 1 +``` + +**预期输出**: + +```json +{ + "TotalCount": 9, + "Items": [ + {"Name": "5节点", "Alias": "L5", "NodeCount": 5, "PodCount": 150, "ConfigMapCount": 128, "RSCount": 900, "CRDCount": 150, "Enable": true}, + {"Name": "20节点", "Alias": "L20", "NodeCount": 20, "PodCount": 600, "ConfigMapCount": 256, "RSCount": 3600, "CRDCount": 600, "Enable": true}, + {"Name": "50节点", "Alias": "L50", "NodeCount": 50, "PodCount": 1500, "ConfigMapCount": 512, "RSCount": 7500, "CRDCount": 1250, "Enable": true}, + {"Name": "100节点", "Alias": "L100", "NodeCount": 100, "PodCount": 3000, "ConfigMapCount": 1024, "RSCount": 15000, "CRDCount": 2500, "Enable": true}, + {"Name": "200节点", "Alias": "L200", "NodeCount": 200, "PodCount": 6000, "ConfigMapCount": 2048, "RSCount": 30000, "CRDCount": 5000, "Enable": true}, + {"Name": "500节点", "Alias": "L500", "NodeCount": 500, "PodCount": 15000, "ConfigMapCount": 4096, "RSCount": 60000, "CRDCount": 10000, "Enable": true}, + {"Name": "1000节点", "Alias": "L1000", "NodeCount": 1000, "PodCount": 30000, "ConfigMapCount": 6144, "RSCount": 120000, "CRDCount": 20000, "Enable": true}, + {"Name": "3000节点", "Alias": "L3000", "NodeCount": 3000, "PodCount": 90000, "ConfigMapCount": 8192, "RSCount": 300000, "CRDCount": 50000, "Enable": true}, + {"Name": "5000节点", "Alias": "L5000", "NodeCount": 5000, "PodCount": 150000, "ConfigMapCount": 10240, "RSCount": 600000, "CRDCount": 100000, "Enable": true} + ] +} +``` + +```bash +# 6. 查询 VPC 和子网 +tccli vpc DescribeVpcs --region \ + --Filters '[{"Name":"vpc-name","Values":[""]}]' +# expected: 至少返回 1 个 VPC,记录 VpcId + +tccli vpc DescribeSubnets --region \ + --Filters '[{"Name":"vpc-id","Values":[""]}]' +# expected: 至少 1 个子网,AvailableIpAddressCount >= 3 + +# 7. 检查 VPC 配额 +tccli vpc DescribeVpcLimits --region \ + --LimitTypes '["appid-max-vpcs"]' +# expected: 已用量 < 总量 +``` + +**查询 VPC 预期输出**: + +```json +{ + "TotalCount": 1, + "VpcSet": [ + { + "VpcId": "vpc-example", + "VpcName": "", + "CidrBlock": "172.16.0.0/12", + "IsDefault": false, + "EnableMulticast": false, + "DnsServerSet": ["183.60.83.19", "183.60.82.98"], + "EnableDhcp": true, + "AssistantCidrSet": [] + } + ] +} +``` + +**查询子网预期输出**: + +```json +{ + "TotalCount": 1, + "SubnetSet": [ + { + "VpcId": "vpc-example", + "SubnetId": "subnet-example", + "SubnetName": "", + "CidrBlock": "172.24.0.0/20", + "Zone": "ap-guangzhou-3", + "AvailableIpAddressCount": 4087, + "TotalIpAddressCount": 4093 + } + ] +} +``` + +上述示例中 VPC CIDR 为 `172.16.0.0/12`,子网 CIDR 为 `172.24.0.0/20`。创建集群时 `ClusterCIDR` 和 `ServiceCIDR` **必须与 VPC CIDR 及辅助 CIDR 不重叠**。 + +## 关键字段说明 + +以下说明 `CreateCluster` 的主要参数。完整参数定义见 `tccli tke CreateCluster help --detail`。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `ClusterType` | String | 是 | `MANAGED_CLUSTER`(托管)或 `INDEPENDENT_CLUSTER`(独立,已停止新建) | 填 `GR` 等控制台概念名 → `InvalidParameter.ClusterType` | +| `ClusterBasicSettings.ClusterVersion` | String | 是 | 如 `1.30.0`。通过 `DescribeVersions` 查询可用版本 | 版本不存在 → `InvalidParameter.ClusterVersion` | +| `ClusterBasicSettings.ClusterName` | String | 是 | 长度 1-60 字符,以字母开头 | 格式不合法 → 参数校验失败 | +| `ClusterBasicSettings.VpcId` | String | 是 | 已存在的 VPC ID,格式 `vpc-xxxxxxxx` | VPC 不存在 → `InvalidParameter.VpcId` | +| `ClusterBasicSettings.SubnetId` | String | 是 | 已存在且属于上述 VPC 的子网 ID | 子网不存在 → `InvalidParameter.SubnetId` | +| `ClusterBasicSettings.ClusterLevel` | String | 否 | `L5`~`L5000`,决定 Pod/ConfigMap/CRD 配额上限 | 级别超配额 → `LimitExceeded` | +| `ClusterCIDRSettings.ClusterCIDR` | String | 是 | Pod IP 范围,如 `10.200.0.0/16`,不与 VPC CIDR、辅助 CIDR、已有集群 CIDR 重叠 | CIDR 冲突 → `InvalidParameter.ClusterCIDRSettings` | +| `ClusterCIDRSettings.ServiceCIDR` | String | 是 | Service IP 范围,**掩码必须 17-27**。如 `/20`(4096 个 Service IP) | `/16` 等越界 → `InvalidParameter.CidrMaskSizeOutOfRange` | +| `ClusterCIDRSettings.MaxNodePodNum` | Integer | 否 | 默认 64,上限 256 | 超出范围 → 参数校验失败 | +| `ClusterCIDRSettings.MaxClusterServiceNum` | Integer | 否 | 须与 ServiceCIDR 掩码匹配:`/20`→4096, `/24`→256, `/17`→32768 | 不匹配 → 参数校验失败 | +| `ClusterAdvancedSettings.ContainerRuntime` | String | 否 | `containerd`(推荐,托管集群强制) | 托管集群填 `docker` → 参数校验失败 | +| `ClusterAdvancedSettings.RuntimeVersion` | String | 否 | 如 `1.6.9`。托管集群自动选择,通常无需指定 | 版本不可用 → 参数校验失败 | +| `ClusterAdvancedSettings.DeletionProtection` | Boolean | 否 | 默认 `false`。生产环境建议 `true` | 忘开 → 可能误删生产集群 | + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 查询可用 K8s 版本 | `DescribeVersions` | 是 | +| 查询集群级别规格 | `DescribeClusterLevelAttribute` | 是 | +| 查询 VPC 和子网 | `DescribeVpcs` / `DescribeSubnets` | 是 | +| 创建安全组 | `CreateSecurityGroup` | 否 | +| 添加安全组规则 | `CreateSecurityGroupPolicies` | 否 | +| 创建托管集群 | `CreateCluster` | 否 | +| 查看集群详情 | `DescribeClusters` | 是 | +| 查看集群状态 | `DescribeClusterStatus` | 是 | +| 创建内网访问端点 | `CreateClusterEndpoint` | 否 | +| 轮询端点状态 | `DescribeClusterEndpointStatus` | 是 | +| 查看访问端点 | `DescribeClusterEndpoints` | 是 | +| 获取 kubeconfig(证书) | `DescribeClusterKubeconfig` | 是 | +| 获取 kubeconfig(token) | `DescribeClusterSecurity` | 是 | +| 删除集群 | `DeleteCluster` | 否 | + +## 操作步骤 + +### 步骤 1:选择集群参数 + +#### 选择依据 + +- **集群类型**:选择 `MANAGED_CLUSTER`(托管集群)。托管集群控制面由腾讯云运维,无需自行管理 Master 节点。独立集群(`INDEPENDENT_CLUSTER`)需自行购买 CVM 部署 Master 节点,且已停止新建。新集群一律选托管。常见错误:填控制台概念名 `GR` 而非 API 枚举 `MANAGED_CLUSTER` → `InvalidParameter.ClusterType`。 +- **K8s 版本**:推荐 `1.30.0`(当前 LTS 长期支持版本,稳定可用)。可通过 `tccli tke DescribeVersions --region ` 查询最新可用版本。 +- **运行时**:选 `containerd`。Docker 运行时已停止维护,K8s 1.24+ 移除 dockershim。托管集群强制使用 containerd。 +- **集群规格**:选 `L5`(最小规格,5 节点/150 Pod)。生产环境按需选择更高规格(`L20`~`L5000`)。 +- **CIDR 规划**:`ClusterCIDR` 选用 `10.200.0.0/16`,`ServiceCIDR` 选用 `10.201.0.0/20`。**必须与 VPC CIDR(本例 `172.16.0.0/12`)、VPC 辅助 CIDR、已有集群 CIDR 不重叠**。ServiceCIDR 掩码必须在 17-27 之间,`MaxClusterServiceNum` 须与掩码匹配(`/20` → 4096)。CIDR 计算公式:`MaxClusterServiceNum = 2^(32-掩码)-2`。 +- **空集群模式**:先创建空集群(`ClusterNodeNum=0`),验证控制面和网络配置正确。节点通过节点池后续添加(见"节点管理"章节)。空集群节点数为 0 是预期行为。 + +**版本确认**: + +```bash +tccli tke DescribeVersions --region \ + | jq '.VersionInstanceSet[] | select(.Version >= "1.30")' +# expected: 返回 1.30.0、1.32.2、1.34.1 +``` + +**CIDR 冲突检查**: + +```bash +# 检查现有集群 CIDR 避免冲突 +tccli tke DescribeClusters --region \ + | jq '.Clusters[].ClusterNetworkSettings | {ClusterCIDR, ServiceCIDR}' +# expected: 列出所有现有 CIDR,确认新 CIDR 不重叠 +``` + +> **注意**:如果 VPC 配置了辅助 CIDR(如 `10.0.0.0/16`、`10.1.0.0/20`),这些网段也不可用于集群 CIDR。通过 `tccli vpc DescribeVpcs --region --VpcIds '[""]'` 查看 `AssistantCidrSet` 字段确认。 + +### 步骤 2:最小创建(空集群,只含必填字段) + +`cluster-minimal.json`: + +```json +{ + "ClusterType": "MANAGED_CLUSTER", + "ClusterBasicSettings": { + "ClusterName": "", + "ClusterVersion": "1.30.0", + "VpcId": "", + "SubnetId": "", + "ClusterLevel": "L5" + }, + "ClusterCIDRSettings": { + "ClusterCIDR": "10.200.0.0/16", + "MaxNodePodNum": 64, + "MaxClusterServiceNum": 4096, + "ServiceCIDR": "10.201.0.0/20" + } +} +``` + +```bash +tccli tke CreateCluster --region \ + --cli-input-json file://cluster-minimal.json +# expected: exit 0,返回 ClusterId +``` + +**预期输出**: + +```json +{ + "ClusterId": "cls-example", + "RequestId": "..." +} +``` + +### 步骤 3:增强配置 + +`cluster-enhanced.json`: + +```json +{ + "ClusterType": "MANAGED_CLUSTER", + "ClusterBasicSettings": { + "ClusterName": "", + "ClusterVersion": "1.30.0", + "VpcId": "", + "SubnetId": "", + "ClusterLevel": "L5", + "ClusterDescription": "", + "TagSpecification": [ + { + "ResourceType": "cluster", + "Tags": [ + {"Key": "env", "Value": "production"} + ] + } + ] + }, + "ClusterCIDRSettings": { + "ClusterCIDR": "10.200.0.0/16", + "MaxNodePodNum": 64, + "MaxClusterServiceNum": 4096, + "ServiceCIDR": "10.201.0.0/20" + }, + "ClusterAdvancedSettings": { + "ContainerRuntime": "containerd", + "RuntimeVersion": "1.6.9", + "DeletionProtection": true, + "AuditEnabled": true + } +} +``` + +```bash +tccli tke CreateCluster --region \ + --cli-input-json file://cluster-enhanced.json +# expected: exit 0,返回 ClusterId,集群异步创建中 +``` + +**预期输出**: + +```json +{ + "ClusterId": "cls-example", + "RequestId": "..." +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | 集群名称 | 长度 1-60 字符,字母开头 | 自定义 | +| `` | VPC ID | 格式 `vpc-xxxxxxxx` | `tccli vpc DescribeVpcs --region ` | +| `` | 子网 ID | 需属于上述 VPC,有可用 IP | `tccli vpc DescribeSubnets --region ` | +| `` | 地域 | 如 `ap-guangzhou` | `tccli configure list` | +| `` | 集群描述 | 可选,长度 ≤ 256 | 自定义 | + +| 层级 | 包含内容 | 目的 | +|------|---------|------| +| **最小创建** | ClusterType, ClusterName, ClusterVersion, VpcId, SubnetId, ClusterLevel, ClusterCIDR, ServiceCIDR | 验证控制面和网络配置 | +| **增强配置** | 最小创建全部字段 + Description, TagSpecification, ContainerRuntime, RuntimeVersion, DeletionProtection, AuditEnabled | 生产环境推荐配置 | + +### 步骤 4:创建安全组并放行 kubectl 访问 + +集群创建后,在创建访问端点之前需准备安全组。以下是自建安全组的完整流程: + +`sg-create.json`: + +```json +{ + "GroupName": "", + "GroupDescription": "" +} +``` + +```bash +tccli vpc CreateSecurityGroup --region \ + --cli-input-json file://sg-create.json +# expected: exit 0,返回 SecurityGroupId +``` + +**预期输出**: + +```json +{ + "SecurityGroup": { + "SecurityGroupId": "sg-example", + "SecurityGroupName": "" + }, + "RequestId": "..." +} +``` + +`sg-policy.json`: + +```json +{ + "SecurityGroupId": "", + "SecurityGroupPolicySet": { + "Ingress": [ + { + "Protocol": "TCP", + "Port": "443", + "CidrBlock": "0.0.0.0/0", + "Action": "ACCEPT", + "PolicyDescription": "kubectl access" + } + ] + } +} +``` + +```bash +tccli vpc CreateSecurityGroupPolicies --region \ + --cli-input-json file://sg-policy.json +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RequestId": "..." +} +``` + +### 步骤 5:创建内网访问端点 + +集群就绪后,创建内网访问端点以获取 kubectl 访问能力。 + +> **为什么选内网端点**:公网端点(`--IsExtranet true`)可能被组织级 CAM 策略拒绝(条件 `tke:clusterExtranetEndpoint=true`)。内网端点(`--IsExtranet false`)通过 VPC 内部 IP 访问,需要同 VPC CVM 或 IOA/VPN/专线连接。 + +`endpoint-internal.json`: + +```json +{ + "ClusterId": "", + "SubnetId": "", + "IsExtranet": false, + "SecurityGroup": "" +} +``` + +```bash +tccli tke CreateClusterEndpoint --region \ + --cli-input-json file://endpoint-internal.json +# expected: exit 0,返回 RequestId。端点异步创建中 +``` + +**预期输出**: + +```json +{ + "RequestId": "..." +} +``` + +端点创建是异步操作。轮询直至状态为 `Created`: + +```bash +tccli tke DescribeClusterEndpointStatus --region \ + --ClusterId \ + --IsExtranet false +# expected: exit 0,Status: "Created" +``` + +**预期输出**: + +```json +{ + "Status": "Created", + "ErrorMsg": "" +} +``` + +> **注意**:端点创建约需 30-60 秒。如果 `Status` 为 `Creating`,等待后重新轮询。 + +### 步骤 6:获取访问端点信息 + +```bash +tccli tke DescribeClusterEndpoints --region \ + --ClusterId +# expected: exit 0,返回内网端点 IP 和域名 +``` + +**预期输出**: + +```json +{ + "CertificationAuthority": "(CA证书内容)", + "ClusterExternalEndpoint": "", + "ClusterIntranetEndpoint": "172.24.0.16", + "ClusterDomain": "cls-example.ccs.tencent-cloud.com", + "ClusterExternalACL": null, + "ClusterExternalDomain": "cls-example.ccs.tencent-cloud.com", + "ClusterIntranetDomain": "", + "SecurityGroup": "", + "ClusterIntranetSubnetId": "subnet-example", + "IntranetSecurityGroup": "sg-example" +} +``` + +### 步骤 7:获取 kubeconfig(token 型,推荐) + +`DescribeClusterSecurity` 返回含 `token` 的 kubeconfig,比证书型(`DescribeClusterKubeconfig`)更便于 kubectl 使用: + +```bash +tccli tke DescribeClusterSecurity --region \ + --ClusterId +# expected: exit 0,返回 Kubeconfig 含 token +``` + +**预期输出**: + +```json +{ + "UserName": "admin", + "Password": "...", + "CertificationAuthority": "(CA证书内容)", + "ClusterExternalEndpoint": "", + "Domain": "cls-example.ccs.tencent-cloud.com", + "PgwEndpoint": "", + "SecurityPolicy": null, + "Kubeconfig": "apiVersion: v1\nclusters:\n- cluster:\n certificate-authority-data: ...\n server: https://cls-example.ccs.tencent-cloud.com\n...\nusers:\n- name: cls-example-admin\n user:\n token: \n", + "JnsGwEndpoint": null +} +``` + +```bash +# 写入 kubeconfig +tccli tke DescribeClusterSecurity --region --ClusterId \ + | jq -r '.Kubeconfig' > ~/.kube/config +# expected: exit 0,kubeconfig 写入成功 +``` + +> **注意**:内网端点 IP `172.24.0.16` 仅限 VPC 内部访问。本地机器 `kubectl` 不可直连,需在同 VPC CVM 或通过 IOA/VPN/专线执行 `kubectl` 命令。备选方案:将 kubeconfig 上传至同 VPC CVM,在 CVM 上执行 `kubectl` 操作。 + +## 验证 + +集群创建是异步操作,耗时约 10-15 分钟。轮询直到状态为 `Running`: + +### 控制面(tccli) + +```bash +tccli tke DescribeClusters --region \ + --ClusterIds '[""]' +# expected: exit 0,ClusterStatus: "Running" +``` + +**预期输出**(集群就绪后): + +```json +{ + "TotalCount": 1, + "Clusters": [ + { + "ClusterId": "cls-example", + "ClusterName": "", + "ClusterDescription": "", + "ClusterVersion": "1.30.0", + "ClusterOs": "ubuntu16.04.1 LTSx86_64", + "ClusterType": "MANAGED_CLUSTER", + "ClusterNetworkSettings": { + "ClusterCIDR": "10.200.0.0/16", + "IgnoreClusterCIDRConflict": false, + "MaxNodePodNum": 64, + "MaxClusterServiceNum": 4096, + "Ipvs": false, + "VpcId": "vpc-example", + "Cni": true, + "KubeProxyMode": "", + "ServiceCIDR": "10.201.0.0/20", + "Subnets": null, + "IgnoreServiceCIDRConflict": false, + "IsDualStack": false, + "Ipv6ServiceCIDR": "", + "CiliumMode": "", + "SubnetId": "subnet-example", + "DataPlaneV2": false + }, + "ClusterNodeNum": 0, + "ProjectId": 0, + "ClusterStatus": "Running", + "ClusterMaterNodeNum": 1, + "ImageId": "", + "OsCustomizeType": "", + "ContainerRuntime": "containerd", + "CreatedTime": "2026-06-23T05:24:09Z", + "DeletionProtection": true, + "EnableExternalNode": false, + "ClusterLevel": "L5", + "AutoUpgradeClusterLevel": false, + "QGPUShareEnable": false, + "RuntimeVersion": "1.6.9", + "ClusterEtcdNodeNum": 0, + "CdcId": "", + "IsHighAvailability": true, + "ClusterCategory": null, + "SecurityModeConfig": { + "Enabled": false, + "Namespaces": null, + "Labels": null + } + } + ] +} +``` + +| 验证维度 | 检查字段 | 预期值 | +|---------|---------|------| +| 集群状态 | `ClusterStatus` | `Running` | +| 网络 | `ClusterCIDR` + `ServiceCIDR` | `10.200.0.0/16` / `10.201.0.0/20` | +| 运行时 | `ContainerRuntime` + `RuntimeVersion` | `containerd` / `1.6.9` | +| 删除保护 | `DeletionProtection` | `true`(增强配置中开启) | +| 节点数 | `ClusterNodeNum` | `0`(空集群,预期行为) | + +集群状态确认: + +```bash +tccli tke DescribeClusterStatus --region \ + --ClusterIds '[""]' +# expected: exit 0,ClusterState: "Running" +``` + +**预期输出**: + +```json +{ + "ClusterStatusSet": [ + { + "ClusterId": "cls-example", + "ClusterState": "Running", + "ClusterInstanceState": "-", + "ClusterBMonitor": false, + "ClusterInitNodeNum": 0, + "ClusterRunningNodeNum": 0, + "ClusterFailedNodeNum": 0, + "ClusterClosedNodeNum": 0, + "ClusterClosingNodeNum": 0, + "ClusterDeletionProtection": false, + "ClusterAuditEnabled": false + } + ], + "TotalCount": 1 +} +``` + +### 数据面(kubectl) + +> **注意**:以下 kubectl 命令需在**同 VPC 的 CVM** 上执行。内网端点 IP 不可从本地机器直连。 + +```bash +# 获取 token 型 kubeconfig 并写入(在同 VPC CVM 上执行) +tccli tke DescribeClusterSecurity --region --ClusterId \ + | jq -r '.Kubeconfig' > ~/.kube/config +# expected: exit 0 + +# 验证 kubectl 连通性 +kubectl cluster-info +# expected: Kubernetes control plane is running at https://cls-example.ccs.tencent-cloud.com + +kubectl get ns +# expected: 返回 default、kube-system 等系统命名空间 +``` + +## 清理 + +> **警告**:`DeleteCluster` 配合 `InstanceDeleteMode: "terminate"` 会**级联删除**集群关联的所有 CVM 实例及磁盘。删除保护开启时需先关闭。生产环境执行删除前务必先 `DescribeClusters` 确认集群 ID 和名称。空集群(`ClusterNodeNum=0`)无 CVM 可删,但 `terminate` 模式仍会尝试清理关联资源。 + +安装依赖倒序清理: + +```bash +# 1. 清理前状态检查 +tccli tke DescribeClusters --region --ClusterIds '[""]' +# 确认 ClusterId、ClusterName、ClusterNodeNum。确认是要删除的目标集群 + +# 2. 关闭删除保护(如启用) +tccli tke ModifyClusterAttribute --region \ + --ClusterId \ + --DeletionProtection false +# expected: exit 0 + +# 3. 删除集群 +tccli tke DeleteCluster --region \ + --ClusterId \ + --InstanceDeleteMode terminate +# ⚠️ --InstanceDeleteMode terminate 会删除所有关联 CVM 节点 +# expected: exit 0 + +# 4. 删除自建安全组 +tccli vpc DeleteSecurityGroup --region \ + --SecurityGroupId +# expected: exit 0 + +# 5. 验证已删除 +tccli tke DescribeClusters --region --ClusterIds '[""]' +# expected: ResourceNotFound 或返回空列表 +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `CreateCluster` 返回 `InvalidParameter.ClusterType` | 检查请求 JSON 中 `ClusterType` 字段 | 填了控制台概念名(如 `GR`)而非 API 枚举 | 改为 `"MANAGED_CLUSTER"`(托管)或 `"INDEPENDENT_CLUSTER"`(独立)。不要填 `"GR"` | +| `CreateCluster` 返回 `InvalidParameter.CidrMaskSizeOutOfRange` | 检查 ServiceCIDR 掩码 | 掩码不在 17-27 之间。`/16` 会触发此错误 | 改为 `/17` 至 `/27`,如 `/20`。同时调整 `MaxClusterServiceNum` 匹配(`/20`→4096) | +| `CreateCluster` 返回 `InvalidParameter.ClusterCIDRSettings` | `tccli vpc DescribeVpcs --region --VpcIds '[""]'` 查看 VPC CIDR 和辅助 CIDR。`tccli tke DescribeClusters --region \| jq '.Clusters[].ClusterNetworkSettings'` 对比现有集群 CIDR | Pod/Service CIDR 与 VPC CIDR、辅助 CIDR 或已有集群 CIDR 重叠 | 修改 `ClusterCIDR` 或 `ServiceCIDR` 为不重叠网段 | +| `CreateCluster` 返回 `InvalidParameter.VpcId` | `DescribeVpcs` 确认 VPC 存在且地域匹配 | VPC ID 不存在或地域不匹配 | 先 `CreateVpc` 或 `DescribeVpcs` 获取已有 VPC ID | +| `CreateCluster` 返回 `InvalidParameter.SubnetId` | `DescribeSubnets` 确认子网存在 | 子网不存在或不属于指定 VPC | 先 `CreateSubnet` 或复用已有子网 | +| `CreateCluster` 参数校验失败(`MaxClusterServiceNum` 与掩码不匹配) | 检查 `MaxClusterServiceNum` vs `ServiceCIDR` 掩码 | 数量与掩码 IP 数量不匹配。计算公式:`2^(32-掩码)-2` | `/20` → `4096`,`/24` → `256`,`/17` → `32768` | +| `CreateClusterEndpoint`(`--IsExtranet true`)返回 `InvalidParameter.Param` | 查看完整错误消息:含 `CAM deny` 和 `strategyId` 字段 | 公网端点被**组织级 CAM 策略**以 `tke:clusterExtranetEndpoint=true` 条件硬拒绝(如 `strategyId:240463971`)。**非安全组问题,是账户级硬策略,自建安全组也无法绕过** | 回退到内网端点:用 `--IsExtranet false` 创建内网端点。内网端点通过 VPC 内部 IP 访问(如 `172.24.0.16`),需通过同 VPC CVM、IOA、VPN 或专线连接 | +| `CreateClusterEndpointVip` 返回 `ResourceUnavailable.ClusterState` | `DescribeClusters` 检查 `ClusterNodeNum` | **集群无工作节点**(`ClusterNodeNum=0`)。VIP 入口要求集群至少有一个工作节点来代理流量 | 先通过 `tccli tke CreateClusterNodePool` 添加工作节点,再创建 VIP 入口。空集群不可创建 VIP 入口 | +| `DeleteCluster` 返回 `ResourceUnavailable.ClusterInDeletionProtection` | `DescribeClusters` 检查 `DeletionProtection` | 删除保护已开启 | 先 `ModifyClusterAttribute --DeletionProtection false` | + +### 创建后长时间不 Running + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 返回 ClusterId 但 15 分钟后仍非 Running | `DescribeClusters` 轮询 `ClusterStatus` | 后端创建卡住——可能是 VPC/子网不可用、CIDR 冲突、资源配额不足 | 保留 Region、ClusterId、RequestId、创建 JSON → 登录 [TKE 控制台](https://console.cloud.tencent.com/tke2/cluster) 查看详细状态 → 仍无法解决则 [提交工单](https://console.cloud.tencent.com/workorder) | +| 状态 `Abnormal` | `DescribeClusters` 查看 `StatusReason` | 网络配置问题或底层资源不足 | `DeleteCluster` 重新创建。保留 RequestId + 创建 JSON 以备排查 | +| 端点状态轮询长时间 `Creating` | `DescribeClusterEndpointStatus` 查看 `Status` 和 `ErrorMsg` | 后端创建端点缓慢或失败 | 超过 2 分钟仍 `Creating` → 检查 `ErrorMsg` → 如非空,据错误信息修正(常见:安全组配置、子网 IP 不足) | + +### kubectl 连通性故障 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 本地机器 `kubectl` 报 `dial tcp 172.24.0.16:443: i/o timeout` | `DescribeClusterEndpoints` 检查 `ClusterIntranetEndpoint` | **内网端点仅限 VPC 内部访问**。内网 IP(如 `172.24.0.16`)只能从同 VPC 的 CVM 访问。本地机器无法直连 | 方案一:在同 VPC CVM 上执行 kubectl 命令。方案二:通过 IOA/VPN/专线连接。方案三:获取 kubeconfig 后 scp 至同 VPC 跳板机执行 | +| 公网端点创建被 CAM 拒绝 | `CreateClusterEndpoint --IsExtranet true` 返回 `InvalidParameter.Param` + `CAM deny` + `strategyId` | 组织级 CAM 策略硬拒绝公网端点(条件 `tke:clusterExtranetEndpoint=true`)。此为环境限制,非命令错误 | 使用内网端点(`--IsExtranet false`)。如需公网访问,联系 CAM 管理员评估策略 `strategyId` 的豁免条件 | +| `DescribeClusterSecurity` 获取的 kubeconfig 中 `server` 地址不可达 | 检查 kubeconfig 中 `server` 字段是否为内网地址 | 内网端点 IP 需在 VPC 内部可达。公网端点(如已创建)使用 `ClusterDomain` 地址 | 确认 `server` 地址类型:内网 IP 需 VPC 内访问,域名需 DNS 可达 | + +## 下一步 + +- [连接集群](../connect) — 获取 kubeconfig 连接集群 +- [集群生命周期](../lifecycle) — 状态流转与生命周期管理 +- [添加节点池](../../../节点管理/普通节点/创建节点池) — 向集群添加工作节点 +- [集群扩缩容](../scale) — 调整集群规格 + +## 控制台替代 + +[TKE 控制台 → 集群列表 → 新建](https://console.cloud.tencent.com/tke2/cluster/create) diff --git a/src/content/docs/cli/tke/cluster-config/cluster-management/custom-control-plane-tkectl.md b/src/content/docs/cli/tke/cluster-config/cluster-management/custom-control-plane-tkectl.md new file mode 100644 index 0000000..5666d31 --- /dev/null +++ b/src/content/docs/cli/tke/cluster-config/cluster-management/custom-control-plane-tkectl.md @@ -0,0 +1,9 @@ +--- +title: "自定义控制面组件参数(产品 CLI)" +description: "自定义控制面组件参数(产品 CLI)" +--- + +> ⚠️ **P1 placeholder** — `tkectl` integration pending. See the [tccli operation page](.-tkectl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[自定义控制面组件参数](https://cloud.tencent.com/document/product/457/47775) +> P0 请使用同目录 [`tccli 操作.md`](.)。 diff --git a/src/content/docs/cli/tke/cluster-config/cluster-management/custom-control-plane.md b/src/content/docs/cli/tke/cluster-config/cluster-management/custom-control-plane.md new file mode 100644 index 0000000..b7d3d63 --- /dev/null +++ b/src/content/docs/cli/tke/cluster-config/cluster-management/custom-control-plane.md @@ -0,0 +1,566 @@ +--- +title: "自定义控制面组件参数" +description: "· page_id `47775` · tccli ≥3.1.107 · API 2018-05-25" +--- + +> 对照官方:[自定义控制面组件参数](https://cloud.tencent.com/document/product/457/47775) · page_id `47775` · tccli ≥3.1.107 · API 2018-05-25 + +## 概述 + +通过 `ModifyClusterExtraArgs` API 对 TKE 托管集群的控制面组件(kube-apiserver、kube-controller-manager、kube-scheduler)设置自定义启动参数。 + +**参数格式**:API 使用 `key=value` 格式(如 `goaway-chance=0.001`),区别于 kube-apiserver 的命令行格式 `--flag=value`。参数以字符串数组传入,通过 `--cli-input-json file://...` 指定嵌套 JSON 结构——不支持 `--KubeAPIServer` 等顶层参数。 + +**关键 API**:`DescribeClusterAvailableExtraArgs`(参数发现)返回指定集群版本和类型下所有可配置的参数完整目录(含名称、类型、用途、默认值、约束范围),是修改参数前的必读参考。 + +**异步与互斥**:`ModifyClusterExtraArgs` 返回 RequestId 后控制面组件需约 2 分钟滚动更新才能生效。期间同类修改任务互斥——不能并发提交第二个 `ModifyClusterExtraArgs` 请求。 + +| 组件 | API 字段 | 托管集群备注 | +|------|---------|------------| +| kube-apiserver | `KubeAPIServer` | 15 个可用参数(1.32.2 版本) | +| kube-controller-manager | `KubeControllerManager` | 36 个可用参数 | +| kube-scheduler | `KubeScheduler` | 5 个可用参数 | +| etcd | `Etcd` | 托管集群不暴露自定义参数,初始为 `null`,修改过其他组件后变为 `[]` | + +## 前置条件 + +- [环境准备](../../../../index.md) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置,region 为目标地域 + +# 3. 检查 CAM 权限 +# 需要: tke:DescribeClusterExtraArgs, tke:ModifyClusterExtraArgs, +# tke:DescribeClusterAvailableExtraArgs, tke:DescribeClusters, +# tke:DescribeMasterComponent, tke:DescribeClusterControllers +# 验证:执行 DescribeClusters 确认权限 +tccli tke DescribeClusters --region +# expected: exit 0,返回集群列表(可为空) +``` + +### 资源检查 + +```bash +# 4. 确认目标集群存在且状态为 Running +tccli tke DescribeClusters --region --ClusterIds '[""]' +# expected: ClusterStatus: "Running" + +# 5. 查询当前参数快照(记录基线,用于回滚) +tccli tke DescribeClusterExtraArgs --region --ClusterId +# expected: exit 0,各组件当前自定义参数 +``` + +**预期输出**: + +```json +{ + "ClusterExtraArgs": { + "Etcd": null, + "KubeAPIServer": [], + "KubeControllerManager": [], + "KubeScheduler": [] + }, + "RequestId": "..." +} +``` + +> `Etcd: null` 表示托管集群 etcd 不暴露自定义参数。其他三个组件为空数组表示未设置任何自定义参数。 + +```bash +# 6. 查询可用参数列表(修改前必做——确认参数名、类型、约束范围) +tccli tke DescribeClusterAvailableExtraArgs \ + --region \ + --ClusterVersion \ + --ClusterType MANAGED_CLUSTER +# expected: 返回 KubeAPIServer、KubeControllerManager、KubeScheduler 各组件完整参数目录 +``` + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 查看可用参数列表 | `DescribeClusterAvailableExtraArgs` | 是 | +| 查看当前参数 | `DescribeClusterExtraArgs` | 是 | +| 设置自定义参数 | `ModifyClusterExtraArgs` | 是 | +| 查看控制面组件状态 | `DescribeMasterComponent` | 是 | +| 查看控制器状态 | `DescribeClusterControllers` | 是 | + +### 关键字段说明 + +`ModifyClusterExtraArgs` 必须通过 `--cli-input-json file://...` 传入嵌套 JSON,不支持 `--KubeAPIServer` 等顶层参数。完整参数结构见下文 JSON 示例。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `ClusterId` | String | 是 | 格式 `cls-xxxxxxxx`。`tccli tke DescribeClusters` 获取 | 集群不存在 → `InvalidParameter.ClusterId` | +| `ClusterExtraArgs.KubeAPIServer` | Array[String] | 否 | `["key=value", ...]`,格式 `key=value`(不带 `--` 前缀)。15 个可用参数,具体见 `DescribeClusterAvailableExtraArgs` | 使用 `--flag=value` 格式 → 参数不会被正确应用 | +| `ClusterExtraArgs.KubeControllerManager` | Array[String] | 否 | 同上,36 个可用参数 | 同上 | +| `ClusterExtraArgs.KubeScheduler` | Array[String] | 否 | 同上,5 个可用参数 | 同上 | +| `ClusterExtraArgs.Etcd` | Array[String] | 否 | 托管集群不暴露自定义参数,传 `[]` 或不传 | 传非空值 → 可能被忽略或报错 | + +## 操作步骤 + +### 步骤 1:查询可用参数目录 + +修改前必须先了解哪些参数可用、类型是什么、约束范围是多少。`DescribeClusterAvailableExtraArgs` 是官方页面未列出但极其关键的 API。 + +```bash +tccli tke DescribeClusterAvailableExtraArgs \ + --region \ + --ClusterVersion \ + --ClusterType MANAGED_CLUSTER +# expected: exit 0,返回三个组件的完整参数目录 +``` + +**预期输出**(节选,完整列表含 KubeAPIServer 15 个、KubeControllerManager 36 个、KubeScheduler 5 个参数): + +```json +{ + "ClusterVersion": "1.32.2", + "AvailableExtraArgs": { + "KubeAPIServer": [ + { + "Name": "goaway-chance", + "Type": "float", + "Usage": "To prevent HTTP/2 clients from getting stuck on a single apiserver...", + "Default": "0", + "Constraint": "[0,0.02]" + }, + { + "Name": "max-requests-inflight", + "Type": "int", + "Usage": "The maximum number of non-mutating requests in flight...", + "Default": "400", + "Constraint": "[1, 3000]" + }, + { + "Name": "max-mutating-requests-inflight", + "Type": "int", + "Usage": "The maximum number of mutating requests in flight at a given time...", + "Default": "200", + "Constraint": "[1, 2000]" + }, + { + "Name": "feature-gates", + "Type": "mapStringBool", + "Usage": "A set of key=value pairs that describe feature gates for alpha/experimental features", + "Default": "", + "Constraint": "('PodShareProcessNamespace=true', 'DynamicKubeletConfig=true', 'DebugContainers=true')" + }, + { + "Name": "request-timeout", + "Type": "duration", + "Usage": "An optional field indicating the duration a handler must keep a request open before timing it out...", + "Default": "1m0s", + "Constraint": "[10s, 5m]" + }, + { + "Name": "tls-min-version", + "Type": "string", + "Usage": "Minimum TLS version supported...", + "Default": "", + "Constraint": "('VersionTLS10','VersionTLS11','VersionTLS12','VersionTLS13')" + } + ], + "KubeControllerManager": [ + { + "Name": "kube-api-burst", + "Type": "int32", + "Default": "30", + "Constraint": "[1, 2000]" + }, + { + "Name": "kube-api-qps", + "Type": "float32", + "Default": "20", + "Constraint": "[1,2000]" + }, + { + "Name": "node-monitor-grace-period", + "Type": "duration", + "Default": "40s", + "Constraint": "[1s, 24h]" + } + ], + "KubeScheduler": [ + { + "Name": "v", + "Type": "int32", + "Default": "3", + "Constraint": "[0,10]" + }, + { + "Name": "kube-api-burst", + "Type": "int32", + "Default": "100", + "Constraint": "[1, 2000]" + }, + { + "Name": "kube-api-qps", + "Type": "float32", + "Default": "50", + "Constraint": "[1, 2000]" + } + ] + }, + "RequestId": "..." +} +``` + +| 字段 | 含义 | +|------|------| +| `Name` | 参数名(即 API 中使用的 key,如 `goaway-chance`) | +| `Type` | 参数类型(`int`、`float`、`string`、`duration`、`mapStringBool` 等) | +| `Default` | 默认值(空字符串表示无预设默认值) | +| `Constraint` | 取值范围(区间 `[min,max]` 或枚举值列表) | +| `Usage` | 参数用途说明(仅部分参数有) | + +### 步骤 2:查看当前参数快照 + +修改前务必查看当前配置,保存为回滚基线。 + +```bash +tccli tke DescribeClusterExtraArgs --region --ClusterId +# expected: exit 0,记录修改前各组件参数值 +``` + +**预期输出**(新集群基线): + +```json +{ + "ClusterExtraArgs": { + "Etcd": null, + "KubeAPIServer": [], + "KubeControllerManager": [], + "KubeScheduler": [] + }, + "RequestId": "..." +} +``` + +### 步骤 3:检查控制面组件运行状态 + +修改前确认组件健康,避免在异常状态下修改参数。 + +```bash +# 检查 kube-apiserver +tccli tke DescribeMasterComponent --region \ + --ClusterId \ + --Component kube-apiserver +# expected: Status: "Running" + +# 检查 kube-scheduler +tccli tke DescribeMasterComponent --region \ + --ClusterId \ + --Component kube-scheduler +# expected: Status: "Running" + +# 检查 kube-controller-manager +tccli tke DescribeMasterComponent --region \ + --ClusterId \ + --Component kube-controller-manager +# expected: Status: "Running" +``` + +**预期输出**(kube-apiserver): + +```json +{ + "Component": "kube-apiserver", + "Status": "Running", + "RequestId": "..." +} +``` + +(kube-scheduler): + +```json +{ + "Component": "kube-scheduler", + "Status": "Running", + "RequestId": "..." +} +``` + +(kube-controller-manager): + +```json +{ + "Component": "kube-controller-manager", + "Status": "Running", + "RequestId": "..." +} +``` + +### 步骤 4:设置自定义参数 + +#### 选择依据 + +- **参数格式**:使用 `key=value`(不加 `--` 前缀)。这是 `ModifyClusterExtraArgs` API 的参数格式,区别于 kube-apiserver 的命令行格式 `--flag=value`。真机测试 `goaway-chance=0.001` 验证通过。 +- **API 调用方式**:必须通过 `--cli-input-json file:///tmp/input.json` 传入嵌套 JSON。不支持 `--KubeAPIServer` 等顶层参数——这些参数在 API skeleton 中不存在。 +- **异步操作**:`ModifyClusterExtraArgs` 返回 RequestId 后立即完成 API 调用,但控制面组件参数的实际生效需要约 2 分钟(控制面滚动更新)。期间 `DescribeClusterExtraArgs` 可能仍返回旧值。 +- **任务互斥**:同类修改任务不能并发。在一次 `ModifyClusterExtraArgs` 任务执行期间提交第二次,会返回 `OperationDenied: same type task in execution`。 +- **参数发现**:修改前必须通过 `DescribeClusterAvailableExtraArgs` 确认参数名称、类型和约束范围,避免使用不存在的参数名或超出约束范围的值。 + +#### 最小修改(单参数,kube-apiserver) + +创建输入文件 `extra-args.json`: + +```json +{ + "ClusterId": "", + "ClusterExtraArgs": { + "KubeAPIServer": ["goaway-chance=0.001"] + } +} +``` + +```bash +tccli tke ModifyClusterExtraArgs --region \ + --cli-input-json file://extra-args.json +# expected: exit 0,返回 RequestId。控制面滚动更新约 2 分钟。 +``` + +**预期输出**: + +```json +{ + "RequestId": "..." +} +``` + +#### 增强修改(多参数,多组件) + +创建输入文件 `extra-args-enhanced.json`: + +```json +{ + "ClusterId": "", + "ClusterExtraArgs": { + "KubeAPIServer": [ + "goaway-chance=0.001", + "max-requests-inflight=600", + "max-mutating-requests-inflight=300", + "request-timeout=2m0s" + ], + "KubeControllerManager": [ + "kube-api-burst=50", + "kube-api-qps=30", + "node-monitor-grace-period=60s" + ], + "KubeScheduler": [ + "kube-api-qps=100" + ] + } +} +``` + +```bash +tccli tke ModifyClusterExtraArgs --region \ + --cli-input-json file://extra-args-enhanced.json +# expected: exit 0,返回 RequestId +``` + +| 占位符 | 说明 | 获取方式 | +|--------|------|---------| +| `` | 集群 ID,格式 `cls-xxxxxxxx` | `tccli tke DescribeClusters --region ` | +| `` | K8s 版本,如 `1.32.2` | `tccli tke DescribeClusters --region --ClusterIds '[""]'` | +| `` | 地域,如 `ap-guangzhou` | `tccli configure list` | + +### 步骤 5:验证参数已生效 + +修改是异步操作,控制面滚动更新约需 2 分钟。轮询验证直到所有目标组件参数与修改值一致。 + +```bash +# 轮询查询——等待约 2 分钟后执行 +tccli tke DescribeClusterExtraArgs --region --ClusterId +# expected: KubeAPIServer 包含 "goaway-chance=0.001" +``` + +**预期输出**(修改生效后): + +```json +{ + "ClusterExtraArgs": { + "Etcd": [], + "KubeAPIServer": ["goaway-chance=0.001"], + "KubeControllerManager": [], + "KubeScheduler": [] + }, + "RequestId": "..." +} +``` + +> 注意:修改过 KubeAPIServer 后,`Etcd` 从 `null` 变为 `[]`。这是 DescribeClusterExtraArgs 的正常行为——托管集群 etcd 不暴露自定义参数,但修改任一组件后查询结果中 Etcd 字段会从 null 变为空数组。 + +## 验证 + +### 控制面(tccli) + +```bash +# 1. 确认所有组件参数与修改值一致 +tccli tke DescribeClusterExtraArgs --region --ClusterId +# expected: 目标组件参数列表与修改值完全一致 + +# 2. 确认集群状态为 Running +tccli tke DescribeClusters --region --ClusterIds '[""]' +# expected: ClusterStatus: "Running" + +# 3. 确认控制面组件运行状态 +tccli tke DescribeMasterComponent --region \ + --ClusterId \ + --Component kube-apiserver +# expected: Status: "Running" + +# 4. 确认控制器状态 +tccli tke DescribeClusterControllers --region --ClusterId +# expected: route-controller 和 node-ipam-controller 均为 Enabled +``` + +**预期输出**(控制器状态): + +```json +{ + "ControllerStatusSet": [ + {"Name": "route-controller", "Enabled": true}, + {"Name": "node-ipam-controller", "Enabled": true} + ], + "RequestId": "..." +} +``` + +**预期输出**(参数一致性): + +```json +{ + "ClusterExtraArgs": { + "Etcd": [], + "KubeAPIServer": ["goaway-chance=0.001"], + "KubeControllerManager": [], + "KubeScheduler": [] + }, + "RequestId": "..." +} +``` + +| 验证维度 | 命令 | 预期 | +|---------|------|------| +| 参数一致性 | `DescribeClusterExtraArgs` | 目标组件参数与修改值完全一致 | +| 集群状态 | `DescribeClusters` | `ClusterStatus: "Running"` | +| 组件运行 | `DescribeMasterComponent`(三个组件各查一次) | 每个组件 `Status: "Running"` | +| 控制器状态 | `DescribeClusterControllers` | route-controller、node-ipam-controller 均为 Enabled | + +## 清理 + +> **警告**:回退参数同样是异步操作,提交清空请求后需等待约 2 分钟控制面滚动更新完成。 +> 错误参数可能导致控制面组件不可用。不要在 KubeAPIServer 中覆盖认证/授权相关参数。 +> 修改后控制面滚动更新期间集群仍可对外服务,但 API Server 可能有短暂抖动。 + +### 1. 清理前状态检查 + +```bash +tccli tke DescribeClusterExtraArgs --region --ClusterId +# 确认当前参数值,记录作为回滚目标基线 +``` + +**预期输出**(修改后基线): + +```json +{ + "ClusterExtraArgs": { + "Etcd": [], + "KubeAPIServer": ["goaway-chance=0.001"], + "KubeControllerManager": [], + "KubeScheduler": [] + }, + "RequestId": "..." +} +``` + +### 2. 回退参数到默认 + +创建回退文件 `revert-args.json`: + +```json +{ + "ClusterId": "", + "ClusterExtraArgs": { + "KubeAPIServer": [] + } +} +``` + +```bash +tccli tke ModifyClusterExtraArgs --region \ + --cli-input-json file://revert-args.json +# expected: exit 0,返回 RequestId +``` + +> **注意**:`ModifyClusterExtraArgs` 是异步互斥操作。如果上一次修改任务还在执行中,提交回退请求会返回 +> `OperationDenied: The operation you submit has a same type task in execution`。 +> 解决方案:等待 `DescribeClusterExtraArgs` 确认上次修改已生效后,再提交回退请求。 + +### 3. 验证已清理 + +```bash +# 等待约 2 分钟后验证 +tccli tke DescribeClusterExtraArgs --region --ClusterId +# expected: KubeAPIServer、KubeControllerManager、KubeScheduler 均为 [],Etcd 为 null 或 [] +``` + +**预期输出**(清理后基线): + +```json +{ + "ClusterExtraArgs": { + "Etcd": null, + "KubeAPIServer": [], + "KubeControllerManager": [], + "KubeScheduler": [] + }, + "RequestId": "..." +} +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ModifyClusterExtraArgs` 返回 `Unknown options: --KubeAPIServer` | 检查命令是否使用了 `--KubeAPIServer` 等顶层参数 | 使用了不存在的顶层参数——API skeleton 中只有 `--cli-input-json` 和 `--ClusterId`(顶层),`KubeAPIServer` 等是 `ClusterExtraArgs` 的嵌套子字段 | 改用 `--cli-input-json file://input.json` 传入嵌套 JSON。示例见 [步骤 4](#步骤-4设置自定义参数) | +| `ModifyClusterExtraArgs` 返回参数未被应用(无显式错误但参数不生效) | 检查 `ClusterExtraArgs` 数组元素格式 | 使用了 `--goaway-chance=0.001` 格式(带 `--` 前缀)而非 API 要求的 `key=value` 格式 | 改为 `goaway-chance=0.001`(不带 `--` 前缀)。API 参数格式为 `key=value`,不是 kube-apiserver 的命令行格式 | +| `ModifyClusterExtraArgs` 返回 `OperationDenied: The operation you submit has a same type task in execution` | 检查是否有正在进行中的 `ModifyClusterExtraArgs` 任务 | 同类修改任务互斥——在上一次 `ModifyClusterExtraArgs` 任务完成之前提交了第二次请求 | 等待约 2 分钟,用 `DescribeClusterExtraArgs` 确认上次修改已生效后重试。`ModifyClusterExtraArgs` 是异步互斥操作,不能并发执行 | +| `ModifyClusterExtraArgs` 返回 `InvalidParameter` | 检查参数名是否在 `DescribeClusterAvailableExtraArgs` 返回的可用列表中 | 参数名不存在于当前版本的可用参数目录中 | 先执行 `DescribeClusterAvailableExtraArgs --ClusterVersion --ClusterType MANAGED_CLUSTER` 确认参数名和约束范围 | + +### 修改已提交但参数未生效 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 返回了 RequestId 但 2 分钟后 `DescribeClusterExtraArgs` 仍显示旧值 | 再次执行 `DescribeClusterExtraArgs`,对比参数列表 | 控制面滚动更新尚未完成(正常行为,可能超过 2 分钟) | 继续等待,每 30 秒轮询一次。保留 Region、ClusterId、RequestId。超过 5 分钟仍未生效 → 登录 [TKE 控制台](https://console.cloud.tencent.com/tke2/cluster) 查看控制面状态 | +| 回退操作被 `OperationDenied` 拒绝 | 检查是否有前一次修改任务正在执行 | 前一次 `ModifyClusterExtraArgs` 任务尚未完成,同类任务互斥 | `DescribeClusterExtraArgs` 确认前次修改已生效 → 重试回退。见上表同名错误修复方案 | +| 修改后控制面组件状态异常 | `DescribeMasterComponent --Component kube-apiserver` 检查组件状态 | 参数值超出约束范围或与其他参数冲突 | `DescribeClusterAvailableExtraArgs` 确认参数的 `Constraint` 字段 → 调整为合法值 → 重新 `ModifyClusterExtraArgs` | + +### 格式要求 + +- 排障过程中请保留 Region、ClusterId、RequestId 和修改前后的参数值,以备工单查询 +- `OperationDenied: same type task in execution` 是 API 设计保证——不是 bug,等待即可 +- 参数不存在于 `DescribeClusterAvailableExtraArgs` 列表中 → 请勿尝试传入未列出的参数名 + +## 下一步 + +- [DescribeClusterAvailableExtraArgs API 文档](https://cloud.tencent.com/document/api/457/92791) — 查询可用参数完整目录 +- [升级集群](../upgrade) — 版本升级前确认自定义参数兼容性 +- [连接集群](../connect) — 修改参数后验证控制面可达 +- [集群管理模式说明](../management-modes) — 了解托管集群与独立集群的 etcd 差异 + +## 控制台替代 + +[TKE 控制台 → 集群详情 → 控制面组件参数](https://console.cloud.tencent.com/tke2/cluster/sub/detail/config) diff --git a/src/content/docs/cli/tke/cluster-config/cluster-management/delete.md b/src/content/docs/cli/tke/cluster-config/cluster-management/delete.md new file mode 100644 index 0000000..d0ab8d4 --- /dev/null +++ b/src/content/docs/cli/tke/cluster-config/cluster-management/delete.md @@ -0,0 +1,392 @@ +--- +title: "删除集群" +description: "· page_id `44808` · tccli ≥3.1.107 · API 2018-05-25" +--- + +> 对照官方:[删除集群](https://cloud.tencent.com/document/product/457/44808) · page_id `44808` · tccli ≥3.1.107 · API 2018-05-25 + +## 概述 + +删除 TKE 集群是**不可逆的破坏性操作**。根据 `InstanceDeleteMode` 参数,可仅移除集群记录(`retain`)或级联销毁关联的 CVM 实例及磁盘(`terminate`)。删除前必须依序处理:关闭删除保护、删除集群端点(内网/外网)、删除伸缩组(如有)。 + +`InstanceDeleteMode` 决策速览: + +| 模式 | 行为 | CVM 计费 | 适用场景 | +|------|------|:--:|------| +| `terminate` | **级联销毁**所有关联 CVM 实例及磁盘 | 停止计费 | 按量计费节点,确认不再需要 | +| `retain` | 仅移除集群记录,保留 CVM 实例及磁盘 | **继续计费** | 需保留 CVM 迁移至其他集群,或包年包月实例 | + +删除流程(依赖倒序): + +1. 检查删除保护状态 → 如需则 `DisableClusterDeletionProtection` +2. 删除集群端点 → `DeleteClusterEndpoint`(内网 + 外网) +3. 删除伸缩组 → `DeleteClusterAsGroups`(如有) +4. 删除集群 → `DeleteCluster` +5. 验证 → `DescribeClusters`(返回 `ResourceNotFound` 或空列表) + +## 前置条件 + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置 + +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tke:DescribeClusters, tke:DeleteCluster, tke:DeleteClusterEndpoint +# tke:DeleteClusterEndpointVip, tke:DeleteClusterAsGroups +# tke:DisableClusterDeletionProtection, tke:DescribeClusterEndpoints +# tke:DescribeClusterEndpointStatus +# 验证: +tccli tke DescribeClusters --region +# expected: exit 0,返回集群列表 +``` + +### 资源检查 + +```bash +# 4. 确认目标集群存在并查看状态 +tccli tke DescribeClusters --region \ + --ClusterIds '[""]' +# expected: exit 0,ClusterStatus 为 Running +``` + +核对要点:`ClusterId` 和 `ClusterName` 双重确认这是目标集群;`DeletionProtection` 若 `true` 需先关闭;`ClusterNodeNum` 确认将级联删除的节点数量。 + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 查看集群详情 | `DescribeClusters` | 是 | +| 查看集群端点 | `DescribeClusterEndpoints` | 是 | +| 查看端点状态 | `DescribeClusterEndpointStatus` | 是 | +| 关闭删除保护 | `DisableClusterDeletionProtection` | 是 | +| 删除内网/外网端点 | `DeleteClusterEndpoint` | 是 | +| 删除外网端口(旧版) | `DeleteClusterEndpointVip` | 是 | +| 删除伸缩组 | `DeleteClusterAsGroups` | 是 | +| 删除集群 | `DeleteCluster` | 是 | + +### DeleteCluster 关键字段说明 + +以下说明 `DeleteCluster` 的主要参数。完整参数定义见 `tccli tke DeleteCluster help --detail`。 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `ClusterId` | String | 是 | 集群 ID,格式 `cls-xxxxxxxx`。`DescribeClusters` 获取 | 集群不存在 → `ResourceNotFound.ClusterNotFound` | +| `InstanceDeleteMode` | String | 是 | `terminate`(销毁实例,仅支持按量计费 CVM)或 `retain`(仅移除记录,保留实例)。**包年包月实例用 terminate → 删除失败** | 填非法值 → `InvalidParameter.InstanceDeleteMode` | +| `ResourceDeleteOptions` | Array of ResourceDeleteOption | 否 | 资源删除策略数组,支持 CBS/CLB/CVM | — | +| `ResourceDeleteOptions[].ResourceType` | String | 是(当使用此字段时) | `CBS`(云硬盘)、`CLB`(负载均衡)、`CVM`(云服务器) | 类型不存在 → `InvalidParameter.ResourceType` | +| `ResourceDeleteOptions[].DeleteMode` | String | 是(当使用此字段时) | `terminate`(销毁)或 `retain`(保留)。CBS 默认 retain,CLB/CVM 默认销毁 | CBS 误设 terminate → 数据不可恢复 | +| `ResourceDeleteOptions[].SkipDeletionProtection` | Boolean | 否 | 是否跳过开启删除保护的资源,默认 `false`。CLB 有终端节点视为开启删除保护 | CLB 有终端节点且未设 `true` → 删除阻塞 | + +## 操作步骤 + +### 步骤 1:查看集群状态与删除保护 + +删除前确认集群当前状态和删除保护开关。若 `DeletionProtection` 为 `true`,需先执行步骤 6 关闭保护。 + +```bash +tccli tke DescribeClusters --ClusterIds '[""]' --region --output json | jq '{ClusterStatus: .Clusters[0].ClusterStatus, ClusterName: .Clusters[0].ClusterName, ClusterType: .Clusters[0].ClusterType, ClusterVersion: .Clusters[0].ClusterVersion, VpcId: .Clusters[0].ClusterNetworkSettings.VpcId, DeletionProtection: .Clusters[0].DeletionProtection}' +# expected: exit 0,DeletionProtection 为 false +``` + +**预期输出**: + +```json +{ + "ClusterStatus": "Running", + "ClusterName": "cls-example", + "ClusterType": "MANAGED_CLUSTER", + "ClusterVersion": "1.30.0", + "VpcId": "vpc-example", + "DeletionProtection": false +} +``` + +> 本例中删除保护已关闭。若为 `true`,须在删除集群前执行 `DisableClusterDeletionProtection`——否则 API 返回 `OperationDenied.ClusterDeletionProtectionEnabled`。 + +### 步骤 2:查看集群端点状态 + +删除集群前须确认当前端点配置。存在内网或外网端点时,需先删除端点再删集群,否则 `DeleteCluster` 返回 `OperationDenied`。 + +```bash +tccli tke DescribeClusterEndpoints --ClusterId --region --output json +# expected: exit 0,返回端点配置 +``` + +**预期输出**: + +```json +{ + "CertificationAuthority": "...", + "ClusterExternalEndpoint": "", + "ClusterIntranetEndpoint": "172.24.0.1", + "ClusterDomain": "cls-example.ccs.tencent-cloud.com", + "ClusterExternalACL": null, + "ClusterExternalDomain": "cls-example.ccs.tencent-cloud.com", + "SecurityGroup": "", + "ClusterIntranetSubnetId": "subnet-example", + "IntranetSecurityGroup": "sg-example", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +> 当前集群存在内网端点 `172.24.0.1`,无外网端点。删除集群前须先删除此内网端点。 + +### 步骤 3:删除集群端点 + +#### 选择依据 + +- **为什么必须先删端点**:集群存在端点时,`DeleteCluster` 直接返回 `OperationDenied`。端点必须先于集群删除。 +- **内网 vs 外网**:`IsExtranet=false` 删除内网端点,`IsExtranet=true` 删除外网端点。按需分别执行,二者无依赖关系。 +- **替代方式**:`DeleteClusterEndpointVip` 为旧版 API,仅支持托管集群外网端口删除。新集群推荐使用 `DeleteClusterEndpoint`。 + +#### 删除内网端点(L2 真执行) + +```bash +tccli tke DeleteClusterEndpoint --ClusterId --IsExtranet false --region --output json +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +#### 删除外网端点(如有) + +```bash +tccli tke DeleteClusterEndpoint --ClusterId --IsExtranet true --region --output json +# expected: exit 0 +``` + +### 步骤 4:删除端点 VIP(旧版方式,如有) + +仅当集群使用旧版外网端口方式时执行。仅支持托管集群。 + +```bash +tccli tke DeleteClusterEndpointVip --ClusterId --region --output json +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +### 步骤 5:验证端点已删除(L2 闭环) + +确认端点删除操作完成,状态为 `Deleted`。 + +```bash +tccli tke DescribeClusterEndpointStatus --ClusterId --IsExtranet false --region --output json +# expected: exit 0,Status 为 Deleted +``` + +**预期输出**: + +```json +{ + "Status": "Deleted", + "ErrorMsg": "Deleted", + "RequestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + +> L2 闭环验证通过:端点状态为 `Deleted`,删除操作已确认生效。 + +### 步骤 6:关闭删除保护(如步骤 1 检测到已启用) + +若步骤 1 中 `DeletionProtection` 为 `true`,必须关闭删除保护后才能删除集群。 + +```bash +tccli tke DisableClusterDeletionProtection --ClusterId --region --output json +# expected: exit 0 +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | 集群 ID | 格式 `cls-xxxxxxxx` | `tccli tke DescribeClusters` | +| `` | 地域 | 如 `ap-guangzhou` | `tccli configure list` | + +### 步骤 7:删除集群伸缩组(如有) + +若集群绑定了弹性伸缩组(ASG),需先删除伸缩组再删集群。 + +```bash +tccli tke DeleteClusterAsGroups --ClusterId \ + --AutoScalingGroupIds '[""]' \ + --region --output json +# expected: exit 0 +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | 集群 ID | 格式 `cls-xxxxxxxx` | `tccli tke DescribeClusters` | +| `` | 伸缩组 ID | 格式 `asg-xxxxxxxx` | `tccli tke DescribeClusterAsGroups` | +| `` | 地域 | 如 `ap-guangzhou` | `tccli configure list` | + +`KeepInstance` 可选参数:默认 `false`(不保留伸缩组中的 CVM 节点)。设为 `true` 时保留节点。 + +### 步骤 8:删除集群 + +#### 选择依据 + +*以下决策依据来自生产环境执行经验。* + +- **集群类型**:本例为 `MANAGED_CLUSTER`(托管集群),控制面由腾讯云运维。独立集群(`INDEPENDENT_CLUSTER`)删除流程与托管集群一致,两种类型无差异。 + +- **`InstanceDeleteMode` 选择**: + - `terminate`(销毁实例):级联删除所有关联 CVM 实例及磁盘。**仅支持按量计费 CVM**,含包年包月实例时删除失败。操作不可逆。 + - `retain`(仅移除记录):保留 CVM 实例,集群记录从 TKE 中移除。CVM 变成游离状态,需手动管理,否则**持续计费**。 + - **常见错误**:选 `terminate` 但节点含包年包月实例 → 删除失败;选 `retain` 后忘记手动清理残留 CVM → 持续计费。 + +- **`ResourceDeleteOptions`(CBS/CLB/CVM 删除策略)**: + - `CBS`:云硬盘。**默认 retain(保留)**——即使 `InstanceDeleteMode=terminate`,CBS 也不会被自动删除,除非显式设置 `DeleteMode=terminate`。 + - `CLB`:负载均衡。默认销毁。如有终端节点视为开启删除保护,需设 `SkipDeletionProtection=true` 跳过。 + - `CVM`:云服务器。按 `InstanceDeleteMode` 处理。 + +- **删除顺序**:必须按 端点 → 伸缩组 → 集群 的顺序执行。端点未删除就直接删集群 → API 返回 `OperationDenied`。 + +#### 最小删除(仅移除集群记录,保留 CVM 和 CBS) + +`delete-cluster-minimal.json`: + +```json +{ + "ClusterId": "", + "InstanceDeleteMode": "retain" +} +``` + +```bash +tccli tke DeleteCluster --region --cli-input-json file://delete-cluster-minimal.json +# expected: exit 0 +``` + +> **警告**:`retain` 模式保留 CVM 实例,需手动清理,否则持续计费。CBS 云硬盘默认保留。 + +#### 增强配置(销毁实例 + 级联删除 CBS + 跳过 CLB 保护) + +`delete-cluster-enhanced.json`: + +```json +{ + "ClusterId": "", + "InstanceDeleteMode": "terminate", + "ResourceDeleteOptions": [ + { + "ResourceType": "CBS", + "DeleteMode": "terminate" + }, + { + "ResourceType": "CLB", + "DeleteMode": "terminate", + "SkipDeletionProtection": true + } + ] +} +``` + +```bash +tccli tke DeleteCluster --region --cli-input-json file://delete-cluster-enhanced.json +# expected: exit 0 +``` + +> **警告**:`terminate` 模式级联删除所有关联 CVM 实例及磁盘,不可逆。CBS `DeleteMode=terminate` 销毁云硬盘,数据不可恢复。生产环境执行前务必双重确认 `ClusterId`。 + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | 集群 ID | 格式 `cls-xxxxxxxx` | `tccli tke DescribeClusters` | +| `` | 地域 | 如 `ap-guangzhou` | `tccli configure list` | + +## 验证 + +集群删除为异步操作。多维度确认删除完成: + +```bash +tccli tke DescribeClusters --region --ClusterIds '[""]' +# expected: ResourceNotFound 或 TotalCount: 0 +``` + +| 验证维度 | 命令 | 预期 | +|------|------|------| +| 集群状态 | `DescribeClusters --ClusterIds '[""]'` | `ResourceNotFound.ClusterNotFound` 或 `Clusters` 数组为空 | +| CVM 残留(retain 模式) | `tccli cvm DescribeInstances --region --Filters '[{"Name":"instance-name","Values":["*"]}]'` | 如选 `retain` 模式,确认 CVM 已被手动清理,否则持续计费 | +| CBS 残留 | `tccli cbs DescribeDisks --region ` 搜索关联磁盘 | 如未设 CBS `DeleteMode=terminate`,磁盘保留且持续计费,需手动清理 | +| 端点状态 | `DescribeClusterEndpointStatus --ClusterId ` | 集群已删后此命令也返回 `ResourceNotFound` | + +## 清理 + +> **⚠️ 副作用警告**:`DeleteCluster` 配合 `InstanceDeleteMode: "terminate"` 会**级联删除**集群关联的所有 CVM 实例及磁盘,操作**不可逆**。`retain` 模式仅移除集群记录,保留 CVM 实例——**需手动清理,否则持续计费**。CBS 默认 `retain`——即使 `terminate` 模式,CBS 也不会被自动删除(除非显式设置 `ResourceDeleteOptions[].DeleteMode=terminate`)。 +> +> 生产环境执行前务必先执行 `DescribeClusters` 确认集群 ID 和名称正确,避免误删。 + +### 清理前状态检查 + +```bash +tccli tke DescribeClusters --region --ClusterIds '[""]' +# 确认是待删除的目标集群,记录 ClusterId、ClusterName +``` + +### 手动清理残留 CVM(retain 模式) + +若使用了 `retain` 模式,CVM 实例残留,需手动销毁: + +```bash +# 查询残留 CVM +tccli cvm DescribeInstances --region \ + --Filters '[{"Name":"instance-name","Values":["*"]}]' + +# 手动销毁 +tccli cvm TerminateInstances --region --InstanceIds '[""]' +``` + +### 验证已删除 + +```bash +tccli tke DescribeClusters --region --ClusterIds '[""]' +# expected: ResourceNotFound 或空列表 +``` + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `DeleteCluster` 返回 `OperationDenied` | `tccli tke DescribeClusterEndpoints --ClusterId ` 检查端点状态 | 集群存在端点(内网/外网),删除前需先删除端点 | `tccli tke DeleteClusterEndpoint --ClusterId --IsExtranet false` 删除内网端点;`--IsExtranet true` 删除外网端点。确认 `DescribeClusterEndpointStatus` 返回 `Deleted` 后重试 | +| `DeleteCluster` 返回 `OperationDenied.ClusterDeletionProtectionEnabled` | `tccli tke DescribeClusters --ClusterIds '[""]'` 查看 `DeletionProtection` 字段 | 集群开启了删除保护 | `tccli tke DisableClusterDeletionProtection --ClusterId --region ` 关闭保护后重试 | +| `DeleteCluster` 返回 `InvalidParameter.InstanceDeleteMode` | 检查请求 JSON 中的 `InstanceDeleteMode` 字段 | 填了非法的删除模式值 | 合法值仅 `terminate` 或 `retain`。检查拼写(大小写敏感,不可用 `Terminate` 或 `delete`) | +| `DeleteCluster` 返回 `InvalidParameter.ResourceType` | 检查 `ResourceDeleteOptions` 中的 `ResourceType` 值 | 填了不支持资源类型 | 合法值:`CBS`、`CLB`、`CVM`。区分大小写 | + +### 删除成功但有残留 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 集群删除成功但 CVM 实例仍在运行 | `tccli cvm DescribeInstances --region ` 搜索残留实例 | `InstanceDeleteMode` 选了 `retain`,仅移除了集群记录,CVM 未被销毁 | `tccli cvm TerminateInstances --region --InstanceIds '[""]'` 手动销毁 CVM。保留 RequestId 和 InstanceId 以备审计 | +| 集群删除成功但 CBS 云硬盘未被销毁 | `tccli cbs DescribeDisks --region ` 搜索残留磁盘 | CBS 默认 `retain`,即使 `terminate` 模式下也未设 `ResourceDeleteOptions[].DeleteMode=terminate` | `tccli cbs TerminateDisks --region --DiskIds '[""]'` 手动销毁磁盘 | +| 集群删除成功但 CLB 未被销毁 | `tccli clb DescribeLoadBalancers --region ` 搜索残留 CLB | CLB 有终端节点,`SkipDeletionProtection` 未设为 `true`,删除被跳过 | 在 TKE 控制台手动清理,或下次删除时设置 `"SkipDeletionProtection": true` | + +## 下一步 + +- [集群管理模式说明](../management-modes) — 了解托管集群与独立集群的区别 +- [创建集群](../create) — 重新创建集群的 CLI 操作指南 +- [连接集群](../connect) — 如需创建新集群并配置访问端点 +- [关闭集群审计](https://cloud.tencent.com/document/product/457/46823) — 删除集群前关闭审计(如已启用) +- [TKE 集群管理](https://console.cloud.tencent.com/tke2/cluster) — TKE 控制台集群列表 + +## 控制台替代 + +通过 [TKE 控制台](https://console.cloud.tencent.com/tke2/cluster) 删除集群:选择目标集群 → 更多 → 删除。 diff --git a/src/content/docs/cli/tke/cluster-config/cluster-management/lifecycle-tkectl.md b/src/content/docs/cli/tke/cluster-config/cluster-management/lifecycle-tkectl.md new file mode 100644 index 0000000..a037331 --- /dev/null +++ b/src/content/docs/cli/tke/cluster-config/cluster-management/lifecycle-tkectl.md @@ -0,0 +1,9 @@ +--- +title: "集群生命周期(产品 CLI)" +description: "集群生命周期(产品 CLI)" +--- + +> ⚠️ **P1 placeholder** — `tkectl` integration pending. See the [tccli operation page](.-tkectl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[集群生命周期](https://cloud.tencent.com/document/product/457/32188) +> P0 请使用同目录 [`tccli 操作.md`](.)。 diff --git a/src/content/docs/cli/tke/cluster-config/cluster-management/lifecycle.md b/src/content/docs/cli/tke/cluster-config/cluster-management/lifecycle.md new file mode 100644 index 0000000..3000734 --- /dev/null +++ b/src/content/docs/cli/tke/cluster-config/cluster-management/lifecycle.md @@ -0,0 +1,278 @@ +--- +title: "集群生命周期" +description: "· page_id `32188`" +--- + +> 对照官方:[集群生命周期](https://cloud.tencent.com/document/product/457/32188) · page_id `32188` + +## 概述 + +TKE 集群从创建到销毁经历多个状态阶段。通过 `DescribeClusterStatus` 查询集群运行状态(`ClusterState`)、删除保护(`ClusterDeletionProtection`)和审计状态(`ClusterAuditEnabled`),通过 `DescribeClusters` 查看完整配置。通过 `ModifyClusterAttribute` 修改名称、描述等属性,通过 `DisableClusterDeletionProtection` 控制删除保护。 + +> **推荐**:仅需查询状态和删除保护时优先使用 `DescribeClusterStatus`(响应快);需完整配置时使用 `DescribeClusters`。 + +### 状态流转 + +``` +Creating → Running ⇄ Upgrading → Running → Deleting → 已删除 + ↓ + Abnormal +``` + +| 状态 | `ClusterStatus` 值 | 含义 | 禁止操作 | +|------|:--:|------|------| +| 创建中 | `Creating` | 控制面和网络初始化中(约 10-15 分钟) | 所有写操作 | +| 运行中 | `Running` | 集群正常运行 | 无 | +| 升级中 | `Upgrading` | K8s 版本升级进行中 | 修改属性、删除、再次升级 | +| 删除中 | `Deleting` | 集群正在删除 | 所有操作 | +| 异常 | `Abnormal` | 集群状态异常 | 升级、扩缩容等变更操作 | +| 欠费隔离 | `Isolated` | 后付费集群欠费隔离 | 写操作 | + +## 前置条件 + +- [环境准备](../../../../index.md) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置 + +# 3. 检查 CAM 权限 +# 需要: tke:DescribeClusters, tke:ModifyClusterAttribute, +# tke:EnableClusterDeletionProtection, tke:DisableClusterDeletionProtection +tccli tke DescribeClusters --region +# expected: exit 0 +``` + +```json +{ + "TotalCount": "", + "Clusters": "", + "ClusterId": "", + "ClusterName": "", + "ClusterDescription": "", + "ClusterVersion": "" +} +``` + +### 资源检查 + +```bash +# 4. 确认目标集群存在 +tccli tke DescribeClusters --region --ClusterIds '[""]' +# expected: TotalCount >= 1 +``` + +```json +{ + "TotalCount": "", + "Clusters": "", + "ClusterId": "", + "ClusterName": "", + "ClusterDescription": "", + "ClusterVersion": "" +} +``` + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 快速查看运行状态和删除保护 | `DescribeClusterStatus` | 是 | +| 查看集群完整详情 | `DescribeClusters` | 是 | +| 修改集群名称、描述 | `ModifyClusterAttribute` | 是 | +| 开启删除保护 | `EnableClusterDeletionProtection` | 是 | +| 关闭删除保护 | `DisableClusterDeletionProtection` | 是 | + +## 关键字段说明 + +| 字段 | 来源 API | 类型 | 说明 | +|------|---------|------|------| +| `ClusterState` | `DescribeClusterStatus` | String | 状态:`Creating`/`Running`/`Upgrading`/`Deleting`/`Abnormal`/`Closed` | +| `ClusterStatus` | `DescribeClusters` | String | 状态:`Creating`/`Running`/`Upgrading`/`Deleting`/`Abnormal`/`Isolated` | +| `ClusterDeletionProtection` | `DescribeClusterStatus` | Boolean | 删除保护是否开启 | +| `DeletionProtection` | `DescribeClusters` | Boolean | 同 `ClusterDeletionProtection`,`true` 时禁止删除 | +| `ClusterAuditEnabled` | `DescribeClusterStatus` | Boolean | 审计是否开启 | + +> `DescribeClusterStatus` 用 `Closed` 表示隔离状态,`DescribeClusters` 用 `Isolated`。两者含义一致。 + +## 操作步骤 + +### 步骤 1:查询集群状态 + +```bash +tccli tke DescribeClusterStatus --region \ + --ClusterIds '[""]' +# expected: exit 0,返回集群当前状态 +``` + +**预期输出**: + +```json +{ + "ClusterStatusSet": [ + { + "ClusterId": "cls-example", + "ClusterState": "Running", + "ClusterInstanceState": "-", + "ClusterBMonitor": false, + "ClusterDeletionProtection": true, + "ClusterAuditEnabled": false + } + ], + "RequestId": "..." +} +``` + +### 步骤 2:查看集群完整详情 + +```bash +tccli tke DescribeClusters --region \ + --ClusterIds '[""]' +# expected: exit 0,返回完整集群信息 +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Clusters": [ + { + "ClusterId": "cls-example", + "ClusterName": "", + "ClusterType": "MANAGED_CLUSTER", + "ClusterVersion": "1.30.0", + "ClusterStatus": "Running", + "ClusterNodeNum": 0, + "ClusterMaterNodeNum": 1, + "CreatedTime": "2026-06-18T03:08:25Z", + "DeletionProtection": true, + "VpcId": "vpc-example", + "ContainerRuntime": "containerd", + "ClusterLevel": "L5" + } + ], + "RequestId": "..." +} +``` + +### 步骤 3:按状态过滤集群 + +```bash +# 仅列出运行中的集群 +tccli tke DescribeClusters --region \ + --Filters '[{"Name":"ClusterStatus","Values":["Running"]}]' +# expected: 返回 ClusterStatus 均为 Running 的集群列表 +``` + +```json +{ + "TotalCount": "", + "Clusters": "", + "ClusterId": "", + "ClusterName": "", + "ClusterDescription": "", + "ClusterVersion": "" +} +``` + +### 步骤 4:修改集群属性 + +```bash +# 修改集群名称 +tccli tke ModifyClusterAttribute --region \ + --ClusterId \ + --ClusterName "" +# expected: exit 0 +``` + +### 步骤 5:开关删除保护 + +```bash +# 开启删除保护 +tccli tke EnableClusterDeletionProtection --region --ClusterId +# expected: exit 0 + +# 关闭删除保护(删除集群前必须) +tccli tke DisableClusterDeletionProtection --region --ClusterId +# expected: exit 0 +``` + +| 占位符 | 说明 | 获取方式 | +|--------|------|---------| +| `` | 目标集群 ID,格式 `cls-xxxxxxxx` | `tccli tke DescribeClusters --region ` | +| `` | 地域,如 `ap-guangzhou` | `tccli configure list` | +| `` | 新集群名称,长度 1-60 字符 | 自定义 | + +## 验证 + +| 验证项 | 命令 | 预期 | +|--------|------|------| +| 集群状态 | `DescribeClusterStatus` → `ClusterState` | `"Running"` | +| 删除保护 | `DescribeClusterStatus` → `ClusterDeletionProtection` | 与操作预期一致 | +| 审计状态 | `DescribeClusterStatus` → `ClusterAuditEnabled` | `true` / `false` | +| 名称已修改 | `DescribeClusters` → `ClusterName` | `""` | + +## 清理 + +本页主要为读操作和属性修改操作。如启用了删除保护且在不需要时: + +```bash +# 清理前确认状态 +tccli tke DescribeClusterStatus --region --ClusterIds '[""]' +# 确认 ClusterDeletionProtection 当前状态 + +# 关闭删除保护(如已开启) +tccli tke DisableClusterDeletionProtection --region --ClusterId +# expected: exit 0 +``` + +```json +{ + "ClusterStatusSet": "", + "ClusterId": "", + "ClusterState": "", + "ClusterInstanceState": "", + "ClusterBMonitor": "", + "ClusterInitNodeNum": "" +} +``` + +> **风险说明**:删除保护是防止误删的关键安全机制。关闭后任何有 `tke:DeleteCluster` 权限的主体均可删除该集群。仅在确认需要删除时关闭。 + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `DeleteCluster` 返回 `OperationDenied` | `DescribeClusterStatus` 确认 `ClusterDeletionProtection: true` | 删除保护已开启 | `DisableClusterDeletionProtection` 关闭后重试 | +| `ModifyClusterAttribute` 返回 "cluster is in upgrading" | `DescribeClusterStatus` 查看 `ClusterState` | 集群正在升级(`Upgrading`),拒绝属性修改 | 等待恢复 `Running` 后重试 | +| `DescribeClusters` 返回 `ResourceNotFound` | 检查 ClusterId 拼写和 region | ClusterId 不存在于当前账号/地域 | `tccli tke DescribeClusters --region ` 确认正确 ID | +| `UnauthorizedOperation` | `tccli configure list` 检查凭据 | CAM 权限不足(环境限制) | 联系主账号授予 `QcloudTKEFullAccess` | +| `ModifyClusterAttribute` 返回 `InvalidParameter.ClusterName` | 检查名称规则(1-60 字符,字母开头) | 名称不合法或重名 | 更换为合法唯一名称 | + +### 状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 状态长时间 `Creating`(>15 分钟) | `DescribeClusterStatus` 轮询 | 后端创建卡住 — VPC/子网不可用或配额不足 | 保留 region、ClusterId、RequestId → 登录 [TKE 控制台](https://console.cloud.tencent.com/tke2/cluster) | +| 状态 `Abnormal` | `DescribeClusterStatus` 确认后查 `DescribeClusters` 的 `StatusReason` | 底层资源故障、欠费等 | 检查 `StatusReason` → 登录控制台 → 无法解决则 [提交工单](https://console.cloud.tencent.com/workorder) | +| 状态 `Closed` / `Isolated` | `DescribeClusterStatus` 确认 | 后付费集群欠费隔离(环境限制) | 充值后自动恢复 `Running` | + +## 下一步 + +- [创建集群](../create) — 创建新托管集群 +- [删除集群](../delete) — 安全删除集群及关联资源 +- [升级集群](../upgrade) — 升级 K8s 版本 + +## 控制台替代 + +[TKE 控制台 → 集群列表](https://console.cloud.tencent.com/tke2/cluster) diff --git a/src/content/docs/cli/tke/cluster-config/cluster-management/management-modes-tkectl.md b/src/content/docs/cli/tke/cluster-config/cluster-management/management-modes-tkectl.md new file mode 100644 index 0000000..35b8158 --- /dev/null +++ b/src/content/docs/cli/tke/cluster-config/cluster-management/management-modes-tkectl.md @@ -0,0 +1,9 @@ +--- +title: "集群管理模式说明(产品 CLI)" +description: "集群管理模式说明(产品 CLI)" +--- + +> ⚠️ **P1 placeholder** — `tkectl` integration pending. See the [tccli operation page](.-tkectl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[集群管理模式说明](https://cloud.tencent.com/document/product/457/31013) +> P0 请使用同目录 [`tccli 操作.md`](.)。 diff --git a/src/content/docs/cli/tke/cluster-config/cluster-management/management-modes.md b/src/content/docs/cli/tke/cluster-config/cluster-management/management-modes.md new file mode 100644 index 0000000..b83ac84 --- /dev/null +++ b/src/content/docs/cli/tke/cluster-config/cluster-management/management-modes.md @@ -0,0 +1,219 @@ +--- +title: "集群管理模式说明" +description: "· page_id `31013`" +--- + +> 对照官方:[集群管理模式说明](https://cloud.tencent.com/document/product/457/31013) · page_id `31013` + +## 概述 + +TKE 提供两种集群管理模式,通过 `ClusterType` 字段区分。核心区别在于**控制面(Master + Etcd)由谁运维**。 + +| 维度 | 托管集群 | 独立集群 | +|------|---------|---------| +| `ClusterType` API 枚举 | `MANAGED_CLUSTER` | `INDEPENDENT_CLUSTER` | +| 控制台概念名 | 托管集群 | 独立集群 | +| 控制面归属 | 腾讯云运维,不在用户资源列表 | 用户自购 CVM 部署 | +| Master/Etcd 管理 | 自动运维,不可修改 | 用户自行管理、扩缩容 | +| 容器运行时 | 强制 `containerd` | 可选 `containerd` / `docker` | +| 费用构成 | 集群管理费 + 工作节点 CVM 等云资源 | Master CVM + 工作节点 CVM 等云资源 | +| Master 扩缩容 | 不开放 | `ScaleOutClusterMaster` / `ScaleInClusterMaster` | +| 新集群支持 | 活跃支持 | **已停止新建** | +| 适用场景 | 绝大多数生产/测试环境,免运维 | (历史)需完全控制控制面的特殊场景 | + +**建议:新集群一律选托管集群(`MANAGED_CLUSTER`)。** + +## 前置条件 + +- [环境准备](../../../../index.md) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置 + +# 3. 检查 CAM 权限 — 需要 tke:DescribeClusters +tccli tke DescribeClusters --region +# expected: exit 0 +``` + +```json +{ + "TotalCount": "", + "Clusters": "", + "ClusterId": "", + "ClusterName": "", + "ClusterDescription": "", + "ClusterVersion": "" +} +``` + +### 资源检查 + +```bash +# 4. 查询目标集群 +tccli tke DescribeClusters --region --ClusterIds '[""]' +# expected: TotalCount >= 1 +``` + +```json +{ + "TotalCount": "", + "Clusters": "", + "ClusterId": "", + "ClusterName": "", + "ClusterDescription": "", + "ClusterVersion": "" +} +``` + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 查看集群列表及类型 | `DescribeClusters` | 是 | +| 按类型过滤集群 | `DescribeClusters --Filters` | 是 | + +## 关键字段说明 + +| 字段 | 类型 | 说明 | 取值 | +|------|------|------|------| +| `ClusterType` | String | 管理模式 | `MANAGED_CLUSTER`(托管)/ `INDEPENDENT_CLUSTER`(独立) | +| `ClusterStatus` | String | 生命周期状态 | `Running` / `Creating` / `Upgrading` / `Deleting` / `Abnormal` | + +## 操作步骤 + +### 步骤 1:查询集群管理模式 + +```bash +tccli tke DescribeClusters --region \ + --ClusterIds '[""]' +# expected: exit 0,ClusterType 字段显示管理模式 +``` + +**预期输出**(托管集群): + +```json +{ + "TotalCount": 1, + "Clusters": [ + { + "ClusterId": "cls-example", + "ClusterName": "", + "ClusterType": "MANAGED_CLUSTER", + "ClusterStatus": "Running", + "ClusterVersion": "1.30.0", + "ClusterNetworkSettings": { + "ClusterCIDR": "10.200.0.0/16", + "ServiceCIDR": "10.201.0.0/20", + "MaxNodePodNum": 64, + "MaxClusterServiceNum": 4096 + }, + "ClusterMaterNodeNum": 1, + "ClusterNodeNum": 0, + "DeletionProtection": true + } + ], + "RequestId": "..." +} +``` + +| 字段 | 值 | 含义 | +|------|-----|------| +| `ClusterType` | `MANAGED_CLUSTER` | 托管集群 — 控制面由腾讯云运维 | +| `ClusterType` | `INDEPENDENT_CLUSTER` | 独立集群(仅存量)— Master/Etcd 部署在用户 CVM 上 | + +### 步骤 2:按类型过滤集群 + +```bash +# 仅列出托管集群 +tccli tke DescribeClusters --region \ + --Filters '[{"Name":"ClusterType","Values":["MANAGED_CLUSTER"]}]' +# expected: 返回 ClusterType 均为 MANAGED_CLUSTER 的集群列表 +``` + +```json +{ + "TotalCount": "", + "Clusters": "", + "ClusterId": "", + "ClusterName": "", + "ClusterDescription": "", + "ClusterVersion": "" +} +``` + +### 步骤 3:两种模式的 API 能力差异 + +| API | `MANAGED_CLUSTER` | `INDEPENDENT_CLUSTER` | +|-----|:--:|:--:| +| `CreateCluster` | 支持 | 已停止新建 | +| `DescribeClusters` | 支持 | 支持 | +| `DeleteCluster` | 支持 | 支持 | +| `ModifyClusterAttribute` | 支持 | 支持 | +| `ScaleOutClusterMaster` | 不开放 | 支持 | +| `ScaleInClusterMaster` | 不开放 | 支持 | + +## 验证 + +```bash +# 确认集群类型 +tccli tke DescribeClusters --region \ + --ClusterIds '[""]' \ + | jq '.Clusters[0].ClusterType' +# expected: "MANAGED_CLUSTER" +``` + +```json +{ + "TotalCount": "", + "Clusters": "", + "ClusterId": "", + "ClusterName": "", + "ClusterDescription": "", + "ClusterVersion": "" +} +``` + +| 验证维度 | 托管集群 | 独立集群 | +|---------|---------|---------| +| `ClusterType` | `MANAGED_CLUSTER` | `INDEPENDENT_CLUSTER` | +| Master 节点来源 | 不在用户 CVM 列表 | 可见 `cvm DescribeInstances` | +| 容器运行时 | 强制 `containerd` | 可选 `containerd` / `docker` | + +## 清理 + +本页为概念说明页,无资源需清理。 + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `DescribeClusters` 返回 `InvalidParameter.ClusterId` | 检查 `--ClusterIds` 格式 | ID 格式错误或不属于当前账号/地域 | `tccli tke DescribeClusters --region ` 列出全部集群确认 ID | +| `DescribeClusters` 返回 `UnauthorizedOperation` | `tccli configure list` 检查凭据 | 子账号缺少 `tke:DescribeClusters` 权限(环境限制) | 联系主账号授予 `QcloudTKEFullAccess` | +| 创建集群返回 `InvalidParameter.ClusterType` | 检查 `ClusterType` 字段值 | 填了控制台概念名(如 `GR`)而非 API 枚举 | 改为 `"MANAGED_CLUSTER"` | + +### 类型选择疑问 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 想新建独立集群但 API 报错 | `DescribeVersions` 查看 | 独立集群已停止新建入口 | 改用托管集群 `MANAGED_CLUSTER` | +| 托管集群删除全部节点后仍计费 | `DescribeClusters` 确认集群仍在运行 | 集群本身仍在运行并计费 | 执行 `DeleteCluster` 删除集群本身 | + +## 下一步 + +- [创建集群](../create) — 通过 CLI 创建托管集群 +- [集群生命周期](../lifecycle) — 集群状态流转与生命周期管理 +- [集群扩缩容](../scale) — 托管集群规格调整 + +## 控制台替代 + +[TKE 控制台 → 集群列表](https://console.cloud.tencent.com/tke2/cluster):集群卡片上「集群类型」列展示。 diff --git a/src/content/docs/cli/tke/cluster-config/cluster-management/scale-tkectl.md b/src/content/docs/cli/tke/cluster-config/cluster-management/scale-tkectl.md new file mode 100644 index 0000000..0538adc --- /dev/null +++ b/src/content/docs/cli/tke/cluster-config/cluster-management/scale-tkectl.md @@ -0,0 +1,9 @@ +--- +title: "集群扩缩容(产品 CLI)" +description: "集群扩缩容(产品 CLI)" +--- + +> ⚠️ **P1 placeholder** — `tkectl` integration pending. See the [tccli operation page](.-tkectl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[集群扩缩容](https://cloud.tencent.com/document/product/457/32190) +> P0 请使用同目录 [`tccli 操作.md`](.)。 diff --git a/src/content/docs/cli/tke/cluster-config/cluster-management/scale.md b/src/content/docs/cli/tke/cluster-config/cluster-management/scale.md new file mode 100644 index 0000000..82a430b --- /dev/null +++ b/src/content/docs/cli/tke/cluster-config/cluster-management/scale.md @@ -0,0 +1,737 @@ +--- +title: "集群扩缩容" +description: "· page_id `32190` · tccli ≥3.1.107 · API 2018-05-25" +--- + +> 对照官方:[集群扩缩容](https://cloud.tencent.com/document/product/457/32190) · page_id `32190` · tccli ≥3.1.107 · API 2018-05-25 + +## 概述 + +TKE 集群扩缩容的方式取决于集群类型。**托管集群**(`MANAGED_CLUSTER`)的 Master 由腾讯云自动维护和扩缩容,用户通过**节点池**(NodePool)管理 Worker 节点的扩缩容。**独立集群**(`INDEPENDENT_CLUSTER`)支持 Master 节点的手动扩缩容。 + +| 集群类型 | 扩缩容方式 | 对应 API | +|---------|-----------|---------| +| 托管集群(`MANAGED_CLUSTER`) | 节点池扩缩容:调整期望节点数、弹性伸缩范围 | `CreateClusterNodePool`、`ModifyNodePoolDesiredCapacityAboutAsg`、`ModifyClusterNodePool` | +| 独立集群(`INDEPENDENT_CLUSTER`) | Master 节点扩缩容 + 节点池扩缩容 | `ScaleOutClusterMaster`、`ScaleInClusterMaster` | + +节点池扩缩容有两种方式: +1. **手动调整期望节点数**:`ModifyNodePoolDesiredCapacityAboutAsg`,直接设置关联 AS 组的期望实例数,立即触发扩缩容 +2. **调整弹性伸缩范围**:`ModifyClusterNodePool`,修改最大/最小节点数及是否开启自动伸缩,集群根据资源使用率自动扩缩容 + +## 前置条件 + +- [环境准备](../../../../index.md) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 +``` + +**预期输出**: + +``` +tccli version 3.1.107.1 +``` + +```bash +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId、secretKey、region 均已配置,region 为目标地域 +``` + +**预期输出**: + +``` +secretId = AKID******************************** +secretKey = ******************************** +region = ap-guangzhou +output = json +``` + +```bash +# 3. 检查 CAM 权限 — 需要以下 Action 名 +# tke:DescribeClusters, tke:DescribeClusterNodePools +# tke:CreateClusterNodePool, tke:ModifyNodePoolDesiredCapacityAboutAsg +# tke:ModifyClusterNodePool, tke:DeleteClusterNodePool +# tke:ScaleOutClusterMaster, tke:ScaleInClusterMaster(独立集群) +# 验证:执行 DescribeClusters 确认权限 +tccli tke DescribeClusters --region +# expected: exit 0,返回集群列表(可为空) +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Clusters": [ + { + "ClusterId": "cls-example", + "ClusterName": "example-cluster", + "ClusterType": "MANAGED_CLUSTER", + "ClusterStatus": "Running", + "ClusterVersion": "1.30.0" + } + ] +} +``` + +```bash +# 验证节点池权限 +tccli tke DescribeClusterNodePools --ClusterId --region +# expected: exit 0,返回节点池列表(可为空) +``` + +**预期输出**: + +```json +{ + "TotalCount": 0, + "NodePoolSet": [] +} +``` + +### 资源检查 + +```bash +# 4. 确认集群存在并查看集群类型 +tccli tke DescribeClusters --region --ClusterIds '[""]' +# expected: 返回目标集群信息,确认 ClusterType 字段 +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Clusters": [ + { + "ClusterId": "cls-example", + "ClusterName": "example-cluster", + "ClusterType": "MANAGED_CLUSTER", + "ClusterStatus": "Running", + "ClusterVersion": "1.30.0" + } + ] +} +``` + +集群存在且状态为 `Running`。确认 `ClusterType`: +- `MANAGED_CLUSTER` → 使用节点池扩缩容(参考本页操作步骤) +- `INDEPENDENT_CLUSTER` → 可使用 `ScaleOutClusterMaster` / `ScaleInClusterMaster` + +## 控制台与 CLI 参数映射 + +以下说明 `CreateClusterNodePool` 的主要参数。完整参数定义见 `tccli tke CreateClusterNodePool --help --detail`。 + +| 字段 | 类型 | 必填 | 幂等 | 取值与约束 | 错误后果 | +|------|------|:--:|:--:|------|------| +| `ClusterId` | String | 是 | 否 | 目标集群 ID,格式 `cls-xxxxxxxx`。目标集群必须为托管集群 | 集群不存在或类型不匹配 → `InvalidParameter.ClusterId` | +| `Name` | String | 否 | 否 | 节点池名称,自定义。不填由系统自动生成 | — | +| `AutoScalingGroupPara` | String | 是 | 否 | AS 创建弹性伸缩组的参数字符串(JSON)。**不可设置 `AutoScalingGroupName`**(系统自动生成)。必须含 `MaxSize`、`MinSize`、`DesiredCapacity`、`VpcId`、`SubnetIds` | 设置了 `AutoScalingGroupName` → `InvalidParameter.Param: autoscaling group name can not be set` | +| `LaunchConfigurePara` | String | 是 | 否 | AS 创建启动配置的参数字符串(JSON)。**不可设置 `ImageId` 和 `LaunchConfigurationName`**(由 `NodePoolOs` 决定)。必须含 `SecurityGroupIds`、`InstanceType`、`SystemDisk`、`LoginSettings` | 设置了 `ImageId` → `InvalidParameter.Param: image id can not be set`;缺失 `SecurityGroupIds` → `InvalidParameter.Param: security group ids is not set` | +| `EnableAutoscale` | Boolean | 否 | 否 | 是否开启自动伸缩。`true` 开启,`false` 关闭。默认 `false` | 忘了开启 → 集群不会根据负载自动扩缩容 | +| `ContainerRuntime` | String | 否 | 否 | 容器运行时。托管集群固定为 `containerd` | 填 `docker` → 托管集群不支持 docker 运行时 | +| `RuntimeVersion` | String | 否 | 否 | 容器运行时版本。**建议不传**,由 API 根据 K8s 版本自动选择匹配版本 | 版本与 K8s 不匹配 → `FailedOperation.PolicyServerRuntimeNotMatchK8sVersionError` | +| `NodePoolOs` | String | 否 | 否 | 节点操作系统。如 `tlinux3.1x86_64`、`ubuntu20.04x86_64` | — | +| `DeletionProtection` | Boolean | 否 | 是 | 节点池删除保护。测试环境建议 `false`,生产环境建议 `true` | 忘开删除保护 → 可能误删生产节点池 | +| `InstanceAdvancedSettings` | Object | 是 | 否 | 节点高级设置。`DesiredPodNumber` 节点上期望的 Pod 数量,`Unschedulable` 是否设置为不可调度 | 参数格式不正确 → `InvalidParameter.Param` | + +### 其他操作的 CLI 映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 查看集群列表/类型 | `DescribeClusters` | 是 | +| 查看节点池列表 | `DescribeClusterNodePools` | 是 | +| 调整期望节点数 | `ModifyNodePoolDesiredCapacityAboutAsg` | 否 | +| 调整弹性伸缩范围 | `ModifyClusterNodePool` | 否 | +| 删除节点池 | `DeleteClusterNodePool` | 是 | +| 独立集群 Master 扩容 | `ScaleOutClusterMaster` | 否 | +| 独立集群 Master 缩容 | `ScaleInClusterMaster` | 否 | + +## 操作步骤 + +### 步骤 1:确认集群类型 + +#### 选择依据 + +- **集群类型**:通过 `DescribeClusters` 确认当前集群类型。托管集群(`MANAGED_CLUSTER`)的 Master 由腾讯云自动维护、扩缩容、升级,用户无需也禁止操作 Master 节点。扩缩容通过节点池完成。独立集群(`INDEPENDENT_CLUSTER`)方可使用 `ScaleOutClusterMaster` / `ScaleInClusterMaster` 操作 Master 节点。 +- **常见错误**:误认为所有集群都支持 `ScaleOutClusterMaster`。该 API 仅适用于独立集群,托管集群使用时会报 `InvalidParameter: only independent cluster allowed to scale master or etcd`。 + +```bash +tccli tke DescribeClusters --region --ClusterIds '[""]' +# expected: exit 0,返回 ClusterType 字段 +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Clusters": [ + { + "ClusterId": "cls-example", + "ClusterName": "example-cluster", + "ClusterType": "MANAGED_CLUSTER", + "ClusterStatus": "Running", + "ClusterVersion": "1.30.0" + } + ] +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | 集群 ID | 格式 `cls-xxxxxxxx` | `tccli tke DescribeClusters` | +| `` | 地域 | 如 `ap-guangzhou` | `tccli tke DescribeRegions` | + +- 如 `ClusterType` 为 `MANAGED_CLUSTER` → 继续本节后续步骤,通过节点池扩缩容 +- 如 `ClusterType` 为 `INDEPENDENT_CLUSTER` → 跳至 [补充:独立集群 Master 扩缩容](#补充独立集群-master-扩缩容) + +### 步骤 2:创建节点池(托管集群扩缩容基础) + +#### 选择依据 + +- **节点池**:托管集群的扩缩容通过节点池(NodePool)管理,而非 Master 扩缩容。节点池关联一个弹性伸缩(AS)组,通过修改期望实例数来控制节点数量。 +- **参数简化策略**:`CreateClusterNodePool` 参数众多,关键原则: + - `AutoScalingGroupPara` 中不设 `AutoScalingGroupName`(系统自动生成) + - `LaunchConfigurePara` 中不设 `ImageId` / `LaunchConfigurationName`(由 `NodePoolOs` 决定镜像) + - 必须提供 `SecurityGroupIds` + - 不传 `RuntimeVersion`,让 API 自动匹配 K8s 版本 +- **查询现有节点池**:先确认当前节点池状态,避免重复创建。 + +```bash +tccli tke DescribeClusterNodePools --ClusterId --region +# expected: exit 0,返回现有节点池列表 +``` + +**预期输出**: + +```json +{ + "TotalCount": 0, + "NodePoolSet": [] +} +``` + +#### 最小创建(只含必填字段) + +`create-nodepool-minimal.json`: + +```json +{ + "ClusterId": "", + "EnableAutoscale": true, + "AutoScalingGroupPara": "{\"MaxSize\":3,\"MinSize\":0,\"DesiredCapacity\":1,\"VpcId\":\"\",\"SubnetIds\":[\"\"],\"MultiZoneSubnetPolicy\":\"PRIORITY\",\"RetryPolicy\":\"IMMEDIATE_RETRY\",\"ServiceSettings\":{\"ScalingMode\":\"CLASSIC_SCALING\"}}", + "LaunchConfigurePara": "{\"InstanceType\":\"S5.MEDIUM2\",\"SystemDisk\":{\"DiskType\":\"CLOUD_PREMIUM\",\"DiskSize\":50},\"InternetAccessible\":{\"InternetChargeType\":\"TRAFFIC_POSTPAID_BY_HOUR\",\"InternetMaxBandwidthOut\":0,\"PublicIpAssigned\":false},\"LoginSettings\":{\"Password\":\"\"},\"EnhancedService\":{\"SecurityService\":{\"Enabled\":true},\"MonitorService\":{\"Enabled\":true}},\"InstanceChargeType\":\"POSTPAID_BY_HOUR\",\"SecurityGroupIds\":[\"\"]}", + "InstanceAdvancedSettings": { + "DesiredPodNumber": 0, + "Unschedulable": 0 + } +} +``` + +```bash +tccli tke CreateClusterNodePool --cli-input-json file://create-nodepool-minimal.json --region +# expected: exit 0,返回 NodePoolId +``` + +**预期输出**: + +```json +{ + "NodePoolId": "np-example", + "RequestId": "dabf5405-abba-4119-b380-53818a9fd50f" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | 集群 ID | 必须为托管集群 | `DescribeClusters` | +| `` | VPC ID | 集群所在 VPC | `DescribeClusters` → `ClusterNetworkSettings.VpcId` | +| `` | 子网 ID | VPC 下的可用子网 | `tccli vpc DescribeSubnets` | +| `` | 安全组 ID | 必须提供 | `tccli vpc DescribeSecurityGroups` | +| `` | 节点登录密码 | 自定义 | — | +| `` | 地域 | 如 `ap-guangzhou` | `tccli tke DescribeRegions` | + +#### 增强配置(加操作系统、容器运行时、删除保护) + +`create-nodepool-enhanced.json`: + +```json +{ + "ClusterId": "", + "Name": "scale-np", + "EnableAutoscale": true, + "ContainerRuntime": "containerd", + "NodePoolOs": "tlinux3.1x86_64", + "DeletionProtection": false, + "AutoScalingGroupPara": "{\"MaxSize\":3,\"MinSize\":0,\"DesiredCapacity\":1,\"VpcId\":\"\",\"SubnetIds\":[\"\"],\"MultiZoneSubnetPolicy\":\"PRIORITY\",\"RetryPolicy\":\"IMMEDIATE_RETRY\",\"ServiceSettings\":{\"ScalingMode\":\"CLASSIC_SCALING\"}}", + "LaunchConfigurePara": "{\"InstanceType\":\"S5.MEDIUM2\",\"SystemDisk\":{\"DiskType\":\"CLOUD_PREMIUM\",\"DiskSize\":50},\"InternetAccessible\":{\"InternetChargeType\":\"TRAFFIC_POSTPAID_BY_HOUR\",\"InternetMaxBandwidthOut\":0,\"PublicIpAssigned\":false},\"LoginSettings\":{\"Password\":\"\"},\"EnhancedService\":{\"SecurityService\":{\"Enabled\":true},\"MonitorService\":{\"Enabled\":true}},\"InstanceChargeType\":\"POSTPAID_BY_HOUR\",\"SecurityGroupIds\":[\"\"]}", + "InstanceAdvancedSettings": { + "DesiredPodNumber": 0, + "Unschedulable": 0 + } +} +``` + +节点池创建后,等待其就绪: + +```bash +tccli tke DescribeClusterNodePools --ClusterId --region +# expected: NodePoolSet 含新节点池,LifeState 为 "normal" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "NodePoolSet": [ + { + "NodePoolId": "np-example", + "Name": "scale-np", + "LifeState": "normal", + "DesiredNodesNum": 1, + "MaxNodesNum": 3, + "MinNodesNum": 0 + } + ] +} +``` + +### 步骤 3:扩容节点池(调整期望节点数) + +#### 选择依据 + +- **扩容方式**:`ModifyNodePoolDesiredCapacityAboutAsg` 直接调整节点池关联 AS 组的期望实例数,立即触发节点创建。适合手动扩容场景。 +- **期望节点数**:必须在当前 `MinNodesNum` 和 `MaxNodesNum` 范围内。修改 `DesiredCapacity` 后,AS 会自动创建/销毁 CVM 实例以达到期望数量。 + +#### 扩容命令(1 → 2) + +```bash +tccli tke ModifyNodePoolDesiredCapacityAboutAsg \ + --ClusterId \ + --NodePoolId \ + --DesiredCapacity 2 \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RequestId": "5ebe51d8-3f57-44c3-86bd-c4290f917805" +} +``` + +| 占位符 | 说明 | 约束 | 获取方式 | +|--------|------|------|---------| +| `` | 集群 ID | — | `DescribeClusters` | +| `` | 节点池 ID | — | `DescribeClusterNodePools` | +| `` | 地域 | — | `tccli tke DescribeRegions` | + +验证扩容生效: + +```bash +tccli tke DescribeClusterNodePools --ClusterId --region +# expected: DesiredNodesNum 为 2,LifeState 为 "normal" +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "NodePoolSet": [ + { + "NodePoolId": "np-example", + "Name": "scale-np", + "LifeState": "normal", + "DesiredNodesNum": 2, + "MaxNodesNum": 3, + "MinNodesNum": 0 + } + ] +} +``` + +### 步骤 4:调整弹性伸缩范围 + +#### 选择依据 + +- **弹性伸缩范围**:`ModifyClusterNodePool` 调整 `MaxNodesNum` 和 `MinNodesNum` 控制弹性伸缩的上限和下限。配合 `EnableAutoscale` 开启后,集群会基于资源使用率自动在范围内扩缩容。 +- **与期望节点数的关系**:期望节点数必须在 [MinNodesNum, MaxNodesNum] 区间内。调整范围后,已运行的节点不受影响,但后续弹性伸缩行为受新范围约束。 + +```bash +tccli tke ModifyClusterNodePool \ + --ClusterId \ + --NodePoolId \ + --MaxNodesNum 5 \ + --MinNodesNum 1 \ + --EnableAutoscale true \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RequestId": "ad2d0204-fc6c-498f-a6df-7acd153f222c" +} +``` + +验证范围修改生效: + +```bash +tccli tke DescribeClusterNodePools --ClusterId --region +# expected: MaxNodesNum 为 5,MinNodesNum 为 1 +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "NodePoolSet": [ + { + "NodePoolId": "np-example", + "Name": "scale-np", + "LifeState": "normal", + "DesiredNodesNum": 2, + "MaxNodesNum": 5, + "MinNodesNum": 1 + } + ] +} +``` + +### 步骤 5:缩容节点池(调低期望节点数) + +```bash +tccli tke ModifyNodePoolDesiredCapacityAboutAsg \ + --ClusterId \ + --NodePoolId \ + --DesiredCapacity 1 \ + --region +# expected: exit 0 +``` + +**预期输出**: + +```json +{ + "RequestId": "297b1b96-eb39-4f85-984b-9a2330abb0bc" +} +``` + +验证缩容生效: + +```bash +tccli tke DescribeClusterNodePools --ClusterId --region +# expected: DesiredNodesNum 为 1 +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "NodePoolSet": [ + { + "NodePoolId": "np-example", + "Name": "scale-np", + "LifeState": "normal", + "DesiredNodesNum": 1, + "MaxNodesNum": 5, + "MinNodesNum": 1 + } + ] +} +``` + +### 补充:独立集群 Master 扩缩容 + +> **适用条件**:仅 `ClusterType: "INDEPENDENT_CLUSTER"` 时有效。托管集群使用会返回 `InvalidParameter: only independent cluster allowed to scale master or etcd`。 + +#### Master 扩容 + +`scaleout-master.json`: + +```json +{ + "ClusterId": "", + "RunInstancesForNode": [ + { + "NodeRole": "MASTER_ETCD", + "RunInstancesPara": [""] + } + ] +} +``` + +```bash +tccli tke ScaleOutClusterMaster --cli-input-json file://scaleout-master.json --region +# expected: exit 0(仅独立集群) +``` + +#### Master 缩容 + +> **注意**:`ScaleInClusterMaster` 为内测功能,需联系腾讯云开通白名单后方可使用。 + +> **⚠️ 副作用警告**:`InstanceDeleteMode: "terminate"` 会**彻底销毁**指定 Master 实例及其关联磁盘,数据不可恢复。执行前务必确认: +> - 集群中至少保留一个健康 Master 节点(缩容后 Master 数量不得低于 etcd 法定人数) +> - `InstanceId` 准确无误,避免误删 +> - 建议先在测试集群验证 + +```bash +tccli tke ScaleInClusterMaster \ + --ClusterId \ + --ScaleInMasters '[{"InstanceId":"","NodeRole":"MASTER_ETCD","InstanceDeleteMode":"terminate"}]' \ + --region +# expected: exit 0(仅独立集群 + 白名单) +``` + +## 验证 + +### 状态维度 + +节点池创建或调整后,从以下维度验证: + +| 维度 | 命令 | 预期 | +|------|------|------| +| 节点池状态 | `tccli tke DescribeClusterNodePools --ClusterId --region ` | `LifeState: "normal"` | +| 期望节点数 | 同上,检查 `DesiredNodesNum` | 与设置的 `DesiredCapacity` 一致 | +| 弹性伸缩范围 | 同上,检查 `MaxNodesNum`、`MinNodesNum` | 与修改后的值一致 | +| 自动伸缩开关 | 同上,检查 `EnableAutoscale` | 与设置值一致 | +| 实际节点数 | `tccli tke DescribeClusterInstances --ClusterId --region ` | 节点实例数 ≥ `DesiredNodesNum` | + +> 节点创建为异步操作,从期望节点数修改到实际节点 Running 需 3-5 分钟。超时参见 [排障](#排障)。 + +### 节点实例状态验证 + +```bash +tccli tke DescribeClusterInstances --ClusterId --region +# expected: 返回节点实例列表,InstanceState 为 "running" +``` + +**预期输出**: + +```json +{ + "TotalCount": 2, + "InstanceSet": [ + { + "InstanceId": "ins-example01", + "InstanceRole": "WORKER", + "InstanceState": "running", + "InstanceType": "S5.MEDIUM2", + "LanIP": "10.0.0.1", + "NodePoolId": "np-example" + }, + { + "InstanceId": "ins-example02", + "InstanceRole": "WORKER", + "InstanceState": "running", + "InstanceType": "S5.MEDIUM2", + "LanIP": "10.0.0.2", + "NodePoolId": "np-example" + } + ] +} +``` + +### 集群访问验证(kubectl) + +> **kubectl 连通性要求**:以下 kubectl 命令需在集群端点可达的环境下执行(公网端点或 IOA/VPN/专线)。 + +```bash +# 获取 kubeconfig(公网端点) +tccli tke DescribeClusterKubeconfig --ClusterId --IsExtranet true --region +# expected: 返回 base64 编码的 kubeconfig +``` + +将输出中的 `Kubeconfig` 字段内容保存为 `~/.kube/config`(或通过 `KUBECONFIG` 环境变量指定),然后验证集群连通性: + +```bash +# 验证集群连通性 +kubectl cluster-info +# expected: Kubernetes control plane is running at https://... +``` + +**预期输出**: + +``` +Kubernetes control plane is running at https:// +CoreDNS is running at https:///api/v1/namespaces/kube-system/services/kube-dns:dns/proxy +``` + +```bash +# 验证新节点已注册并可调度 Pod +kubectl get nodes +# expected: 新节点状态为 Ready +``` + +**预期输出**: + +``` +NAME STATUS ROLES AGE VERSION +10.0.0.1 Ready 5m v1.32.2 +10.0.0.2 Ready 1m v1.32.2 +``` + +```bash +# 部署测试 Pod 验证可调度性 +kubectl run test-schedule --image=busybox --restart=Never -- sleep 30 +# expected: pod/test-schedule created +``` + +**预期输出**: + +``` +pod/test-schedule created +``` + +```bash +kubectl get pod test-schedule -o wide +# expected: Pod 状态 Running,NODE 列为新扩容节点 +``` + +**预期输出**: + +``` +NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES +test-schedule 1/1 Running 0 5s 10.0.0.4 10.0.0.2 +``` + +```bash +kubectl delete pod test-schedule +# expected: pod "test-schedule" deleted +``` + +## 清理 + +> **⚠️ 警告**:`DeleteClusterNodePool` 使用 `--KeepInstance false` 会**级联删除**节点池中所有 CVM 实例和关联磁盘。 +> 生产环境执行前务必确认节点池名称和 ID,建议先 `DescribeClusterNodePools` 检查。 +> 删除节点池前确认无关键 Pod 运行在该节点池上(`kubectl get pods -o wide`)。 + +### 1. 清理前状态检查 + +```bash +tccli tke DescribeClusterNodePools --ClusterId --region +# 确认是待删除的目标节点池,记录 NodePoolId、Name、DesiredNodesNum +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "NodePoolSet": [ + { + "NodePoolId": "np-example", + "Name": "scale-np", + "LifeState": "normal", + "DesiredNodesNum": 1, + "MaxNodesNum": 5, + "MinNodesNum": 1, + "EnableAutoscale": true + } + ] +} +``` + +### 2. 关闭节点池自动伸缩 + +> **⚠️ 重要**:删除节点池前,如已开启自动伸缩(`EnableAutoscale: true`),必须先关闭。否则自动伸缩可能在中途触发节点创建,导致删除失败或资源残留。 + +```bash +tccli tke ModifyClusterNodePool --ClusterId \ + --NodePoolId \ + --EnableAutoscale false \ + --region +``` + +**预期输出**: + +```json +{ + "RequestId": "84c2d256-33e3-44f7-81f6-adc5f949f1b9" +} +``` + +### 3. 关闭节点池删除保护(如启用) + +```bash +tccli tke ModifyClusterNodePool --ClusterId \ + --NodePoolId \ + --DeletionProtection false \ + --region +``` + +### 4. 删除节点池 + +```bash +tccli tke DeleteClusterNodePool \ + --ClusterId \ + --NodePoolIds '[""]' \ + --KeepInstance false \ + --region +# ⚠️ --KeepInstance false 会删除节点池中所有 CVM 实例和磁盘 +``` + +**预期输出**: + +```json +{ + "RequestId": "312ae00e-1a62-40b4-89cf-a029b3cecd6e" +} +``` + +### 5. 验证已删除 + +```bash +tccli tke DescribeClusterNodePools --ClusterId --region +# expected: 返回节点池 LifeState 为 "deleting",后续为空列表 +``` + +> 节点池删除为异步操作,`LifeState` 变为 `deleting` 后仍需等待实际资源销毁。最终 `DescribeClusterNodePools` 返回的 `NodePoolSet` 中不含该节点池即为删除完成。 + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `ScaleOutClusterMaster` 返回 `InvalidParameter`,消息 "only independent cluster allowed to scale master or etcd",RequestId `301b8090-...` | `tccli tke DescribeClusters --ClusterIds '[""]' --region ` 查看 `ClusterType` | 当前集群为托管集群(`MANAGED_CLUSTER`),`ScaleOutClusterMaster` 仅支持独立集群(`INDEPENDENT_CLUSTER`) | 托管集群使用节点池扩缩容:`CreateClusterNodePool` → `ModifyNodePoolDesiredCapacityAboutAsg`。如需 Master 扩缩容,先创建独立集群 | +| `ScaleInClusterMaster` 返回 `InvalidParameter`,消息 "only independent cluster allowed to scale master or etcd",RequestId `cca096fa-...` | 同上 | 同 `ScaleOutClusterMaster`。且 `ScaleInClusterMaster` 为内测功能,需白名单 | 托管集群无需手动缩容 Master(腾讯云自动管理)。独立集群需先开通白名单 | +| `CreateClusterNodePool` 返回 `InvalidParameter.Param`,消息 "autoscaling group name can not be set" | 检查 `AutoScalingGroupPara` JSON | `AutoScalingGroupPara` 中设置了 `AutoScalingGroupName` 字段——该字段由系统自动生成,不可手动设置 | 从 `AutoScalingGroupPara` JSON 字符串中移除 `AutoScalingGroupName` 字段 | +| `CreateClusterNodePool` 返回 `InvalidParameter.Param`,消息 "image id can not be set" | 检查 `LaunchConfigurePara` JSON | `LaunchConfigurePara` 中设置了 `ImageId` 字段——操作系统镜像由 `NodePoolOs` 参数决定,不可在启动配置中指定 | 从 `LaunchConfigurePara` JSON 字符串中移除 `ImageId` 和 `LaunchConfigurationName` 字段 | +| `CreateClusterNodePool` 返回 `InvalidParameter.Param`,消息 "security group ids is not set" | 检查 `LaunchConfigurePara` JSON | `LaunchConfigurePara` 中缺少 `SecurityGroupIds` 字段——安全组为必填项 | 在 `LaunchConfigurePara` JSON 字符串中添加 `"SecurityGroupIds":[""]`。可通过 `tccli vpc DescribeSecurityGroups --region ` 查询可用安全组 | +| `CreateClusterNodePool` 返回 `FailedOperation.PolicyServerRuntimeNotMatchK8sVersionError`,消息 "k8s version and runtime (type/version) not match" | 检查 `RuntimeVersion` 参数 | 指定的 `RuntimeVersion` 与集群 K8s 版本不兼容 | **删除 `RuntimeVersion` 参数**,让 API 自动选择与 K8s 版本匹配的容器运行时版本 | + +### 创建已提交但状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 返回 `NodePoolId` 但 `LifeState` 长时间非 `normal` | `tccli tke DescribeClusterNodePools --ClusterId --region ` 查看 `LifeState` | 节点池创建为异步操作,通常 3-5 分钟就绪。超时可能是 VPC/子网/安全组不可用 | 保留 `ClusterId`、`NodePoolId`、`RequestId`、创建 JSON → 登录 [TKE 控制台](https://console.cloud.tencent.com/tke2/cluster) 查看详细状态 → 仍无法解决则 [提交工单](https://console.cloud.tencent.com/workorder) | +| 扩容后实际节点数未达到期望节点数 | `tccli tke DescribeClusterInstances --ClusterId --region ` + `tccli tke DescribeClusterNodePools --ClusterId --region ` 对比 `DesiredNodesNum` 与实际节点数 | AS 创建 CVM 实例耗时、CVM 配额不足、可用区资源售罄(此为环境限制,非命令错误) | 等待 3-5 分钟;检查 CVM 配额:`tccli cvm DescribeAccountQuota --region `;尝试更换可用区 | +| 缩容后节点池 LifeState 为 `updating` 持续 | `DescribeClusterNodePools` 查看 `LifeState` | 节点正在被驱逐和销毁,期间 LifeState 为 `updating`,完成后恢复 `normal` | 等待更新完成(通常 1-3 分钟)。若超过 10 分钟仍为 `updating`,保留 `ClusterId`、`NodePoolId`、`RequestId` 并提交工单 | +| 删除节点池后仍在列表中 | `tccli tke DescribeClusterNodePools --ClusterId --region ` | 删除为异步操作,`LifeState: "deleting"` 后节点和 CVM 实例在后台逐步销毁 | 等待删除完成。若超过 10 分钟仍为 `deleting`,登录控制台查看状态或提交工单 | + +## 下一步 + +- [创建集群](https://cloud.tencent.com/document/product/457/32191) — 如需创建新集群进行扩缩容操作 +- [节点池管理](https://cloud.tencent.com/document/product/457/43719) — 节点池的完整生命周期管理 +- [集群升级](https://cloud.tencent.com/document/product/457/32192) — 集群 K8s 版本升级 +- [弹性伸缩](https://cloud.tencent.com/document/product/457/37383) — 基于 HPA/CA 的自动扩缩容策略 +- [环境准备](../../../../index.md) — tccli 安装和凭据配置 + +## 控制台替代 + +[通过控制台进行集群扩缩容](https://console.cloud.tencent.com/tke2/cluster) diff --git a/src/content/docs/cli/tke/cluster-config/cluster-management/upgrade-tkectl.md b/src/content/docs/cli/tke/cluster-config/cluster-management/upgrade-tkectl.md new file mode 100644 index 0000000..5bdf196 --- /dev/null +++ b/src/content/docs/cli/tke/cluster-config/cluster-management/upgrade-tkectl.md @@ -0,0 +1,9 @@ +--- +title: "升级集群(产品 CLI)" +description: "升级集群(产品 CLI)" +--- + +> ⚠️ **P1 placeholder** — `tkectl` integration pending. See the [tccli operation page](.-tkectl.md) for the working P0 version. + +> **P1 stub** · 对照官方:[升级集群](https://cloud.tencent.com/document/product/457/32192) +> P0 请使用同目录 [`tccli 操作.md`](.)。 diff --git a/src/content/docs/cli/tke/cluster-config/cluster-management/upgrade.md b/src/content/docs/cli/tke/cluster-config/cluster-management/upgrade.md new file mode 100644 index 0000000..d3383a7 --- /dev/null +++ b/src/content/docs/cli/tke/cluster-config/cluster-management/upgrade.md @@ -0,0 +1,349 @@ +--- +title: "升级集群" +description: "· page_id `32192`" +--- + +> 对照官方:[升级集群](https://cloud.tencent.com/document/product/457/32192) · page_id `32192` + +## 概述 + +通过 `UpdateClusterVersion` 升级 TKE 集群的 Kubernetes 控制面版本,仅升级 Master 控制面,不升级工作节点(节点升级需单独执行 `UpgradeClusterInstances`)。 + +升级策略:先升级控制面验证稳定性,再逐步升级节点。版本须逐级递进,不可跨版本跳跃(如 1.30 直接跳到 1.34)。升级不可逆,不可回退。`DescribeAvailableClusterVersion` 返回当前集群可到达的升级路径。 + +## 前置条件 + +- [环境准备](../../../../index.md) + +### 环境检查 + +```bash +# 1. 检查 tccli 版本 +tccli --version +# expected: tccli version >= 1.0.0 + +# 2. 检查当前凭据和地域 +tccli configure list +# expected: secretId, secretKey, region 均已配置 + +# 3. 检查 CAM 权限 +# 需要: tke:UpdateClusterVersion, tke:DescribeAvailableClusterVersion, +# tke:DescribeClusters, tke:DescribeVersions +tccli tke DescribeClusters --region +# expected: exit 0 + +tccli tke DescribeAvailableClusterVersion --region --ClusterId +# expected: exit 0 +``` + +```json +{ + "TotalCount": "", + "Clusters": "", + "ClusterId": "", + "ClusterName": "", + "ClusterDescription": "", + "ClusterVersion": "" +} +``` + +### 资源检查 + +```bash +# 4. 确认集群状态和当前版本 +tccli tke DescribeClusters --region --ClusterIds '[""]' +# expected: ClusterStatus: "Running" + +# 5. 查询该集群可升级的目标版本 +tccli tke DescribeAvailableClusterVersion --region --ClusterId +# expected: Versions 非空 + +# 6. 查询全地域可用版本 +tccli tke DescribeVersions --region +# expected: exit 0 + +# 7. 检查集群状态详情 +tccli tke DescribeClusterStatus --region --ClusterIds '[""]' +# expected: ClusterState: "Running" +``` + +```json +{ + "TotalCount": "", + "Clusters": "", + "ClusterId": "", + "ClusterName": "", + "ClusterDescription": "", + "ClusterVersion": "" +} +``` + +### 升级前检查清单 + +| 检查项 | 通过标准 | +|--------|---------| +| 集群状态 | `ClusterStatus: "Running"` | +| 目标版本可用 | 在 `DescribeAvailableClusterVersion` 返回列表中 | +| 删除保护状态 | `ClusterDeletionProtection: false`(`true` 需先关闭) | +| 时间窗口 | 业务低峰期,预留 1-2 小时 | + +## 控制台与 CLI 参数映射 + +| 控制台操作 | tccli 命令 | 幂等 | +|-----------|-----------|:--:| +| 查看可升级版本 | `DescribeAvailableClusterVersion` | 是 | +| 查询所有可用版本 | `DescribeVersions` | 是 | +| 升级集群 | `UpdateClusterVersion` | 否 | +| 查询集群状态 | `DescribeClusterStatus` | 是 | +| 查看集群详情 | `DescribeClusters` | 是 | + +## 关键字段说明 + +| 字段 | 类型 | 必填 | 取值与约束 | 错误后果 | +|------|------|:--:|------|------| +| `ClusterId` | String | 是 | 格式 `cls-xxxxxxxx`,集群必须 `Running` | 不存在 → `InvalidParameter.ClusterId`;非 Running → `FailedOperation.ClusterState` | +| `DstVersion` | String | 是 | 目标 K8s 版本,须在 `DescribeAvailableClusterVersion` 返回列表中 | 版本不在可用列表 → `InvalidParameter` | +| `ExtraArgs` | ClusterExtraArgs | 否 | 自定义参数,子字段为 `Array of String`,格式 `["k1=v1","k2=v2"]` | 格式错误 → `InvalidParameter.ExtraArgs` | +| `SkipPreCheck` | Boolean | 否 | 默认 `false` | 跳过检查可能导致升级失败后难以定位 | +| `MaxNotReadyPercent` | Float | 否 | 可容忍的最大不可用 Pod 比例 | 设置过高可能导致服务不可用未被检测 | + +## 操作步骤 + +### 步骤 1:查询当前版本和可升级版本 + +```bash +# 当前版本 +tccli tke DescribeClusters --region --ClusterIds '[""]' +# expected: ClusterVersion, ClusterStatus +``` + +**预期输出**: + +```json +{ + "TotalCount": 1, + "Clusters": [ + { + "ClusterId": "cls-example", + "ClusterName": "", + "ClusterVersion": "1.30.0", + "ClusterStatus": "Running", + "ClusterType": "MANAGED_CLUSTER" + } + ], + "RequestId": "..." +} +``` + +```bash +# 可升级版本 +tccli tke DescribeAvailableClusterVersion --region --ClusterId +# expected: Versions 列表 +``` + +**预期输出**(1.30.0 可升级到 1.32.2): + +```json +{ + "Versions": ["1.32.2"], + "Clusters": [ + { + "ClusterId": "cls-example", + "Versions": ["1.32.2"] + } + ], + "RequestId": "..." +} +``` + +> `DescribeAvailableClusterVersion` 只返回当前集群允许的升级路径。不能跨版本升级。 + +```bash +# 集群状态详情 +tccli tke DescribeClusterStatus --region --ClusterIds '[""]' +# 确认 ClusterDeletionProtection(true 时需先关闭) +``` + +**预期输出**: + +```json +{ + "ClusterStatusSet": [ + { + "ClusterId": "cls-example", + "ClusterState": "Running", + "ClusterDeletionProtection": true, + "ClusterAuditEnabled": false + } + ], + "RequestId": "..." +} +``` + +### 步骤 2:执行升级 + +#### 选择依据 + +- **版本选择**:从 `1.30.0` 升级到 `1.32.2`。`1.32.2` 是 `1.30.0` 的推荐升级路径。虽全局最新为 `1.34.1`,但不能跳级。 +- **升级策略**:仅升级控制面(Master),不升级节点。节点升级需单独执行 `UpgradeClusterInstances`。 +- **不可回退**:升级不可逆,不支持版本降级。 +- **升级时机**:业务低峰期或维护窗口,耗时约 10-30 分钟。 + +#### 最小升级 + +```bash +tccli tke UpdateClusterVersion --region \ + --ClusterId \ + --DstVersion +# expected: exit 0,返回 RequestId。异步操作,耗时 10-30 分钟。 +``` + +**预期输出**: + +```json +{ + "RequestId": "..." +} +``` + +| 占位符 | 说明 | 获取方式 | +|--------|------|---------| +| `` | 集群 ID | `tccli tke DescribeClusters --region ` | +| `` | 目标版本,如 `1.32.2` | `DescribeAvailableClusterVersion` 返回列表 | +| `` | 地域 | `tccli configure list` | + +> 参数名是 `--DstVersion`(不是 `--ClusterVersion` 也不是 `--Version`)。 + +#### 增强配置 + +`upgrade-enhanced.json`: + +```json +{ + "ClusterId": "", + "DstVersion": "", + "ExtraArgs": { + "KubeAPIServer": ["max-requests-inflight=500"], + "KubeControllerManager": [], + "KubeScheduler": [] + }, + "MaxNotReadyPercent": 0.1, + "SkipPreCheck": false +} +``` + +```bash +tccli tke UpdateClusterVersion --region \ + --cli-input-json file://upgrade-enhanced.json +# expected: exit 0 +``` + +### 步骤 3:轮询升级进度 + +建议轮询间隔 3-5 分钟: + +```bash +tccli tke DescribeClusters --region --ClusterIds '[""]' +# expected: ClusterStatus: "Upgrading" → 等待 → "Running",ClusterVersion 更新 +``` + +**预期输出(进行中)**: + +```json +{ + "Clusters": [ + { + "ClusterId": "cls-example", + "ClusterVersion": "1.32.2", + "ClusterStatus": "Upgrading" + } + ] +} +``` + +**预期输出(完成)**: + +```json +{ + "Clusters": [ + { + "ClusterId": "cls-example", + "ClusterVersion": "1.32.2", + "ClusterStatus": "Running", + "ClusterType": "MANAGED_CLUSTER" + } + ] +} +``` + +## 验证 + +```bash +# 确认版本已更新 +tccli tke DescribeClusters --region --ClusterIds '[""]' \ + | jq '.Clusters[0] | {ClusterVersion, ClusterStatus}' +# expected: ClusterVersion: "", ClusterStatus: "Running" +``` + +```json +{ + "TotalCount": "", + "Clusters": "", + "ClusterId": "", + "ClusterName": "", + "ClusterDescription": "", + "ClusterVersion": "" +} +``` + +| 维度 | 预期 | +|------|------| +| 集群版本 | `ClusterVersion` 为 `` | +| 集群状态 | `ClusterStatus: "Running"` | +| kubectl 连通性 | `Kubernetes control plane is running...`(内网环境) | +| 节点状态 | `kubectl get nodes` → 所有节点 `Ready` | + +> 升级后节点 kubelet 不会自动更新。旧版 kubelet 一般可与新版控制面兼容(偏差策略允许 <= 2 个次版本),建议通过 `UpgradeClusterInstances` 逐步升级节点。 + +## 清理 + +> **警告**:升级操作不可逆——不支持版本降级。升级完成后无需清理资源。 + +如果升级后发现问题: +1. 保留 region、ClusterId、RequestId,登录 [TKE 控制台](https://console.cloud.tencent.com/tke2/cluster) +2. 检查 `ClusterStatus` 和 `StatusReason` +3. 评估是否需要将业务迁移至新版本集群 + +## 排障 + +### 命令返回错误 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| `DescribeAvailableClusterVersion` 返回 `UnknownAction` | 检查 API 拼写 | API 名为 `DescribeAvailableClusterVersion`(非 `DescribeClusterAvailableVersion`) | 使用正确 API 名 | +| `DescribeAvailableClusterVersion` 返回 `null` 或空列表 | `DescribeClusters` 确认集群状态;`DescribeVersions` 查看全局版本 | 当前集群版本可能已最新,或 API 需特定条件才返回升级路径 | 检查集群状态为 `Running` | +| `UpdateClusterVersion` 返回 `UnknownParameter: DstVersion` | 检查参数拼写 | 参数名是 `--DstVersion`,不是 `--ClusterVersion` | 改为 `--DstVersion ` | +| `UpdateClusterVersion` 返回 `OperationDenied: DeletionProtection is enabled` | `DescribeClusterStatus` 查看 `ClusterDeletionProtection` | 升级前未关闭删除保护 | `DisableClusterDeletionProtection` 关闭后重试 | +| `UpdateClusterVersion` 返回 `InvalidParameter`(版本不可升级) | 确认 `--DstVersion` 是否在 `DescribeAvailableClusterVersion` 列表中 | 目标版本不在可升级路径中(如 1.30 跳到 1.34) | 选择返回列表中的版本,不能跳级 | +| `UpdateClusterVersion` 返回 `FailedOperation.ClusterState` | `DescribeClusters` 查看 `ClusterStatus` | 集群状态不是 `Running` | 等待恢复 `Running` 后重试 | +| `UpdateClusterVersion` 返回 `InvalidParameter.ExtraArgs` | 检查 `ExtraArgs` 格式 | 子字段必须为 `Array of String`,格式 `["k1=v1","k2=v2"]` | 调整格式 | + +### 升级已提交但状态异常 + +| 现象 | 诊断 | 根因 | 修复 | +|------|------|------|------| +| 升级后 >30 分钟仍 `Upgrading` | `DescribeClusters` 持续轮询 | 升级过程卡住 | 保留 RequestId → 登录控制台 → 提交工单 | +| 升级后 `Abnormal` | `DescribeClusters` 查看 `StatusReason` | 版本兼容性问题或组件故障 | 保留 RequestId → 登录控制台。紧急处理需迁移业务至新集群 | +| 升级后 kubectl 不可达 | 重新获取 kubeconfig | API Server 重启中或凭证过期 | 等待 2-5 分钟重试 | +| 升级后节点 NotReady | `kubectl get nodes` | kubelet 与控制面版本偏差过大 | 通过 `UpgradeClusterInstances` 升级节点 | +| 只升级控制面,节点版本不一致 | `kubectl get nodes -o wide` 查看 KubeletVersion | `UpdateClusterVersion` 只升级控制面 | 执行 `UpgradeClusterInstances` 升级节点 | + +## 下一步 + +- [集群生命周期](../lifecycle) — 状态流转管理 +- [TKE Kubernetes 大版本更新说明](https://cloud.tencent.com/document/product/457/47714) — 各版本关键变更 +- [Kubernetes API 废弃指南](https://kubernetes.io/docs/reference/using-api/deprecation-guide/) + +## 控制台替代 + +[TKE 控制台 → 集群详情 → 基本信息 → 集群版本 → 升级](https://console.cloud.tencent.com/tke2/cluster) diff --git a/src/content/docs/cli/tke/index.md b/src/content/docs/cli/tke/index.md new file mode 100644 index 0000000..810915c --- /dev/null +++ b/src/content/docs/cli/tke/index.md @@ -0,0 +1,52 @@ +--- +title: "TKE · tccli 操作参考" +description: "TKE (容器服务) tccli 操作契约:JSON bridge、waiter 模式、参数骨架与常用命令模式。" +--- + +> P0 done · 对照 [TKE 官方文档](https://cloud.tencent.com/document/product/457) · tccli ≥ 3.1.107.1 + +TKE (Tencent Kubernetes Engine) 任务页使用以下 tccli 契约。环境准备见 [CLI 操作指南概览](../)。 + +## Read actions (Describe*) + +使用单行命令加 `--output json`,可选 `--filter` (JMESPath): + +```bash +tccli tke DescribeClusters --region ap-guangzhou --output json +# 可选 JMESPath 过滤: +# tccli tke DescribeClusters --region ap-guangzhou --output json --filter 'TotalCount' +``` + +## 复杂写操作:JSON bridge + waiter + +异步创建/更新操作(如 `CreateCluster`)使用 **JSON bridge** 模式: + +1. 通过 `--cli-input-json file://examples/...` 传入精选最小模板 +2. 使用 `--waiter` 在后续 Describe* 调用中轮询至目标状态 + +```bash +tccli tke CreateCluster --cli-input-json file://examples/CreateCluster.min.json --region ap-guangzhou --output json +tccli tke DescribeClusters --ClusterIds '[""]' --region ap-guangzhou --output json --waiter "{'expr':'Clusters[0].ClusterStatus','to':'Running'}" +``` + +## 查看完整参数骨架 + +```bash +tccli tke CreateCluster --generate-cli-skeleton --output json > /tmp/CreateCluster.skeleton.json +``` + +任务页上优先使用 `examples/*.min.json`;从骨架中只取需要的字段。 + +## 集群配置 + +### 集群管理 + +- [创建集群](./cluster-config/cluster-management/create/) +- [删除集群](./cluster-config/cluster-management/delete/) +- [升级集群](./cluster-config/cluster-management/upgrade/) +- [集群扩缩容](./cluster-config/cluster-management/scale/) +- [连接集群](./cluster-config/cluster-management/connect/) +- [集群生命周期](./cluster-config/cluster-management/lifecycle/) +- [集群管理模式说明](./cluster-config/cluster-management/management-modes/) +- [更改集群操作系统](./cluster-config/cluster-management/change-os/) +- [自定义控制面组件参数](./cluster-config/cluster-management/custom-control-plane/) diff --git a/src/content/docs/cli/tkectl.md b/src/content/docs/cli/tkectl.md new file mode 100644 index 0000000..13c1e87 --- /dev/null +++ b/src/content/docs/cli/tkectl.md @@ -0,0 +1,8 @@ +--- +title: "tkectl — 产品 CLI" +description: "tkectl:统一 TKE + TCR 任务级命令的产品 CLI。即将发布。" +--- + +> 🚧 **即将发布** — `tkectl` 是面向 TKE 和 TCR 的 L1 产品 CLI,提供任务导向的命令封装、状态轮询、teaching errors 和结构化输出。 + +在当前阶段,请使用 **[tccli on TKE](../tke/)** 和 **[tccli on TCR](../tcr/)** 的 L0 API 直通文档。 diff --git a/src/data/navigation.mjs b/src/data/navigation.mjs index 29d413c..a424ab8 100644 --- a/src/data/navigation.mjs +++ b/src/data/navigation.mjs @@ -41,6 +41,12 @@ export const workshopNavItems = [ match: ['/contribute/'], sidebarLabels: ['Contribute'], }, + { + label: 'Agent CLI', + href: '/cli/', + match: ['/cli/'], + sidebarLabels: ['面向 Agent · CLI'], + }, ]; export function normalizePathname(pathname) {