openshift 4.3 Istio的搭建


openshift 4.3 Istio的搭建

本文檔覆蓋了官方文檔的Setup的所有章節

安裝Istio

本次安裝的Istio版本為1.7.0,環境為openshift 4.3

注:不建議使用openshift 1.11(即kubernetes 3.11)安裝istio,可能會出現如下兼容性問題,參見此issue

must only have "properties", "required" or "description" at the root if the status subresource is enabled

openshift安裝Istio

istio的安裝涉及到兩個文件:profile和manifest。前者用於控制組件的安裝和組件的參數,profile配置文件所在的目錄為manifests/profiles;后者為安裝所使用的yaml文件,如service,deployment等,會用到profile提供的參數,manifest配置文件所在的目錄為manifests/charts。因此可以通過兩種方式安裝istio,一種是通過profile進行安裝,istio默認使用這種方式,如:

$ istioctl install --set profile=demo

第二種是通過導出的manifest進行安裝(不推薦,見下),如:

$ kubectl apply -f $HOME/generated-manifest.yaml

參考不同平台可以參考對應的SetUp。在openshift下面部署istio需要注意版本

OpenShift 4.1 and above use nftables, which is incompatible with the Istio proxy-init container. Make sure to use CNI instead.

首先創建istio-system命名空間

$ cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Namespace
metadata:
  name: istio-system
  labels:
    istio-injection: disabled
EOF

允許istio的serviceaccount使用UID為0的用戶,使用的命名空間為istio-system

$ oc adm policy add-scc-to-group anyuid system:serviceaccounts:istio-system

istio默認會注入一個名為istio-initinitContainer,用於將pod的網絡流量導向istio的sidecar proxy,該initContainer需要用到完整的NET_ADMINNET_RAW capabilities來配置網絡,由此可能造成安全問題,使用istio CNI插件可以替換istio-init,且無需提升kubernetes RBAC權限,區別見下:

# with istio-cni
          capabilities:
            drop:
            - ALL
          privileged: false
          readOnlyRootFilesystem: true
          runAsGroup: 1337
          runAsNonRoot: true
          runAsUser: 1337
          
# without istio-cni
          capabilities:
            add:
            - NET_ADMIN
            - NET_RAW
            drop:
            - ALL
          privileged: false
          readOnlyRootFilesystem: false
          runAsGroup: 0
          runAsNonRoot: false
          runAsUser: 0

由於openshift 4.1以上版本不再使用iptables,轉而使用nftables,因此需要安裝istio CNI插件,否則在sidecar注入時會出現如下istio iptables-restore: unable to initialize table 'nat'的錯誤,即無法執行iptables-resotre命令。

執行如下命令安裝istio-cni並使用default類型的profile(見下)安裝istio,具體參數含義參見官方文檔

cat <<'EOF' > cni-annotations.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  components:
    cni:
      enabled: true
      namespace: kube-system
  values:
    cni:
      excludeNamespaces:
       - istio-system
       - kube-system
      chained: false
      cniBinDir: /var/lib/cni/bin
      cniConfDir: /etc/cni/multus/net.d
      cniConfFileName: istio-cni.conf
    sidecarInjectorWebhook:
      injectedAnnotations:
        "k8s.v1.cni.cncf.io/networks": istio-cni
EOF
$ istioctl manifest install -f cni-annotations.yaml --set meshConfig.accessLogFile="/dev/stdout"

安裝結果如下:

# oc get pod -n istio-system
NAME                                    READY   STATUS    RESTARTS   AGE
istio-ingressgateway-746548c687-wjc6j   1/1     Running   0          95s
istiod-6c5f6f55ff-htrss                 1/1     Running   0          2m31s
# oc get pod -nkube-system
NAME                   READY   STATUS    RESTARTS   AGE
istio-cni-node-44tx7   2/2     Running   0          112s
istio-cni-node-92qqk   2/2     Running   0          112s
istio-cni-node-gdg6g   2/2     Running   0          112s
istio-cni-node-pwtjh   2/2     Running   0          112s
istio-cni-node-z5p2z   2/2     Running   0          112s

為ingress gateway暴露router:

$ oc -n istio-system expose svc/istio-ingressgateway --port=http2

