1. Kubeflow 用户管理概述 2. 修改现有用户的 Email 或密码 步骤 1：访问 Dex 配置 3. 新增用户 步骤 1：添加用户到 Dex 步骤 2：创建 Profile 4. 删除用户 步骤 1：从 Dex 中删除用户 步骤 2：删除 Profile 和 Namespace 5. 其他用户管理操作 6. 注意事项与最佳实践 总结 在 Kubeflow 中，用户管理主要依赖其认证系统，通常通过 Dex（一个支持多种身份提供者的开源身份服务）与 Kubernetes 的 RBAC（基于角色的访问控制）结合实现。Kubeflow 支持多用户隔离，每个用户通常关联一个 Kubernetes Namespace（通过 Profile 资源实现）。以下是如何在 Kubeflow 中管理用户（包括修改现有用户的 email 或密码、新增用户等）的详细说明： 1. Kubeflow 用户管理概述 认证方式：Kubeflow 使用 Dex 提供认证，支持多种身份提供者（如 LDAP、OIDC、GitHub、Google 等）。用户登录后，Kubeflow 会自动为其创建对应的 Profile 和 Namespace（如果配置了自动创建）。 Profile 资源：Kubeflow 中的 Profile 是 Kubernetes 的自定义资源（CRD），用于定义用户及其关联的 Namespace。每个 Profile 通常对应一个用户或用户组，并绑定到 Kubernetes RBAC 策略。 权限管理：用户权限通过 Kubernetes RBAC 管理，管理员可以为用户的 Namespace 设置只读或可写权限。 用户数据存储：用户凭据（如 email 和密码）通常存储在 Dex 的配置文件中（静态用户）或外部身份提供者（如 LDAP）中。 2. 修改现有用户的 Email 或密码 在 Kubeflow 中，修改用户的 email 或密码需要根据使用的认证方式进行操作。以下以 Dex 的静态用户配置为例（如果使用 LDAP 或 OIDC，请参考对应身份提供者的文档）： 步骤 1：访问 Dex 配置 Dex 的用户数据存储在 Kubernetes ConfigMap 中，位于 auth Namespace 的 dex ConfigMap 内。 获取 Dex 配置： kubectl get configmap dex -n auth -o jsonpath='{.data.config\.yaml}' > dex-config.yaml 编辑 dex-config.yaml 文件，找到 staticPasswords 部分。示例： staticPasswords: - email: "user1@example.com" hash: "$2y$12$..." username: "user1" 修改 Email：直接更改 email 字段，例如将 user1@example.com 改为 newuser1@example.com。 修改密码： 使用 Python 的 passlib 库生成新的 bcrypt 密码哈希： python3 -c 'from passlib.hash import bcrypt; import getpass; print(bcrypt.using(rounds=12, ident="2y").hash(getpass.getpass()))' 输入新密码后，获取生成的哈希值。 将 hash 字段替换为新生成的哈希值。 更新 Dex ConfigMap： kubectl create configmap dex -n auth --from-file=config.yaml=dex-config.yaml --dry-run=client -o yaml | kubectl apply -f - 重启 Dex Pod 使配置生效： kubectl delete pod -n auth -l app=dex 注意事项 如果用户通过 LDAP 或 OIDC 认证，需在相应的外部身份提供者系统中修改 email 或密码，而不是直接修改 Dex 配置。 修改 email 后，可能需要更新对应的 Profile 资源（见下文）。 更新 Profile 的 Email 如果修改了用户的 email，需要同步更新 Kubeflow Profile 资源： 编辑 Profile： kubectl edit profile <profile-name> -n kubeflow 更新 spec.owner.name 字段为新的 email，例如： apiVersion: kubeflow.org/v1beta1 kind: Profile metadata: name: user1-profile spec: owner: kind: User name: newuser1@example.com 应用更改后，Kubeflow 会更新用户与 Namespace 的关联。 3. 新增用户 在 Kubeflow 中新增用户需要创建用户凭据并为用户分配 Profile 和 Namespace。 步骤 1：添加用户到 Dex 编辑 Dex ConfigMap（如上所述）： kubectl get configmap dex -n auth -o jsonpath='{.data.config\.yaml}' > dex-config.yaml 在 staticPasswords 部分添加新用户： staticPasswords: - email: "newuser@example.com" hash: "$2y$12$..." # 使用 passlib 生成的哈希 username: "newuser" 生成密码哈希的命令： python3 -c 'from passlib.hash import bcrypt; import getpass; print(bcrypt.using(rounds=12, ident="2y").hash(getpass.getpass()))' 更新 ConfigMap 并重启 Dex： kubectl create configmap dex -n auth --from-file=config.yaml=dex-config.yaml --dry-run=client -o yaml | kubectl apply -f - kubectl delete pod -n auth -l app=dex 步骤 2：创建 Profile 为新用户创建 Profile 以分配 Namespace 和权限： 创建 Profile YAML 文件（例如 newuser-profile.yaml）： apiVersion: kubeflow.org/v1beta1 kind: Profile metadata: name: newuser-profile spec: owner: kind: User name: newuser@example.com 应用 Profile： kubectl apply -f newuser-profile.yaml 验证 Namespace 是否创建： kubectl get namespace newuser-profile 自动创建 Profile 如果 Kubeflow 配置了自动创建 Profile（从 v0.6.2 开始支持），用户首次登录时会自动生成 Profile 和 Namespace，无需手动创建。确保 Dex 配置正确即可。 4. 删除用户 删除用户需要从 Dex 和 Kubeflow 中移除用户数据，并清理相关资源。 步骤 1：从 Dex 中删除用户 编辑 Dex ConfigMap，移除 staticPasswords 中对应的用户条目。 更新 ConfigMap 并重启 Dex： kubectl create configmap dex -n auth --from-file=config.yaml=dex-config.yaml --dry-run=client -o yaml | kubectl apply -f - kubectl delete pod -n auth -l app=dex 步骤 2：删除 Profile 和 Namespace 删除 Profile： kubectl delete profile <profile-name> -n kubeflow 删除对应的 Namespace： kubectl delete namespace <profile-name> 注意事项 如果 Namespace 被自动重建，可能是 Dex 或 LDAP 中仍有用户数据。确保从身份提供者中完全移除用户。 删除 Namespace 前，检查是否有重要资源（如 Jupyter Notebooks 或 Pipelines）需要备份。 5. 其他用户管理操作 权限管理： 使用 Kubernetes RBAC 控制用户权限。例如，为用户授予特定 Namespace 的读写权限： apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: user-read-write namespace: newuser-profile subjects: - kind: User name: newuser@example.com apiGroup: rbac.authorization.k8s.io roleRef: kind: Role name: edit apiGroup: rbac.authorization.k8s.io 应用 RoleBinding： kubectl apply -f rolebinding.yaml 共享 Profile： 用户可以通过 Profile 共享 Namespace 的访问权限（只读或可写）。编辑 Profile 的 spec 添加共享用户： spec: owner: kind: User name: newuser@example.com resourceQuota: hard: cpu: "4" memory: 16Gi 使用外部身份提供者： 如果使用 LDAP 或 OIDC，需在 Dex 配置中添加连接器（connectors 字段），并在外部系统中管理用户。例如，LDAP 配置： connectors: - type: ldap name: OpenLDAP id: ldap config: host: ldap.example.com:636 bindDN: cn=admin,dc=example,dc=com bindPW: adminpassword 6. 注意事项与最佳实践 安全性：避免使用简单的静态密码，优先集成 LDAP 或 OIDC 以提高安全性。 备份：在修改 Dex 配置或删除用户前，备份 ConfigMap 和相关资源。 日志监控：检查 Dex 和 Kubeflow 的日志以排查认证问题： kubectl logs -n auth -l app=dex 版本兼容性：不同 Kubeflow 版本的 Dex 配置可能略有差异，参考官方文档。 自动化：对于大规模用户管理，考虑使用脚本或 CI/CD 流水线来管理 Dex 配置和 Profile 资源。 总结 修改现有用户名和密码 步骤1：生成新的密码，通过 UI 或 yaml 文件进行替换新的密码 python3 -c 'from passlib.hash import bcrypt; import getpass; print(bcrypt.using(rounds=12, ident="2y").hash(getpass.getpass()))' 类似如下： $2y$12$eRxphHBSoP/5R6L8ArPW7uk8UZjwIsY9hMLQaUoVaZUxNzm1IKkPO 步骤2：修改 data->config.yaml->staticPasswords->email 为新的 email kubectl edit configmap dex -n auth 步骤3：修改 spec->owner->name 为新的 email kubectl edit profile <profile> -n kubeflow 步骤4：所在的 namespace 中修改 subjects->name 为新的 email kubectl edit rolebinding namespaceAdmin -n <namespace>
跳过 Knative-serving revision 针对私有 registry 证书认证问题
问题现象 尝试方法 将私有 registry 证书导入 终极方法，还是在 GitHub issue 找到 问题现象 当使用私有 registry 的 image 作为 ClusterServingRuntime 时，启动 knative-serving revision 后，会出现以下错误，导致内网私有 registry 的证书不被信任，无法启动 kserve InferenceService 。困扰了我好久好久。 Unable to fetch image "hub.yiqisoft.cn/openvino/model_server:latest-gpu": failed to resolve image to digest: Get "https://hub.yiqisoft.cn/v2/": tls: failed to verify certificate: x509: certificate signed by unknown authority 尝试方法 将私有 registry 证书导入 虽然手动拉取 image 没问题，但是 knative-serving 还是 无效 终极方法，还是在 GitHub issue 找到 issue: https://github.com/knative/serving/issues/5126 需要修改 ConfigMaps , knative-serving namespace 下 config-deployment，增加一个 key/value registries-skipping-tag-resolving: kind.local,ko.local,dev.local,hub.yiqisoft.cn
如何在 KServe 的 InferenceService 中引用 S3(Minio) Secret 并注入到整个 pipeline？
使用 Secret 直接注入 需要一个单独的 Secret 来管理 S3(Minio) 连接 创建 KServe 的 InferenceService 时需要引入注解 最后，在 pod 中就出现了这些 env 使用 ServiceAccount 注入 创建 S3(Minio) 连接 Secret 创建 ServiceAccount 并引用 上面的 Secret 创建 InferenceService 时引用 ServiceAccount 问题 使用 Secret 直接注入 需要一个单独的 Secret 来管理 S3(Minio) 连接 这里的注解千万不能少，否则 S3 连接信息不会注入到 pod 中，尤其是 storage-initializer 阶段需要用到 S3 连接信息从 模型仓库拿到模型文件。 尤其要注意：模型仓库的连接信息 uri 必须是以 / 结尾，比如：s3://models/ovms/yolo11n/，一定不能是 s3://models/ovms/yolo11n apiVersion: v1 data: AWS_ACCESS_KEY_ID: bWluaW8= AWS_SECRET_ACCESS_KEY: bWluaW8xMjM= kind: Secret metadata: annotations: serving.kserve.io/s3-endpoint: minio-service.kubeflow.svc.cluster.local:9000 serving.kserve.io/s3-region: east serving.kserve.io/s3-useanoncredential: 'false' serving.kserve.io/s3-usehttps: '0' name: minio-secret namespace: yiqisoft type: Opaque 创建 KServe 的 InferenceService 时需要引入注解 最关键的就是这个注解："serving.kserve.io/storageSecretName": "minio-secret" 完整的代码 from kubernetes import client import kserve isvc = kserve.V1beta1InferenceService( api_version=kserve.constants.KSERVE_GROUP + "/v1beta1", kind=kserve.constants.KSERVE_KIND, metadata=client.V1ObjectMeta( name=f"{DET_MODEL_NAME}", namespace=kserve.utils.get_default_target_namespace(), labels={ "modelregistry/registered-model-id": model.id, "modelregistry/model-version-id": version.id, }, annotations={ "serving.kserve.io/storageSecretName": "minio-secret" }, ), spec=kserve.V1beta1InferenceServiceSpec( predictor=kserve.V1beta1PredictorSpec( model=kserve.V1beta1ModelSpec( storage_uri=art.uri, model_format=kserve.V1beta1ModelFormat( name=art.model_format_name, version=art.model_format_version, ), runtime="kserve-ovms" ), ) ), ) ks_client = kserve.KServeClient() ks_client.create(isvc) 最后，在 pod 中就出现了这些 env 这些 env 怎么来的我还没有完全搞清楚。反正 secret 里面的内容都注入成功。 env: - name: AWS_ACCESS_KEY_ID valueFrom: secretKeyRef: key: AWS_ACCESS_KEY_ID name: minio-secret - name: AWS_SECRET_ACCESS_KEY valueFrom: secretKeyRef: key: AWS_SECRET_ACCESS_KEY name: minio-secret - name: S3_USE_HTTPS value: '0' - name: S3_ENDPOINT value: minio-service.kubeflow.svc.cluster.local:9000 - name: AWS_ENDPOINT_URL value: http://minio-service.kubeflow.svc.cluster.local:9000 - name: awsAnonymousCredential value: 'false' - name: AWS_DEFAULT_REGION value: east 使用 ServiceAccount 注入 创建 S3(Minio) 连接 Secret 需要在 Secret 中创建 S3 的连接信息注解，便于 Kserve controller 解析 apiVersion: v1 data: AWS_ACCESS_KEY_ID: bWluaW8= AWS_SECRET_ACCESS_KEY: bWluaW8xMjM= kind: Secret metadata: annotations: serving.kserve.io/s3-endpoint: minio-service.kubeflow.svc.cluster.local:9000 serving.kserve.io/s3-region: east serving.kserve.io/s3-useanoncredential: 'false' serving.kserve.io/s3-usehttps: '0' name: s3creds namespace: yiqisoft type: Opaque 创建 ServiceAccount 并引用 上面的 Secret apiVersion: v1 kind: ServiceAccount metadata: name: sa namespace: yiqisoft secrets: - name: s3creds 创建 InferenceService 时引用 ServiceAccount 这行代码特别要注意，不能少 service_account_name="sa" from kubernetes import client import kserve isvc = kserve.V1beta1InferenceService( api_version=kserve.constants.KSERVE_GROUP + "/v1beta1", kind=kserve.constants.KSERVE_KIND, metadata=client.V1ObjectMeta( name=f"{DET_MODEL_NAME}", namespace=kserve.utils.get_default_target_namespace(), labels={ "modelregistry/registered-model-id": model.id, "modelregistry/model-version-id": version.id, } ), spec=kserve.V1beta1InferenceServiceSpec( predictor=kserve.V1beta1PredictorSpec( model=kserve.V1beta1ModelSpec( storage_uri=art.uri, model_format=kserve.V1beta1ModelFormat( name=art.model_format_name, version=art.model_format_version, ), runtime="kserve-ovms" ), service_account_name="sa" ) ), ) ks_client = kserve.KServeClient() ks_client.create(isvc) 问题 KubeFlow 默认已经生成了一个 mlpipeline-minio-artifact Secret ，如何才能复用里面的内容呢？由于关键字不一样，无法完成注入。
Kubeflow Notebook 里映射 Minio secret 键值对 env 环境变量
Secret 配置和绑定 Kubeflow 默认安装配置 Minio secret PodDefault 绑定 自定义配置 Minio secret PodDefault 绑定 secret Kubeflow Notebook 配置 metadata -> labels 加上你的 PodDefault name 即可 使用 PodDefault 绑定 label 即可实现 Secret 配置和绑定 Minio secret 肯定是保存在 secret 中 Kubeflow 默认安装配置 Minio secret 在自己的 namespace 中： mlpipelin-minio-artifact PodDefault 绑定 apiVersion: kubeflow.org/v1alpha1 kind: PodDefault metadata: name: add-minio-secret namespace: yiqisoft spec: envFrom: - secretRef: name: mlpipeline-minio-artifact selector: matchLabels: add-minio-secret: 'true' 自定义配置 Minio secret apiVersion: v1 kind: Secret metadata: name: s3creds namespace: yiqisoft data: AWS_ACCESS_KEY_ID: bWluaW8= AWS_SECRET_ACCESS_KEY: bWluaW8xMjM= type: Opaque PodDefault 绑定 secret apiVersion: kubeflow.org/v1alpha1 kind: PodDefault metadata: name: add-minio-secret namespace: yiqisoft spec: desc: add minio credential to env envFrom: - secretRef: name: s3creds selector: matchLabels: add-minio-secret: 'true' Kubeflow Notebook 配置 metadata -> labels 加上你的 PodDefault name 即可 apiVersion: kubeflow.org/v1 kind: Notebook metadata: labels: add-minio-secret: 'true'
环境准备 下载离线安装文件 准备数据库 PostgreSQL 安装部署 k3s 准备服务器 导入镜像包 拷贝主程序 k3s 安装 安装 Helm 部署工具 部署 Cert Manager 部署 Rancher 集群管理工具 导入镜像 部署 Rancher 部署网络存储 使用 Ceph（推荐） 使用 NFS 网络文件系统 部署 Harbor 私有化仓库 部署 pv 和 pvc 安装 NFS 依赖 部署 Harbor 部署 ChartMuseum 管理工具 下载 ChartMuseum 镜像 部署 ChartMuseum Helm Chart 上传 Charts 到 ChartMuseum 部署 frps 反向代理 编译 frps 开发 frps Helm Chart 部署到 k3s 管理集群 验证结果 管理新的集群（业务集群） 环境准备 下载离线安装文件 本例子用 v1.30.4+k3s1 安装脚本 install.sh wget https://github.com/k3s-io/k3s/raw/refs/tags/v1.30.4+k3s1/install.sh 主程序 k3s wget https://github.com/k3s-io/k3s/releases/download/v1.30.4%2Bk3s1/k3s 镜像压缩包 wget https://github.com/k3s-io/k3s/releases/download/v1.30.4%2Bk3s1/k3s-airgap-images-amd64.tar 准备数据库 PostgreSQL 数据库 name ip notes database 192.168.100.100 PostgreSQL 创建 compose.yml services: postgres: image: postgres:alpine container_name: postgres restart: unless-stopped environment: POSTGRES_USER: k3s POSTGRES_PASSWORD: talent POSTGRES_DB: k3s ports: - "5432:5432" volumes: - ./data:/var/lib/postgresql/data - ./init-db.sh:/docker-entrypoint-initdb.d/init-db.sh 创建脚本 init-db.sh #!/bin/bash set -e psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname=postgres <<-EOSQL CREATE DATABASE harbor; EOSQL 启动 PostgreSQL 数据库容器 docker compose up -d 验证数据库 docker exec -it postgres psql -U k3s -c '\l' List of databases Name | Owner | Encoding | Locale Provider | Collate | Ctype | Locale | ICU Rules | Access privileges -----------+-------+----------+-----------------+------------+------------+--------+-----------+------------------- harbor | k3s | UTF8 | libc | en_US.utf8 | en_US.utf8 | | | k3s | k3s | UTF8 | libc | en_US.utf8 | en_US.utf8 | | | ... (5 rows) 安装部署 k3s 准备服务器 准备三台机器，都作为 master 运行，本节内容需要在三台都执行。 name ip notes master01 192.168.100.101 必须 master02 192.168.100.102 可选 master03 192.168.100.103 可选 cat <<EOF >> /etc/hosts 192.168.100.100 database 192.168.100.101 master01 192.168.100.102 master02 192.168.100.103 master03 EOF 导入镜像包 离线安装重要步骤 mkdir -p /var/lib/rancher/k3s/agent/images/ cp k3s-airgap-images-amd64.tar /var/lib/rancher/k3s/agent/images/ 拷贝主程序 k3s 拷贝进执行目录，不需要下载主程序 chmod a+x k3s && cp k3s /usr/local/bin/ 安装 执行安装脚本，并连接数据库 INSTALL_K3S_SKIP_DOWNLOAD=true \ sh install.sh -s - server \ --datastore-endpoint="postgres://k3s:talent@database:5432/k3s?sslmode=disable" \ --token K10c71232f051944de58ab058871ac7a85b42cbdee5c6df94deb6bb493c79b15a92 查看镜像状态 ctr i ls -q docker.io/rancher/klipper-helm:v0.8.4-build20240523 docker.io/rancher/klipper-lb:v0.4.9 docker.io/rancher/local-path-provisioner:v0.0.28 docker.io/rancher/mirrored-coredns-coredns:1.10.1 docker.io/rancher/mirrored-library-busybox:1.36.1 docker.io/rancher/mirrored-library-traefik:2.10.7 docker.io/rancher/mirrored-metrics-server:v0.7.0 docker.io/rancher/mirrored-pause:3.6 三台机器都执行完成，查看集群 node 状态 kubectl get nodes NAME STATUS ROLES AGE VERSION master01 Ready control-plane,master 4m49s v1.30.4+k3s1 master02 Ready control-plane,master 51s v1.30.4+k3s1 master03 Ready control-plane,master 45s v1.30.4+k3s1 查看 pod 状态 kubectl get pods -A NAMESPACE NAME READY STATUS RESTARTS AGE kube-system coredns-576bfc4dc7-f6bsb 1/1 Running 0 27m kube-system helm-install-traefik-crd-5b2x4 0/1 Completed 0 27m kube-system helm-install-traefik-pl6k7 0/1 Completed 1 27m kube-system local-path-provisioner-5f9d8-lhntz 1/1 Running 0 27m kube-system metrics-server-557ff575fb-77xzw 1/1 Running 0 27m kube-system svclb-traefik-151f91a9-8tkdq 2/2 Running 0 23m kube-system svclb-traefik-151f91a9-b7pwz 2/2 Running 0 23m kube-system svclb-traefik-151f91a9-kwwp8 2/2 Running 0 25m kube-system traefik-5fb479b77-r84mv 1/1 Running 0 25m 安装 Helm 部署工具 下载 Helm 二进制，安装 wget https://get.helm.sh/helm-v3.16.2-linux-amd64.tar.gz tar -zxvf helm-v3.16.2-linux-amd64.tar.gz chmod a+x linux-amd64/helm && cp helm /usr/local/bin/ 设置环境变量 加入到 ～/.bashrc 自启动 echo "export KUBECONFIG=/etc/rancher/k3s/k3s.yaml" >> ~/.bashrc && source ~/.bashrc 部署 Cert Manager 在线下载 Cert Manager 镜像，并导出，复制到三台服务器 docker images --format "{{.Repository}}:{{.Tag}}" |grep cert-manager > cert-manager.txt docker save $(awk '{printf "%s ", $0}' cert-manager.txt) | gzip > cert-manager.tar.gz 导入 Cert Manager 镜像 gzip -d -c cert-manager-images.tar.gz | ctr i import - 下载所需文件，复制到某一台服务器后 wget https://github.com/cert-manager/cert-manager/releases/download/v1.15.3/cert-manager.crds.yaml wget https://charts.jetstack.io/charts/cert-manager-v1.15.3.tgz 部署 cert-manager 配置 kubectl apply -f cert-manager.crds.yaml customresourcedefinition.apiextensions.k8s.io/certificaterequests.cert-manager.io created customresourcedefinition.apiextensions.k8s.io/certificates.cert-manager.io created customresourcedefinition.apiextensions.k8s.io/challenges.acme.cert-manager.io created customresourcedefinition.apiextensions.k8s.io/clusterissuers.cert-manager.io created customresourcedefinition.apiextensions.k8s.io/issuers.cert-manager.io created customresourcedefinition.apiextensions.k8s.io/orders.acme.cert-manager.io created 使用 Helm 部署 cert-manager 离线包 helm install cert-manager ./cert-manager-v1.15.3.tgz --namespace cert-manager --create-namespace or helm install cert-manager ./cert-manager-v1.15.3.tgz --namespace cert-manager --create-namespace --set crds.enabled=true kubectl get pods -n cert-manager NAME READY STATUS RESTARTS AGE cert-manager-9647b459d-dqd8b 1/1 Running 0 23m cert-manager-cainjector-5d8798687c-l2kkf 1/1 Running 0 23m cert-manager-startupapicheck-6gglh 0/1 Completed 1 23m cert-manager-webhook-c77744d75-wd2kx 1/1 Running 0 23m 部署 Rancher 集群管理工具 导入镜像 在线下载 Rancher 镜像，并导出，复制到三台服务器 docker images --format "{{.Repository}}:{{.Tag}}" |grep rancher > rancher.txt docker save $(awk '{printf "%s ", $0}' rancher.txt) | gzip > rancher-images.tar.gz 导入 Rancher 镜像 gzip -d -c rancher-images.tar.gz | ctr i import - 部署 Rancher 下载 Rancher Helm 包 wget https://releases.rancher.com/server-charts/latest/rancher-2.9.2.tgz 使用 Helm 部署 Rancher 离线包 helm install rancher ./rancher-2.9.2.tgz \ --namespace cattle-system \ --set hostname=manager.yiqisoft.cn \ --create-namespace 查询 Rancher 安装结果 kubectl get pods -n cattle-system NAME READY STATUS RESTARTS AGE rancher-57c9747d96-p498t 1/1 Running 0 52m rancher-57c9747d96-pdrtn 1/1 Running 1 (52m ago) 52m rancher-57c9747d96-xm9sl 1/1 Running 0 52m rancher-webhook-77b49b9ff9-fqdxf 1/1 Running 0 51m 配置 dns 指向，访问 Rancher 页面 dns 指向到三台服务器，如果有条件，要设置 HAProxy 和 KeepAlived 做负载均衡。 address=/manager.yiqisoft.cn/192.168.100.101 address=/manager.yiqisoft.cn/192.168.100.102 address=/manager.yiqisoft.cn/192.168.100.103 部署网络存储 使用 Ceph（推荐） 准备一个 Ceph 集群用于存储（这里省略，请自行补脑）。 使用 NFS 网络文件系统 简单实用，但是效率不高，安全性一般。 在一个文件服务器上部署 NFS，这里用 database 主机 apt update apt install nfs-kernel-server -y mkdir -p /opt/nfs_share chown nobody:nogroup /opt/nfs_share chmod 777 /opt/nfs_share echo "/opt/nfs_share *(rw,sync,no_subtree_check)" >> /etc/exports exportfs -ra systemctl restart nfs-kernel-server 部署 Harbor 私有化仓库 部署 pv 和 pvc 编辑 pv-pvc.yaml apiVersion: v1 kind: PersistentVolume metadata: name: nfs-pv spec: capacity: storage: 30Gi accessModes: - ReadWriteMany nfs: path: /opt/nfs_share server: database --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nfs-pvc spec: accessModes: - ReadWriteMany resources: requests: storage: 10Gi volumeName: nfs-pv storageClassName: "" 应用到 namespace harbor kubectl create namespace harbor kubectl apply -f pv-pvc.yaml -n harbor 结果 persistentvolume/nfs-pv created persistentvolumeclaim/nfs-pvc created 查看结果，nfs-pvc 必须是 Bound kubectl get pvc -n harbor 结果 NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE nfs-pvc Bound nfs-pv 30Gi RWX <unset> 92s 查看 nfs-pv 必须是 Bound kubectl get pv -n harbor 结果 NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE nfs-pv 30Gi RWX Retain Bound harbor/nfs-pvc <unset> 97s 安装 NFS 依赖 所有的 Master 上需要安装 nfs-common，并验证（可选），可以访问 /tmp/nfs 目录 apt install nfs-common -y mkdir /tmp/nfs mount -t nfs database:/opt/nfs_share /tmp/nfs ll /tmp/nfs 部署 Harbor 在线下载 Harbor Helm 文件，并复制到某一台服务器 wget https://helm.goharbor.io/harbor-1.15.1.tgz 在线方式，导出默认 values，修改 helm show values harbor/harbor > harbor-values.yml # 内容修改如下 expose: ingress: hosts: core: hub.yiqisoft.cn externalURL: https://hub.yiqisoft.cn persistentVolumeClaim: registry:/exter existingClaim: "nfs-pvc" storageClass: "-" database: type: external external: host: "192.168.100.100" port: "5432" username: "k3s" password: "talent" coreDatabase: "harbor" harborAdminPassword: "Harbor12345" 在线下载 Harbor 镜像，导出 docker images --format "{{.Repository}}:{{.Tag}}" |grep goharbor > harbor.txt docker save $(awk '{printf "%s ", $0}' harbor.txt) | gzip > harbor-images.tar.gz 导入 Harbor 镜像 gzip -d -c harbor-images.tar.gz | ctr i import - 安装 Harbor，需要通过互联网下载所有的镜像，时间根据网络带宽决定 helm install harbor ./harbor-1.15.1.tgz -f harbor-values.yml -n harbor 确认 Harbor 是否安装成功 kubectl get pods -n harbor 都显示 READY 即可 NAME READY STATUS RESTARTS AGE harbor-core-666cf84bc6-vq8mp 1/1 Running 0 85s harbor-jobservice-c68bc669-6twbp 1/1 Running 0 85s harbor-portal-d6c99f896-cq9fx 1/1 Running 0 85s harbor-redis-0 1/1 Running 0 84s harbor-registry-5984c768c8-4xnrg 2/2 Running 0 84s harbor-trivy-0 1/1 Running 0 84s 配置 dns 指向，访问 Harbor 页面 dns 指向到三台服务器，如果有条件，要设置 HAProxy 和 KeepAlived 做负载均衡。 address=/hub.yiqisoft.cn/192.168.100.101 address=/hub.yiqisoft.cn/192.168.100.102 address=/hub.yiqisoft.cn/192.168.100.103 验证 Harbor 工作 curl -k https://hub.yiqisoft.cn/v2/ {"errors":[{"code":"UNAUTHORIZED","message":"unauthorized: unauthorized"}]} 部署 ChartMuseum 管理工具 下载 ChartMuseum 镜像 在线下载 ChartMuseum 镜像，并导出，复制到三台服务器 docker save hub.yiqisoft.cn/yiqisoft/chartmuseum:v0.16.2 | gzip > chartmuseum-images.tar.gz 上传到私有仓库 docker push hub.yiqisoft.cn/yiqisoft/chartmuseum:v0.16.2 或导入 ChartMuseum 镜像 gzip -d -c chartmuseum-images.tar.gz | ctr i import - 部署 ChartMuseum Helm Chart 下载 ChartMuseum chart wget https://github.com/chartmuseum/charts/releases/download/chartmuseum-3.10.3/chartmuseum-3.10.3.tgz 部署 Chart helm install chartmuseum ./chartmuseum-3.10.3.tgz \ --namespace chartmuseum \ --create-namespace \ --set env.open.DISABLE_API=false \ --set service.type=LoadBalancer \ --set persistence.enabled=true \ --set persistence.existingClaim=pvc-chart \ --set env.secret.BASIC_AUTH_USER=yiqisoft \ --set env.secret.BASIC_AUTH_PASS=talent 部署一个 Ingress apiVersion: networking.k8s.io/v1 kind: Ingress name: chartmuseum namespace: chartmuseum spec: ingressClassName: traefik rules: - host: chart.yiqisoft.cn http: paths: - backend: service: name: chartmuseum port: number: 8080 path: / pathType: Prefix 或在 Rancher UI 中增加一个类似配置。 上传 Charts 到 ChartMuseum 使用类似这样的命令即可上传，ChartMuseum 会自动索引文件到 index.yaml curl --data-binary "@yiedge-base-3.1.0.tgz" http://chart.yiqisoft.cn/api/charts 检查上传结果 curl chart.yiqisoft.cn/index.yaml apiVersion: v1 entries: yiedge-base: - apiVersion: v1 appVersion: Pnicom created: "2024-11-11T10:11:55.473460704Z" description: A Helm Chart for YiEDGE base digest: 14b9a971a53a2482a6d4a930daefe2b0fee1cb1bcef909b023cc34959b4c142a home: https://www.yiqisoft.cn icon: https://www.yiqisoft.cn/images/yiqilogo.png keywords: - IoT - Middleware - YiEDGE name: yiedge-base sources: - https://github.com/yiqisoft urls: - charts/yiedge-base-3.1.0.tgz version: 3.1.0 generated: "2024-11-11T10:11:56Z" serverInfo: {} 部署 frps 反向代理 编译 frps clone 仓库，并编译 docker image git clone https://github.com/fatedier/frp.git git checkout v0.61.0 docker build -f dockerfiles/Dockerfile-for-frps . -t hub.yiqisoft.cn/yiqisoft/frps:v0.61.0 push 到私有仓库 docker push hub.yiqisoft.cn/yiqisoft/frps:v0.61.0 开发 frps Helm Chart 给出范例，根据自己的需求完成。 Helm Chart 目录结构 frp-server/ ├── charts/ # 可选，依赖的子 Chart ├── templates/ # 存放模板文件 │ ├── deployment.yaml # 部署模板 │ ├── service.yaml # 服务模板 │ ├── configmap.yaml # 配置文件模板 │ ├── ingress.yaml # 可选的 Ingress 配置 ├── values.yaml # 默认配置文件 ├── Chart.yaml # Chart 描述文件 Chart.yaml apiVersion: v1 name: frps version: "0.0.1" appVersion: frp Server description: A Helm Chart for frp server keywords: - frp - reverse proxy home: https://www.yiqisoft.cn icon: https://www.yiqisoft.cn/images/yiqilogo.png sources: - https://github.com/yiqisoft values.yaml 根据需要修改 domain 和 subDomainHost，还有一些对外开放的端口 service.nodePorts.http/frp/dashboard rreplicaCount: 1 fullnameOverride: "frp-server" domain: "*.h.yiqisoft.cn" # dynamic domain image: repository: hub.yiqisoft.cn/yiqisoft/frps tag: v0.61.0 pullPolicy: IfNotPresent service: type: NodePort nodePorts: http: 30080 # vhost http nodePort frp: 30000 # frp service nodePort dashboard: 30500 # dashboard nodePort frpConfig: bindPort: 7000 vhostHTTPPort: 8888 subDomainHost: "h.yiqisoft.cn" webServer: addr: "0.0.0.0" port: 7500 user: "yiqisoft" password: "talent123!" pprofEnable: true log: to: "/tmp/frps.log" level: "info" maxDays: 7 auth: token: "yiqisoft" deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Values.fullnameOverride }} labels: app: {{ .Values.fullnameOverride }} spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: app: {{ .Values.fullnameOverride }} template: metadata: labels: app: {{ .Values.fullnameOverride }} spec: containers: - name: frp-server image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} args: - "-c" - "/frps.toml" ports: - name: frp-vhost containerPort: {{ .Values.frpConfig.vhostHTTPPort }} - name: frp-port containerPort: {{ .Values.frpConfig.bindPort }} - name: frp-dashboard containerPort: {{ .Values.frpConfig.webServer.port }} volumeMounts: - name: frp-config mountPath: /frps.toml subPath: frps.toml volumes: - name: frp-config configMap: name: {{ .Values.fullnameOverride }}-config service.yaml apiVersion: v1 kind: Service metadata: name: {{ .Values.fullnameOverride }}-service labels: app: {{ .Values.fullnameOverride }} spec: type: NodePort ports: - name: frp-vhost port: {{ .Values.frpConfig.vhostHTTPPort }} # 配置的 HTTP 端口 targetPort: frp-vhost protocol: TCP nodePort: {{ .Values.service.nodePorts.http }} # 从 values.yaml 中读取对外暴露的端口 - name: frp-port port: {{ .Values.frpConfig.bindPort }} # 配置的 bindPort targetPort: frp-port protocol: TCP nodePort: {{ .Values.service.nodePorts.frp }} # 从 values.yaml 中读取对外暴露的端口 - name: frp-dashboard port: {{ .Values.frpConfig.webServer.port }} # 配置的 dashboard 端口 targetPort: frp-dashboard protocol: TCP nodePort: {{ .Values.service.nodePorts.dashboard }} # 从 values.yaml 中读取对外暴露的端口 selector: app: {{ .Values.fullnameOverride }} ingress.yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: {{ .Values.fullnameOverride }}-ingress labels: app: {{ .Values.fullnameOverride }} annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: rules: - host: "{{ .Values.domain }}" http: paths: - path: / pathType: Prefix backend: service: name: {{ .Values.fullnameOverride }}-service port: number: {{ .Values.frpConfig.vhostHTTPPort }} configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: {{ .Values.fullnameOverride }}-config data: frps.toml: | bindPort = {{ .Values.frpConfig.bindPort }} vhostHTTPPort = {{ .Values.frpConfig.vhostHTTPPort }} subDomainHost = "{{ .Values.frpConfig.subDomainHost }}" webServer.addr = "{{ .Values.frpConfig.webServer.addr }}" webServer.port = {{ .Values.frpConfig.webServer.port }} webServer.user = "{{ .Values.frpConfig.webServer.user }}" webServer.password = "{{ .Values.frpConfig.webServer.password }}" webServer.pprofEnable = {{ .Values.frpConfig.webServer.pprofEnable }} log.to = "{{ .Values.frpConfig.log.to }}" log.level = "{{ .Values.frpConfig.log.level }}" log.maxDays = {{ .Values.frpConfig.log.maxDays }} auth.token = "{{ .Values.frpConfig.auth.token }}" 打包 Chart helm package frps-server Successfully packaged chart and saved it to: /opt/charts/frps-0.0.1.tgz 上传到私有 ChartMuseum curl --data-binary "@frps-0.0.1.tgz" http://chart.yiqisoft.cn/api/charts 部署到 k3s 管理集群 通过手工方式将 frps-0.0.1.tgz 部署到集群 helm install frps-server ./frp-0.0.1.tgz -n frps --createnamespace 通过 Rancher UI 部署更为简单一些。 验证结果 需要在私有 dns 解析地址，例如 dnsmasq address=/h.yiqisoft.cn/192.168.100.101 address=/*.h.yiqisoft.cn/192.168.100.101 访问 http://h.yiqisoft.cn:30500 可以管理 frps frpc 连接服务器 tcp://h.yiqisoft.cn:30000，即可访问 client-id.h.yiqisoft.cn 管理新的集群（业务集群） 某些情况下，可能需要清理 CoreDNS 缓存 kubectl -n kube-system rollout restart deployment coredns
创建 Namespace PVC 作为存储，其实不需要 上级 Registry/Hub 证书（私有证书的话） Deployment 部署 pod Service 部署 Ingress 配置 部署完成后，验证 创建 Namespace # Namespace apiVersion: v1 kind: Namespace metadata: name: registry PVC 作为存储，其实不需要 # PersistentVolumeClaim (PVC) for storage apiVersion: v1 kind: PersistentVolumeClaim metadata: name: registry-storage namespace: registry spec: accessModes: - ReadWriteOnce resources: requests: storage: 20Gi # 根据缓存需求调整大小 storageClassName: local-path # 替换为你的 StorageClass 上级 Registry/Hub 证书（私有证书的话） # ConfigMap for CA certificate apiVersion: v1 kind: ConfigMap metadata: name: registry-ca-cert namespace: registry data: hub-yiqisoft-cert.pem: | # 替换为你的证书内容，或通过 kubectl create configmap 上传 -----BEGIN CERTIFICATE----- MIIDNDCCAhygAwIBAgIQJ6pOclGOhjgEFTkYFs4HcTANBgkqhkiG9w0BAQsFADAU MRIwEAYDVQQDEwloYXJib3ItY2EwHhcNMjUwMzE0MTAzOTU3WhcNMjYwMzE0MTAz ... [binary] -----END CERTIFICATE----- Deployment 部署 pod 要把上级证书导入 # Deployment apiVersion: apps/v1 kind: Deployment metadata: name: registry namespace: registry spec: replicas: 1 selector: matchLabels: app: registry template: metadata: labels: app: registry spec: initContainers: - name: update-ca-certificates image: hub.yiqisoft.cn/library/registry:2 command: ["/bin/sh", "-c", "cp /etc/certs/hub-yiqisoft-cn.crt /usr/local/share/ca-certificates/ && update-ca-certificates"] volumeMounts: - name: ca-cert mountPath: /etc/certs - name: ca-cert-dir mountPath: /usr/local/share/ca-certificates - name: ca-cert-system mountPath: /etc/ssl/certs containers: - name: registry image: hub.yiqisoft.cn/library/registry:2 ports: - containerPort: 5000 volumeMounts: - name: registry-data mountPath: /var/lib/registry - name: ca-cert mountPath: /etc/certs readOnly: true - name: ca-cert-system mountPath: /etc/ssl/certs readOnly: true env: - name: REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY value: /var/lib/registry - name: REGISTRY_PROXY_REMOTEURL value: "https://hub.yiqisoft.cn" - name: REGISTRY_PROXY_USERNAME value: "admin" - name: REGISTRY_PROXY_PASSWORD value: "abcd" volumes: - name: registry-data persistentVolumeClaim: claimName: registry-storage - name: ca-cert configMap: name: registry-ca-cert items: - key: hub-yiqisoft-cert.pem path: hub-yiqisoft-cn.crt - name: ca-cert-dir emptyDir: {} - name: ca-cert-system emptyDir: {} Service 部署 # Service apiVersion: v1 kind: Service metadata: name: registry namespace: registry spec: selector: app: registry ports: - protocol: TCP port: 5000 targetPort: 5000 type: ClusterIP Ingress 配置 自定义证书 openssl req -x509 -newkey rsa:4096 -keyout hub-yiqisoft.key -out hub-yiqisoft.crt -days 3650 -nodes -subj "/CN=hub.yiqisoft.cn" -addext "subjectAltName=DNS:hub.yiqisoft.cn" 创建 Secret kubectl -n default create secret tls hub-tls-secret --cert=hub-yiqisoft.crt --key=hub-yiqisoft.key Ingress 需要绑定 Secret # Ingress (可选，用于外部访问) apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: registry-ingress namespace: registry annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: rules: - host: registry.yiqisoft.cn # 替换为你的域名 http: paths: - path: / pathType: Prefix backend: service: name: registry port: number: 5000 tls: - hosts: - hub.yiqisoft.cn secretName: hub-tls-secret 部署完成后，验证 openssl s_client -showcerts -connect hub.yiqisoft.cn:443 </dev/null 2>/dev/null | openssl x509 -text -noout
C 语言应用开发中的注意事项 1.全局变量 2.源代码文件夹中 .c 模块文件的划分 3.多线程编程注意的问题 线程安全和互斥锁 线程间通信 4.深浅拷贝问题 总结 C 语言应用开发中的注意事项 C 语言作为一门经典的编程语言，广泛应用于系统编程、嵌入式开发和高性能计算等领域。然而，在进行 C 语言应用开发时，开发者需要关注一系列问题，以确保代码的正确性、性能和可维护性。 笔者在基于 C 语言开发 EdgeX Foundry 设备服务实践之中，遇到了一些问题，如全局变量的使用、.c 模块文件的划分、多线程编程注意事项、深浅拷贝问题以及编译链接时的多重定义问题。本文将对其展开讨论，并提供具体的代码示例和实用建议。 1.全局变量 全局变量是在函数外部定义的变量，其作用域从定义处开始到文件结束。它们在程序整个生命周期内存在，通常不会被轻易时放掉。可被程序中的任何函数访问。它能够方便多个函数共享数据，并减少函数参数传递的开销，但同时也不利于模块化编程和代码重用，增加调试难度。 以下展示的 global_pp 全局变量，在 EdgeX Foundry 设备服务中用于保存设备协议的详细信息，为部分关键代码： #include "device_openvino.h" #include "circular_buffer.h" /* protocols */ typedef struct { char *input_model; char *input_image_path; /* Uri */ char *device_name; /* CPU, GPU[0,1,...], NPU */ char *Score; } protocol_properties; protocol_properties *global_pp; // malloc a protocol_properties void func() { if (!global_pp) { iot_log_debug(driver->lc, "malloc a global_pp (protocol_properties)"); global_pp = (protocol_properties *)malloc(sizeof(protocol_properties)); global_pp->input_model = NULL; global_pp->input_image_path = NULL; global_pp->device_name = NULL; global_pp->Score = NULL; } } int main() { func(); return 0; } 注意事项： 尽量减少全局变量的使用，优先考虑局部变量或函数参数。 使用有意义的命名（如 g_config），避免冲突。 在多线程环境中，使用锁机制确保线程安全。 2.源代码文件夹中 .c 模块文件的划分 模块化编程将程序分解为独立的模块，每个模块负责特定功能。这种方法能提高代码的可读性、可维护性和可重用性，同时便于团队协作开发。划分 .c 文件的指导思想有以下几点： 每个 .c 文件应包含一组相关功能的实现，例如字符串处理或网络通信。 避免单个 .c 文件过大，建议控制在 1000 行以内。 使用头文件 (.h) 声明对外接口，.c 文件实现具体逻辑。 头文件用于声明函数、宏、类型和全局变量，供其他模块使用。 以下是 EdgeX Foundry 设备服务中环形 buffer 缓冲区的模块设计： //circular_buffer.h #pragma once #include <stddef.h> // 包含 size_t 的定义 // Circular buffer structure struct circular_buffer { struct infer_result* buffer; // storing data size_t capacity; // Maximum buffer capacity size_t size; // The current number of stored elements size_t head; // Head index (write location) size_t tail; // Tail index (read position) }; struct circular_buffer* create_circular_buffer(size_t capacity); ... //circular_buffer.c #include "circular_buffer.h" #include <stdlib.h> #include "edgex_common.h" // Initialize circular buffer struct circular_buffer* create_circular_buffer(size_t capacity) { //malloc a circular_buffer struct circular_buffer* cb = (struct circular_buffer*)malloc(sizeof(struct circular_buffer)); if (cb == NULL) { return NULL; } cb->buffer = (struct infer_result*)malloc(sizeof(struct infer_result) * capacity); if (cb->buffer == NULL) { free(cb); return NULL; } //Init cb->capacity = capacity; cb->size = 0; cb->head = 0; cb->tail = 0; return cb; } 要点： 使用 #ifndef 和 #define 或 #pragma once 防止头文件重复包含。 头文件仅声明，不定义变量或函数。 保持头文件简洁，避免引入不必要的依赖。 3.多线程编程注意的问题 线程安全和互斥锁 在多线程程序中，多个线程可能同时访问共享资源，导致数据不一致。互斥锁（mutex）可用于保护临界区，确保同一时间只有一个线程操作共享数据。在 EdgeX Foundry 设备服务 SDK 设计中，服务实例结构体包含有互斥锁指针，在设备服务初始化时一并初始化 mutex 以便于在整个设备服务声明周期中进行资源同步管理： /** * @brief Structure representing an OpenVINO driver instance for managing inference operations. */ typedef struct openvino_driver { iot_logger_t *lc; pthread_mutex_t mutex; // for synchronization pthread_t inference_thread; // 推理线程句柄 bool stop_inference; // 控制推理线程停止的标志 } openvino_driver; viod *openvino_inference_thread (){ ... // Thread-safe storage of results pthread_mutex_lock(&driver->mutex); push_result(global_cb, *results); pthread_mutex_unlock(&driver->mutex); } 线程间通信 线程间可通过共享内存、信号量或消息队列通信。例如，使用条件变量实现生产者-消费者模型。在 EdgeX Foundry 设备服务的后续开发中也会频繁出现。 4.深浅拷贝问题 浅拷贝： 只复制基本数据类型和指针，指针指向的内存不复制。 深拷贝： 复制所有数据，包括指针指向的内存。 在 EdgeX Foundry 设备服务开发中，协议资源 global_pp; 在 openvino_create_addr 中由深拷贝赋值，以此避免global_pp;在 SDK 回调函数中被错误释放资源： /** * @brief Creates an address structure for the OpenVINO device service. * @details This callback function is used by the EdgeX Foundry framework to create and populate a protocol_properties * structure with OpenVINO-specific configuration data extracted from the provided protocols. * @param impl Pointer to the implementation-specific data (cast to openvino_driver). * @param protocols Pointer to the protocol data containing OpenVINO configuration. * @param exception Pointer to store any exception data (not used in this implementation). * @return devsdk_address_t A pointer to the populated protocol_properties structure, cast to the EdgeX address type. */ static devsdk_address_t openvino_create_addr(void *impl, const devsdk_protocols *protocols, iot_data_t **exception) { ... const iot_data_t *props = devsdk_protocols_properties(protocols, "Openvino"); //从设备获取协议资源 if (props) { result = iot_data_string_map_get_string(props, "Uri"); pp->input_image_path = result; result = iot_data_string_map_get_string(props, "model"); pp->input_model = result; result = iot_data_string_map_get_string(props, "CPU_GPU"); pp->device_name = result; result = iot_data_string_map_get_string(props, "Score"); pp->Score = result; } // Allocate and populate new global protocol properties // 深拷贝 global_pp->input_model = malloc(strlen(pp->input_model) + 1); strcpy(global_pp->input_model,pp->input_model); global_pp->input_image_path = malloc(strlen(pp->input_image_path) + 1); strcpy(global_pp->input_image_path,pp->input_image_path); global_pp->device_name = malloc(strlen(pp->device_name) + 1); strcpy(global_pp->device_name,pp->device_name); global_pp->Score = malloc(strlen(pp->Score) + 1); strcpy(global_pp->Score,pp->Score); return (devsdk_address_t)pp; } 深拷贝的原因： 对象包含动态分配的内存。 需要独立副本，避免共享内存导致的问题。 总结 在 C 语言应用开发中，开发者需要综合考虑全局变量的合理使用、模块化设计的规范性、多线程编程的线程安全性、深浅拷贝的适用场景以及编译链接的正确性。 通过谨慎使用全局变量、合理划分模块、确保线程安全、正确处理拷贝和避免多重定义问题，可以编写出健壮、高效且易于维护的 C 语言程序。
仅仅配置 HTTPS 自动跳转 HTTP 到 HTTPS 设置 ingress 优先级 仅仅配置 HTTPS 这里的值只填写 websecure 即可。打开 ingress 映射 http 时，就显示 404apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: {{ .Values.fullnameOverride }}-ingress annotations: traefik.ingress.kubernetes.io/router.entrypoints: "websecure" 自动跳转 HTTP 到 HTTPS 增加这一条即可，适合那种更智能的跳转需求。metadata: annotations: traefik.ingress.kubernetes.io/router.middlewares: "redirect-https@kubernetescrd" 设置 ingress 优先级 优先级设置，数字越大，优先级越高metadata: annotations: traefik.ingress.kubernetes.io/router.priority: "1000"
EdgeX Foundry 开发设备服务的几个关键点 1.设备服务工作流 2.设备服务示例模板 2.1 SDK 相关工作流 2.2 template.c 的细节 3.删除设备的处理逻辑 4.增加设备 4.1 增加设备的方式 4.2 增加设备的处理逻辑 总结 EdgeX Foundry 开发设备服务的几个关键点 EdgeX Foundry 设备服务是与传感器/设备或物联网对象 (“物”) 交互的边缘连接器，它需要与各种设备交互，将数据传输到边缘计算系统，并根据需求执行控制命令。EdgeX Foundry 通过其模块化设计和丰富的工具集，使得设备服务的开发变得更加简单高效。我们还可以使用设备服务 SDK 来创建您自己的 EdgeX 设备服务，本文重点介绍使用 C-SDK 开发设备服务源代码的几个必须明确的关键点 。 1.设备服务工作流 以 设备服务 template.c 为例，作为一个虚拟设备服务，它可以将数据发送到核心数据微服务，用户可以通过命令和控制微服务发送命令并获得响应，虚拟设备服务的这些功能在执行功能或性能测试时不需要任何实际设备就可以使用。 注意点： 虚拟设备服务对核心数据和元数据微服务具有依赖性，因为初始化过程需要检查或注册配置文件，设备和值描述符，因此，核心数据和元数据服务必须在设备服务初始化之前完全启动。 服务启动时，服务会通过从 YAML 文件加载“值描述符”,“设备服务”,“设备描述”来初始化，并登记在元数据微服务中，YAML文件默认存在，也可以自行创建。虚拟设备服务为元数据微服务提供回调API来管理设备实例。 2.设备服务示例模板 2.1 SDK 相关工作流 EdgeX Foundry 的 C-SDK 提供了一个设备服务模板（位于 examples/template.c 强烈推荐先看一遍代码），它提供了一套规范化的开发示例，包括自定义回调函数、设备服务启动关闭、内存开辟释放等，是我们初次接触设备服务开发的良好开端。设备服务工作流在上一节有了一个大致的介绍，结合官方的指导文档 servicewritersguide.md (强烈推荐先阅读一遍)，这里进一步描述设备服务内部 SDK 相关的工作流，展示如下： 从根本上说，设备服务由多个回调组成，上图中的 SDK 内部工作流均在一系列回调函数中作处理。一个设备服务只有一个设备服务实例结构体指针 Implementation 每次调用回调时，该指针都会被传递，如初始化回调函数的第一个参数 (template_driver *) impl。 /* --- template.c ---- */ /* Initialize performs protocol-specific initialization for the device * service. */ static bool template_init ( void *impl, struct iot_logger_t *lc, const iot_data_t *config ) { template_driver *driver = (template_driver *) impl; iot_log_debug (lc, "Template Init."); return true; } 2.2 template.c 的细节 一个设备服务必须要自定义好的回调函数如下：每一个回调函数的实现正如上面提到的 template_init 那样。 /* Device Callbacks */ devsdk_callbacks *templateImpls = devsdk_callbacks_init ( template_init, template_get_handler, template_put_handler, template_stop, template_create_addr, template_free_addr, template_create_resource_attr, template_free_resource_attr ); 同时设备服务 SDK 还提供了一些非必要的回调函数，以便于开发者实现更灵活的需求，在 servicewritersguide.md 的 Optional handlers 部分有提到。举一个例子，一个设备服务实例可以实现 device_added、device_updated 和 device_removed 处理程序，然后它将收到服务管理的设备范围的更改通知。如果需要特殊的初始化/关闭操作来优化设备的使用，这可能会有所帮助。只需要将该函数实现，并注册进设备服务的回调初始化函数即可： //device_added 的具体实现 static void device_added( void *impl, const char *devname, const devsdk_protocols *protocols, const devsdk_device_resources *resources, bool adminEnabled) { openvino_driver *driver = (openvino_driver *)impl; iot_log_info(driver->lc, "New device added: %s", devname); } ... //主函数内的回调初始化 devsdk_callbacks *templateImpls = devsdk_callbacks_init ( template_init, template_get_handler, template_put_handler, template_stop, template_create_addr, template_free_addr, template_create_resource_attr, template_free_resource_attr ); //注册自定义的设备服务回调 devsdk_callbacks_set_listeners(templateImpls, device_added, NULL, NULL); 3.删除设备的处理逻辑 每删除一个设备，一定依次进入的回调函数是：template_free_resource_attr 和 template_free_addr 。 如果自定义回调 device_removed 已经初始化，则会优先进入该回调，然后再进入上面两个默认的释放内存处理回调之中。 4.增加设备 4.1 增加设备的方式 通过 EdgeX Foundry 官方 UI 界面（如下图所示）增加一个具体的设备，通过界面可视化引导，很容易完成以下步骤： SelectDeviceService SelectDeviceProfile DevicePrimary CreateAutoEvent CreateDeviceProtocol 注意点：CreateDeviceProtocol 为必须设置项，且需要跟自己的设备服务所规定的协议保持一致。 通过 API 控制命令，直接向 core-metadata 发送 POST 请求，即可一行命令生成新设备，如下所示： curl http://localhost:59881/api/v3/device -H "Content-Type: application/json" -X POST \ -d '[ { "apiVersion" : "v3", "device": { "name": "Device1", "description": "An Example Device", "adminState": "UNLOCKED", "operatingState": "UP", "serviceName": "device-template", "profileName": "template", "protocols": { "Others": { "key1": "xxxx", "key2": "yyyy", } } } } ]' 4.2 增加设备的处理逻辑 每增加一个设备，会先调用自定义回调 device_add （如果已经初始化） 然后在第一次获取设备资源，也就是 GET 请求时，会依次调用 template_create_resource_attr 、 template_create_addr 和 template_get_handler。 以后的 GET 请求只会直接调用 template_get_handler。 总结 本文深入探讨了使用 EdgeX Foundry C-SDK 开发设备服务的几个关键点，为开发者提供了一个清晰的指导框架。通过分析设备服务的工作流、模板代码的细节以及设备增删的处理逻辑，我们可以更高效地构建自定义设备服务。 首先，设备服务的工作流强调了其与核心数据和元数据微服务的依赖性，以及通过 YAML 文件初始化配置的重要性。其次，C-SDK 的模板（template.c）提供了一个规范化的开发起点，包含必备回调函数（如 init、get、put、stop）和可选回调（如 device_added），开发者可根据需求灵活扩展。再次，删除设备的逻辑涉及内存释放回调（如 template_free_addr），而增加设备则通过 UI 或 API 触发，并涉及初始化和资源获取的回调链。 总的来说，掌握这些关键点——理解工作流、熟悉模板结构、明确设备管理的回调逻辑——能够帮助开发者快速上手 EdgeX Foundry 设备服务开发，无论是构建虚拟服务还是连接真实设备。
EdgeX Foundry 在 Docker 容器微服务下的相关使用技巧 1.Device Services 注册问题 2.数据库服务的操作工具 3.docker-compose.yml 用法 4.Docker Network 容器互联 总结 EdgeX Foundry 在 Docker 容器微服务下的相关使用技巧 EdgeX Foundry 是一个开源的边缘计算框架，广泛用于物联网设备的开发与管理。它高度依赖 Docker 来实现服务的容器化部署。本文将分享一些在 Docker 容器中使用 EdgeX Foundry 的实用技巧，包括 Device Services 的注册、数据库服务操作、docker-compose.yml 的用法以及网络管理等方面的经验总结。希望能为开发者提供参考。 1.Device Services 注册问题 在开发或调试 EdgeX Foundry 的 Device Services 时，可能会遇到设备服务配置残留或配置未正确加载的问题，默认情况下在官方 UI 界面（如下图所示）中没有删除具体一个 Device Service 的接口。 这会导致我们在加载 修改后的设备服务(关键是 /res/configration.yml 文件)时很麻烦，Device Service 的配置信息是存放在官方数据库服务容器 postgres:16.3 中的，最简单干脆的清理办法是清除所有Docker 卷： 清理容器和卷 # 删除所有已停止和运行中的容器 docker rm -f $(docker ps -a -q) # 删除所有未使用的 Docker 卷 docker volume rm -f $(docker volume ls -q) 启动 EdgeX 服务 假设你已经下载了 EdgeX 的 edgex-compose 仓库，可以通过以下步骤重新启动服务，此时获得了全新的一套 EdgeX 微服务： # 进入 compose-builder 目录 cd /home/ubuntu/edgex/edgex-compose/compose-builder/ # 后台启动所有服务 docker compose up -d root@ubuntu:/home/ubuntu/edgex/edgex-compose/compose-builder# docker compose up -d [+] Running 17/17 ✔ Volume "edgex_db-data" Created 0.0s ✔ Volume "edgex_kuiper-data" Created 0.0s ... ✔ Container edgex-support-scheduler Started 2.4s ✔ Container edgex-app-rules-engine Started 2.6s ✔ Container edgex-core-command Started 2.6s 注意： 在执行清理命令前，建议检查是否有重要数据未备份。 如果遇到权限问题，可使用 sudo 提升权限。 2.数据库服务的操作工具 EdgeX 的 Compose 文件仓库会为我们生成数据库服务容器如下： ID IMAGE COMMAND PORTS NAMES ... 8xxx7a postgres:16.3-alpine3.20 "docker-entrypoint.s…" 127.0.0.1:5432->5432/tcp edgex-postgres ... 为了方便查看数据库内部一些关键数据表的状态和信息，建议把 Postgres 端口映射到本地，用 SQL 工具（推荐 DBeaver）连接上，进行增删改查 : 注意： 首次使用需要登陆数据库，在 docker-compose.yml 文件中有数据库用户名和密码： database: container_name: edgex-postgres environment: POSTGRES_DB: edgex_db POSTGRES_PASSWORD: postgres ... 3.docker-compose.yml 用法 Compose 是用于定义和运行多容器 Docker 应用程序的工具。通过 Compose，您可以使用 YML 文件来配置应用程序需要的所有服务。然后，使用一个命令，就可以从 YML 文件配置中创建并启动所有服务。 EdgeX Foundry 部署服务的核心配置文件正是 docker-compose.yml , 同时也是自定义设备服务的重要部署工具。以下是一些常用操作和注意事项 : （在 docker-compose.yml 同级文件夹下运行） 停止并删除所有服务容器： docker compose down 查看运行中的容器： docker compose ps 后台启动服务： docker compose up -d 重启某一个具体的服务并查看日志： docker compose restart edgex-device-openvino docker logs -f edgex-device-openvino 可以添加自定义设备服务在现有的 docker-compose.yml 中，如添加 s7 设备服务,也可调整现有的端口映射或服务依赖关系。在调整 docker-compose.yml 时，也可能需要更改文件权限： chmod +w docker-compose.yml vim docker-compose.yml ... bind: create_host_path: true device-s7: command: - -cp=keeper.http://edgex-core-keeper:59890 - --registry ... read_only: true bind: create_host_path: true mqtt-broker: ... 常见问题： 命令未生效：检查是否在正确的目录下运行（包含 docker-compose.yml 的目录）。 服务未启动：运行 docker compose ps 检查状态，可能需要查看日志定位问题。 4.Docker Network 容器互联 Docker 有一个连接系统允许将多个容器连接在一起，共享连接信息。EdgeX Foundry 的服务之间通过 Docker 网络通信，默认网络名为 edgex-network。以下是网络相关的操作技巧： 列出所有 Docker 网络并过滤出 EdgeX 相关网络： docker network ls | grep edgex-network # docker network ls | grep edgex-network 399e879a892b edgex_edgex-network bridge local 检查容器是否加入正确网络： docker inspect edgex-device-openvino | grep Network 如果服务无法通信，可以尝试重建网络： docker network rm edgex-network docker compose up -d # 自动重新创建网络 注意：删除网络前，确保没有正在运行的服务依赖它。 总结 通过本文，我们介绍了在 EdgeX Foundry 中使用 Docker 的一些实用技巧，包括清理容器、操作数据库、管理 docker-compose.yml 文件以及网络排查。这些方法可以帮助开发者更高效地部署和调试 EdgeX 服务。Docker 的灵活性为 EdgeX 提供了强大的支持，但在实际使用中仍需注意资源管理和服务依赖关系。