至此istio的基本組件已經安裝完畢,可以使用如下方式導出本次安裝的profile

$ istioctl profile dump -f cni-annotations.yaml > generated-profile.yaml

使用如下命令導出安裝istio的manefest的內容

$ istioctl manifest generate -f cni-annotations.yaml  > generated-manifest.yaml

校驗安裝結果

$ istioctl verify-install -f generated-manifest.yaml

istio會使用UID為1337的用戶將sidecar注入到應用中,openshift默認不允許使用該用戶,執行如下命令進行授權。target-namespace為應用所在的命名空間。

$ oc adm policy add-scc-to-group privileged system:serviceaccounts:<target-namespace>
$ oc adm policy add-scc-to-group anyuid system:serviceaccounts:<target-namespace>

當清理應用pod后,需要刪除添加的權限

$ oc adm policy remove-scc-from-group privileged system:serviceaccounts:<target-namespace>
$ oc adm policy remove-scc-from-group anyuid system:serviceaccounts:<target-namespace>

openshift下使用multus管理CNI,它需要在應用的命名空間中部署NetworkAttachmentDefinition來使用istio-cni插件,使用如下命令創建NetworkAttachmentDefinitiontarget-namespace替換為實際應用所在的命名空間。

$ cat <<EOF | oc -n <target-namespace> create -f -
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
  name: istio-cni
EOF

移除應用后,使用如下方式移除NetworkAttachmentDefinition

$ oc -n <target-namespace> delete network-attachment-definition istio-cni
更新istio配置

例如可以使用如下方式卸載已經安裝的第三方工具Prometheus,注意必須帶上文件cni-annotations.yaml,否則會使用默認的profile重新配置istio,這樣會導致刪除istio-cni。如果組件的配置沒有改變,則不會重新該組件的pod

$ istioctl manifest apply -f cni-annotations.yaml --set addonComponents.prometheus.enabled=false
openshif卸載istio
$ istioctl manifest generate -f cni-annotations.yaml | kubectl delete -f -

標准安裝istio

istioctl 使用內置的charts生成manifest,這些charts位於目錄manifests/charts

# ll
total 76
drwxr-xr-x. 5 root root  4096 Aug 22 03:00 base
drwxr-xr-x. 4 root root  4096 Aug 22 03:00 gateways
-rw-r--r--. 1 root root 16888 Aug 22 03:00 global.yaml
drwxr-xr-x. 3 root root  4096 Aug 22 03:00 istio-cni
drwxr-xr-x. 3 root root  4096 Aug 22 03:00 istio-control
drwxr-xr-x. 3 root root  4096 Aug 22 03:00 istiocoredns
drwxr-xr-x. 4 root root  4096 Aug 22 03:00 istiod-remote
drwxr-xr-x. 4 root root  4096 Aug 22 03:00 istio-operator
drwxr-xr-x. 3 root root  4096 Aug 22 03:00 istio-policy
drwxr-xr-x. 8 root root  4096 Aug 22 03:00 istio-telemetry
-rw-r--r--. 1 root root  2408 Aug 22 03:00 README-helm3.md
-rw-r--r--. 1 root root  8629 Aug 22 03:00 README.md
-rw-r--r--. 1 root root  2661 Aug 22 03:00 UPDATING-CHARTS.md

直接執行如下命令即可安裝官方默認配置的istio。

$ istioctl install --set profile=default

istio默認支持如下6種profile

# istioctl profile list
Istio configuration profiles:
    demo
    empty
    minimal
    preview
    remote
    default

安裝的組件的區別如下

default demo minimal remote
Core components
istio-egressgateway X
istio-ingressgateway X X
istiod X X X

使用如下方式可以查看某個profile的配置信息,profile類型helm的values.yaml,用於給部署用的yaml提供配置參數。每個組件包含兩部分內容:components下的組件以及組件的參數value

istio提倡使用 IstioOperator API 進行定制化配置。

$ istioctl profile dump default

使用如下方式可以查看某個組件的配置:

$ istioctl profile dump --config-path components.pilot default

在安裝前可以使用如下方式導出需要安裝的所有yaml信息 ,包括CRD,deployment,service等

$ istioctl manifest generate --set profile=default --set hub=docker-local.com/openshift4 > $HOME/generated-manifest.yaml

kubectl apply可以使用manifest generate的輸出來安裝istio。但這種方式可能無法具有與istioctl install相同的資源依賴,且不會在istio發行版中進行測試。

在進行確認或修改之后可以使用apply命令執行安裝(不推薦)

$ kubectl apply -f $HOME/generated-manifest.yaml

如果嘗試使用istioctl manifest generate進行istio的安裝和管理,請注意以下事項:

  1. 必須手動創建istio命名空間(默認為istio-system)
  2. 雖然istioctl install會自動探測kubernetes上下文中的環境設置,但manifest generate並不能脫機運行(即獨立於istio安裝環境運行),這可能導致非預期的結果。特別地,在kubernetes環境不支持第三方service account token的前提下,用戶必須遵守這些步驟
  3. 當依賴的資源不正確時,對生成的manifest執行kubectl apply時可能會顯示錯誤。
  4. 當配置變更后(如移除一個gateway)istioctl install會自動清理所有的資源。當配合kubectl執行istio manifest generate時需要手動移除這些資源。

使用如下方式校驗安裝結果

$ istioctl verify-install -f $HOME/generated-manifest.yaml

自定義配置:

  • 使用--set選項進行設置,如下面用於設置profile中的global.controlPlaneSecurityEnabled為true

    $ istioctl install --set values.global.controlPlaneSecurityEnabled=true
    
  • 如果修改的參數比較多,可以使用yaml文件統一進行配置,實際使用 IstioOperator API進行profile的修改

    $ istioctl install -f samples/operator/pilot-k8s.yaml
    

istio的核心組件定義在 IstioOperator API的components下面:

Field Type Description Required
base BaseComponentSpec No
pilot ComponentSpec No
policy ComponentSpec No
telemetry ComponentSpec No
cni ComponentSpec No
istiodRemote ComponentSpec No
ingressGateways GatewaySpec[] No
egressGateways GatewaySpec[] No

第三方插件可以在IstioOperator API 的addonComponents下指定,如:

apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  components:
    telemetry:
      enabled: false

每個組件都有一個KubernetesResourceSpec,用於設置如下k8s屬性

  1. Resources
  2. Readiness probes
  3. Replica count
  4. HorizontalPodAutoscaler
  5. PodDisruptionBudget
  6. Pod annotations
  7. Service annotations
  8. ImagePullPolicy
  9. Priority class name
  10. Node selector
  11. Affinity and anti-affinity
  12. Service
  13. Toleration
  14. Strategy
  15. Env
標准卸載istio

使用如下方式可以移除kubernetes中所有istio組件

$ istioctl x uninstall --purge

使用如下方式可以移除某個特定的istio控制面

$ istioctl x uninstall <your original installation options>

$ istioctl manifest generate <your original installation options> | kubectl delete -f -
自定義外部charts和profiles

istioctl install, manifest generateprofile 命令都可以在charts和profiles中使用下面的資源:

  • charts內置的資源。如果沒有設置--manifests選項,則為默認配置。charts內置的資源與Istio發行版.tgzmanifests/目錄中資源相同.
  • 本地文件系統的charts。如istioctl install --manifests istio-1.7.0/manifests
  • Github上的charts,如istioctl install --manifests https://github.com/istio/istio/releases/download/1.7.0/istio-1.7.0-linux-arm64.tar.gz

本地文件系統charts和profiles可以通過編輯manifests/目錄下的文件實現自定義。建議拷貝manifests命令並對其進行修改。但必須保留manifests目錄中的內容布局。

可以通過編輯manifests/profiles/中的profile來創建新的profile文件。istioctl會掃描profiles子目錄以及可以被IstioOperatorSpec profile字段引用的所有profile名稱。在用戶配置生效前會使用內置的profile作為默認的profile YAML。例如,可以創建一個名為custom1.yaml的文件,該文件自定義了default profile中的某些配置,然后應用該用戶自定義的配置:

$ istioctl manifest generate --manifests mycharts/ --set profile=custom1 -f path-to-user-overlay.yaml

這種情況下, custom1.yamluser-overlay.yaml文件會覆蓋default.yaml文件。

一般來說,不需要創建新的配置文件,因為可以通過傳遞多個文件來覆蓋獲得類似的結果。例如,如下命令等同於傳入了兩個用戶覆蓋的文件:

$ istioctl manifest generate --manifests mycharts/ -f manifests/profiles/custom1.yaml -f path-to-user-overlay.yaml

只有在需要通過istooperatorspec按名稱引用profile文件時,才需要創建自定義profile文件。

更新Istio

金絲雀升級

revision安裝方式支持同時部署多個版本的istio,在升級時可以將流量逐步轉移到新版本的istio上。每個修訂revision都是一個完整的istio控制面,具有獨立的Deployment, Service等。

控制面升級

使用如下方式可以安裝一個名為canary的istio修訂版本。下面方式默認會使用default profile創建一套新的istio,如果新的istio和老的istio有組件重疊,則可能導致組件重建。從下面的AGE字段可以看出,新的Prometheus覆蓋了老的Prometheusingressgateway也一樣,因此最好的方式是指定文件,參見istio install

$ istioctl install --set revision=canary

注:revision中不能包含.

命令執行成功后,可以看到存在2個控制面,每個控制面有各自的DeploymentService等。

$ kubectl get pods -n istio-system -l app=istiod
NAME                                    READY   STATUS    RESTARTS   AGE
istiod-786779888b-p9s5n                 1/1     Running   0          114m
istiod-canary-6956db645c-vwhsk          1/1     Running   0          1m

$ kubectl get svc -n istio-system -l app=istiod
NAME            TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)                                                AGE
istiod          ClusterIP   10.32.5.247   <none>        15010/TCP,15012/TCP,443/TCP,15014/TCP                  33d
istiod-canary   ClusterIP   10.32.6.58    <none>        15010/TCP,15012/TCP,443/TCP,15014/TCP,53/UDP,853/TCP   12m

sidecar注入配置也有2套

$ kubectl get mutatingwebhookconfigurations
NAME                            CREATED AT
istio-sidecar-injector          2020-03-26T07:09:21Z
istio-sidecar-injector-canary   2020-04-28T19:03:26Z
卸載老的控制面

使用如下命令卸載revision1-6-5的控制面

$ istioctl x uninstall --revision 1-6-5

如果沒有revision標簽,則使用原始安裝它的選項進行卸載

$ istioctl x uninstall -f manifests/profiles/default.yaml
數據面升級

創建新的istio修訂版本並不會影響現有的代理。為了升級代理,需要將代理的配置指向新的控制面。通過命名空間標簽istio.io/rev設置控制sidecar注入的控制面。

下面操作升級了test-ns命名空間,移除標簽istio-injection,並增加標簽istio.io/rev,指向新的istio canary。注意必須移除標簽istio-injection,否則istio會優先處理istio-injection(原因是為了向后兼容)

$ kubectl label namespace test-ns istio-injection- istio.io/rev=canary

在升級命名空間后,需要重啟pod來觸發sidecar的注入,如下使用滾動升級

$ kubectl rollout restart deployment -n test-ns

通過如下方式可以查看使用canaryistio修訂版的pod

$ kubectl get pods -n test-ns -l istio.io/rev=canary

為了驗證test-ns命名空間中的新pod使用了istiod-canary服務,可以選擇一個pod使用如下命令進行驗證。從輸出中可以看到其使用了istiod-canary控制面

$ istioctl proxy-config endpoints ${pod_name}.test-ns --cluster xds-grpc -ojson | grep hostname
"hostname": "istiod-canary.istio-system.svc"

可以通過如下方式導出canary修訂版的配置信息

$ istioctl manifest generate --revision=canary >canary.yaml

替換升級

升級過程中可能會造成流量中斷,為了最小化影響,需要確保istio中的各個組件(除Citadel)至少有兩個副本正在運行,此外需要通過PodDistruptionBudgets保證至少有一個可用的pod。

  • 首先下載最新的istio

  • 校驗當前環境中支持的升級到的版本,如下命令會給出推薦的版本

    $ istioctl manifest versions
    
  • 確認需要升級的cluster是否正確

    $ kubectl config view
    
  • 通過如下命令執行升級,<your-custom-configuration-file>為當前版本的 IstioOperator API 配置 文件

    $ istioctl upgrade -f `<your-custom-configuration-file>`
    

    istioctl upgrade不支持--set命令

  • 在更新完畢之后,需要手動重啟帶有istio sidecar的pod來更新istio數據面

    $ kubectl rollout restart deployment
    

sidecar注入

sidecar不能注入到kube-system或kube-public名稱空間中

sidecar不能注入使用主機網絡的吊艙中

為了使用Istio的特性,pods必須運行在istio sidecar proxy的網格中。下面介紹兩種注入istio sidecar的方式:手動注入和自動注入。

手動注入通過直接修改,如deployment的配置信息,將proxy配置注入到配置中;當應用所在的命名空間啟用自動注入時,會在pod創建時通過mutating webhook admission controller 注入proxy配置。

sidecar的(手動或自動)注入會用到istio-sidecar-injector configmap。

  • 手動注入

    當前版本手動注入時有一個問題,就是使用istio CNI之后無法將annotation k8s.v1.cni.cncf.io/networks注入(或導出)到配置文件中,導致出現如下問題,參見該issue

    in new validator: 10.80.2.222
    Listening on 127.0.0.1:15001
    Listening on 127.0.0.1:15006
    Error connecting to 127.0.0.6:15002: dial tcp 127.0.0.1:0->127.0.0.6:15002: connect: connection refused
    Error connecting to 127.0.0.6:15002: dial tcp 127.0.0.1:0->127.0.0.6:15002: connect: connection refused
    Error connecting to 127.0.0.6:15002: dial tcp 127.0.0.1:0->127.0.0.6:15002: connect: connection refused
    Error connecting to 127.0.0.6:15002: dial tcp 127.0.0.1:0->127.0.0.6:15002: connect: connection refused
    Error connecting to 127.0.0.6:15002: dial tcp 127.0.0.1:0->127.0.0.6:15002: connect: connection refused
    
    • 手動注入需要滿足一個條件,即在應用所在的命名空間中創建NetworkAttachmentDefinition

    • 一種是直接使用istio-sidecar-injector的默認配置直接注入sidecar

      $ istioctl kube-inject -f samples/sleep/sleep.yaml | kubectl apply -f -
      

      可以使用如下方式直接先導出注入sidecar的deployment,然后使用kubectl直接部署

      $ istioctl kube-inject -f samples/sleep/sleep.yaml -o sleep-injected.yaml --injectConfigMapName istio-sidecar-injector
      $ kubectl apply -f deployment-injected.yaml
      
    • 另一種是先導出istio-sidecar-injector的默認配置,可以修改后再手動注入sidecar

      導出配置:

      $ kubectl -n istio-system get configmap istio-sidecar-injector -o=jsonpath='{.data.config}' > inject-config.yaml
      $ kubectl -n istio-system get configmap istio-sidecar-injector -o=jsonpath='{.data.values}' > inject-values.yaml
      $ kubectl -n istio-system get configmap istio -o=jsonpath='{.data.mesh}' > mesh-config.yaml
      

      手動注入:

      $ istioctl kube-inject \
          --injectConfigFile inject-config.yaml \
          --meshConfigFile mesh-config.yaml \
          --valuesFile inject-values.yaml \
          --filename samples/sleep/sleep.yaml \
          | kubectl apply -f -
      
  • 自動注入

    自動注入時需要滿足兩個條件:

    • 給應用所在的命名空間打上標簽istio-injection=enabled

      $ kubectl label namespace <app-namespace> istio-injection=enabled
      
    • 與手動注入相同,需要在應用所在的命名空間中創建NetworkAttachmentDefinition,然后在該命名空間下面正常創建(或刪除並重建)pod即可自動注入sidecar。需要注意的是自動注入發生在pod層,並不體現在deployment上面,可以使用describe pod查看注入的sidecar。自動注入通過mutatingwebhookconfiguration定義了注入sidecar的規則,當前是istio-injection=enabled

      namespaceSelector:
          matchLabels:
            istio-injection: enabled
      

      可以通過如下命令修改注入的規則,修改后需要重啟已經注入sidecar的pod,使規則生效

      $ kubectl edit mutatingwebhookconfiguration istio-sidecar-injector
      

      自動注入下,sidecar inject的webhook默認是打開的,如果要禁用該webhook,可以在上面cni-annotations.yaml文件中將sidecarInjectorWebhook.enabled置為false,這樣就不會自動注入。

sidecar的注入控制

有兩種方式可以控制sidecar的注入:

  • 第一種是修改pod template spec的sidecar.istio.io/inject annotation:當該annotation為true時會進行自動注入sidecar,為false則不會注入sidecar。默認為true。這種情況需要修改應用的deployment。

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: ignored
    spec:
      template:
        metadata:
          annotations:
            sidecar.istio.io/inject: "false"
        spec:
          containers:
          - name: ignored
            image: tutum/curl
            command: ["/bin/sleep","infinity"]
    
  • 第二種是修改istio-sidecar-injector configmap,通過在neverInjectSelector數組中羅列出標簽,並禁止對匹配這些標簽的pod注入sidecar(各個匹配項的關系為OR)。如下內容中不會對具有標簽openshift.io/build.nameopenshift.io/deployer-pod-for.name的pod注入sidecar。這種方式不需要修改應用的deployment。類似地,可以使用alwaysInjectSelector對某些具有特殊標簽的pod注入sidecar

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: istio-sidecar-injector
    data:
      config: |-
        policy: enabled
        neverInjectSelector:
          - matchExpressions:
            - {key: openshift.io/build.name, operator: Exists}
          - matchExpressions:
            - {key: openshift.io/deployer-pod-for.name, operator: Exists}
        template: |-
          initContainers:
    ...
    

更多istio CNI與sidecar注入和流量重定向相關的參數參見官方文檔

卸載自動注入

使用如下方式可以卸載自動注入功能:

$ kubectl delete mutatingwebhookconfiguration istio-sidecar-injector
$ kubectl -n istio-system delete service istio-sidecar-injector
$ kubectl -n istio-system delete deployment istio-sidecar-injector
$ kubectl -n istio-system delete serviceaccount istio-sidecar-injector-service-account
$ kubectl delete clusterrole istio-sidecar-injector-istio-system
$ kubectl delete clusterrolebinding istio-sidecar-injector-admin-role-binding-istio-system

注意上面命令並不會卸載已經注入到pod的sidecar,需要通過滾動更新或直接刪除pod來生效

或者使用如下方式對某個命名空間禁用自動注入(推薦):

$ kubectl label namespace <target-namespace> istio-injection-

Istio CNI的兼容

與init容器的兼容

當使用istio CNI的時候,kubelet會按照如下步驟啟動注入sidecar的pod:

  • 使用istio CNI插件進行配置,將流量導入到pod中的istio sidecar proxy容器
  • 執行所有init容器,並成功運行結束
  • 啟動pod中的istio sidecar proxy和其他容器

由於init容器會在sidecar proxy容器之前運行,因此可能導致應用本身的init容器的通信中斷。為了避免發生這種情況,可以通過如下配置避免重定向應用的init容器的流量:

  • 設置traffic.sidecar.istio.io/excludeOutboundIPRanges annotation來禁用將流量重定向到與init容器通信的任何cidr。
  • 設置traffic.sidecar.istio.io/excludeOutboundPorts annotation來禁止將流量重定向到init容器使用的出站端口

與其他CNI插件的兼容

istio CNI插件作為CNI插件鏈中的一環,當創建或刪除一個pod時,會按照順序啟動插件鏈上的每個插件,istio CNI插件僅僅(通過pod的網絡命名空間中的iptables)將應用的pod流量重定向到注入的istio proxy sidecar容器。

istio CNI插件不會干涉配置pod網絡的基本CNI插件。

更多細節參見CNI specification reference

TIPs:

  • 不同平台下使用istio CNI執行initContainer時可能會出現istio-validation無法啟動的錯誤,這種情況下默認會導致kubelet刪除並重建pod,為了定位問題,可以將repair.deletePods配置為false,這樣就不會立即刪除pod

    apiVersion: install.istio.io/v1alpha1
    kind: IstioOperator
    spec:
      components:
        cni:
          ...
      values:
        cni:
          repair:
            enabled: true
            deletePods: false
            ...
    


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM