initContainers(初始化容器)

k8s在1.3版本的时候引入了一个初始化容器(init container)的特性,主要是用于在业务容器启动之前来启动一个或多个初始化容器,来为业务容器提供基础

容器的启动过程大概是这样的

init1 --> init2 --> ……所有的初始化容器都执行完之后,才会启动应用容器 --> app container

记住这个启动顺序,初始化容器一定是在业务容器之前启动的

我们来看一个示例

# 这个文件名是initContainer.yml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: initpod02
name: initpod02
spec:
containers:
- image: nginx
imagePullPolicy: Never
name: initpod02
resources: {}
# 注意看这里,有一个键是initContainers,不难理解,这个就是初始化容器
initContainers:
# 这个就是初始化容器使用的镜像,不是业务容器的镜像哦
- image: centos
name: initc
imagePullPolicy: Never
# 这里说的是安全上下文,我们给这个初始化容器一个超级权限
securityContext:
privileged: true
# 这里就是很重要的,初始化容器的动作,初始化容器创建出来之后他该干什么,我们就写在这个地方
command: ["ls"]
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}

我们去apply 一下这个文件,注意一下启动顺序哦,是初始化容器都跑完了才会启动业务容器,单反有一个初始化容器失败,那么整个pod就会失败

这里我们只是让他执行一下ls,肯定是成功的,我们来看看是不是

[root@master k8s]# kubectl apply -f initContainer.yml
pod/initpod02 created
[root@master k8s]# kubectl get pods
NAME READY STATUS RESTARTS AGE
initpod02 0/1 PodInitializing 0 3s
[root@master k8s]# kubectl get pods
NAME READY STATUS RESTARTS AGE
initpod02 1/1 Running 0 4s

我们可以看到,他有一个initializing的状态,然后就变成了running,说明业务容器正常运行了,那么我们将上面的文件进行修改

让他执行一个错误的命令,看看业务容器是否会启动,我们能将yaml文件修改,将初始化容器里的command改掉

apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: initpod02
name: initpod02
spec:
containers:
- image: nginx
imagePullPolicy: Never
name: initpod02
resources: {}
initContainers:
- image: centos
name: initc
imagePullPolicy: Never
securityContext:
privileged: true
# 我们将这里的命令故意写错
command: ["lssssssss"]
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}

我们来看看效果

# 首先删掉刚刚创建的pod
[root@master k8s]# kubectl delete -f initContainer.yml
pod "initpod02" deleted
# 然后我们使用新的文件重新创建
[root@master k8s]# kubectl apply -f initContainer.yml
pod/initpod02 created
[root@master k8s]# kubectl get pods
NAME READY STATUS RESTARTS AGE
initpod02 0/1 Init:CrashLoopBackOff 1 (3s ago) 4s

这个时候我们发现他报错了,restarts为1,我们来看看他的报错信息

[root@master k8s]# kubectl describe pod/initpod02
# 信息太多,我们直接看event栏
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 3m31s default-scheduler Successfully assigned default/initpod02 to node2
Normal Pulled 119s (x5 over 3m31s) kubelet Container image "centos" already present on machine
Normal Created 119s (x5 over 3m31s) kubelet Created container initc
Warning Failed 119s (x5 over 3m31s) kubelet Error: failed to create containerd task: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "lssssssss": executable file not found in $PATH: unknown
Warning BackOff 104s (x10 over 3m30s) kubelet Back-off restarting failed container initc in pod initpod02_default(2688307f-4e20-4ace-8448-f256f7ec5255)

在这里我们可以看见,有一个Error,报错信息是 lssssssss 这个命令没有在环境变量里面找到,他可能找到吗?不可能啊,因为压根就没有这个命令啊

所以初始化容器执行失败了,业务容器也不会再启动了,那么相信你肯定会有疑问,这个初始化容器也没什么用啊,应用场景是什么呢?

我们来看看这样一个yaml文件

# 我先说一下这个yaml文件的作用,首先是业务容器是nginx,初始化容器镜像使用的是centos
# 然后我们看到下面,是有定义volume的,volume的名字叫做testvolume,这个volume被业务容器挂在到了/test下,被初始化容器挂在到了/initdir下
# 初始化容器执行的命令是 echo 123 > /initdir/initfile
# 按照容器的启动顺序,我们最终应该会在业务容器里面的/test下会有一个initfile文件
# 我们来执行看看
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: initpod02
name: initpod02
spec:
containers:
- image: nginx
imagePullPolicy: Never
name: initpod02
volumeMounts:
- name: testvolume
mountPath: /test
resources: {}
initContainers:
- image: centos
name: initc
volumeMounts:
- name: testvolume
mountPath: /initdir
imagePullPolicy: Never
securityContext:
privileged: true
command: ["sh","-c","echo 123 > /initdir/initfile"]
dnsPolicy: ClusterFirst
restartPolicy: Always
volumes:
- name: testvolume
emptyDir: {}
status: {}

这个文件的作用在上面说过了,没看见的可以往上稍微翻一下,现在我们开始apply

[root@master k8s]# kubectl apply -f test.yml
pod/initpod02 created
[root@master k8s]# kubectl get pods
NAME READY STATUS RESTARTS AGE
initpod02 1/1 Running 0 4s

我们可以看见容器是正常运行的,说明初始化容器成功了,那么我们现在需要关注的是initfile到底存不存在与业务容器nginx里面的/test下

[root@master k8s]# kubectl exec -it initpod02 -- cat /test/initfile
Defaulted container "initpod02" out of: initpod02, initc (init)
123

我们可以看见,业务容器下确实存在了initfile文件,并且内容就是我们ehco的123

现在我们应该可以理解初始化容器是用来干什么的了,他可以在我们的业务容器启动之前执行一些操作,来为我们的业务容器提供一定的平台

静态pod

什么是静态pod?干什么的?带着这2个问题我们往下看

首先,我们目前的k8s集群是使用kubeadm创建的,在我们使用kubeadm init初始化集群的时候呢,他会帮我们创建一些pod,在kube-system命令空间下,包括这个命令空间,也是在集群初始化的时候被创建出来的,当我们的集群起来之后呢,我们才可以去创建pod

诶?不对啊,不是说集群起来之后才能创建pod吗,那我们集群没有起来的时候,kube-system里面的pod是怎么创建的呢,既然集群都没有起来,你凭什么可以创建pod?那不能创建pod,我们的集群怎么起来呢? 这???? 这不是世纪难题,是先有鸡还是先有蛋吗?

这个就是我们现在要了解的静态pod

假设我们现在有了一个yaml文件,那我们想要创建pod的话是不是应该使用命令kubectl apply 去让他根据这个文件创建pod啊,那有没有这样一种可能,我只要在某个目录下面存在yaml文件,那k8s集群就会自动创建pod,而不需要我们去apply这个文件呢?当然是可以的,这个就是静态pod,当文件存在,那么pod被创建,当文件不存在,那么pod就会被删除,我们不妨来做个实验

# 谨慎操作,一步步来,我们先查看kubelet的状态,这里面会显示他读取了哪个配置文件
# 在/etc/kubernetes/manifests这个目录下有一些yaml文件,仔细一点看就可以看出来,他的文件名跟kube-system里面是一样的
[root@master manifests]# kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-5bbd96d687-7h966 1/1 Running 0 21m
coredns-5bbd96d687-gg849 1/1 Running 0 21m
etcd-master 1/1 Running 0 33h
kube-apiserver-master 1/1 Running 0 33h
kube-controller-manager-master 1/1 Running 0 33h
kube-proxy-mp98s 1/1 Running 2 (4h16m ago) 33h
kube-proxy-snk8k 1/1 Running 5 (3h16m ago) 33h
kube-proxy-xmxpj 1/1 Running 2 (4h16m ago) 33h
kube-scheduler-master 1/1 Running 0 33h
metrics-server-7bf8c67888-qjqvw 1/1 Running 1 (4h16m ago) 28h
[root@master manifests]# ls
etcd.yaml kube-apiserver.yaml kube-controller-manager.yaml kube-scheduler.yaml
# 他创建出来的pod在文件名后面加上了master,那我们可以尝试一下在这里面创建一个yaml文件,看看他是不是真的会自己去创建pod
# 这里我们可以这样,我们在其他节点的这个目录下去创建yaml文件,因为master上删错了很麻烦,其他节点上没有这些文件
# 我们可以在master节点上生成yaml文件然后传到node1上
root@master k8s]# kubectl run pod01 --image=nginx --image-pull-policy=IfNotPresent --dry-run=client -o yaml > nginx.yaml
# 然后scp到node1节点上
[root@master k8s]# scp nginx.yml node1:/root
# 先传到其他目录,我们此时先查看pod
[root@master k8s]# kubectl get pods
NAME READY STATUS RESTARTS AGE
initpod02 1/1 Running 0 85m
# 可以看到这是之前的initpod,没有其他的pod了,现在我们将这个yaml文件移动到指定目录下
# 注意,现在的操作是在node1上
[root@node1 root]# cd /etc/kubernetes/manifests/
[root@node1 manifests]# cp /root/nginx.yml .
# 此时我们回到master节点
[root@master k8s]# kubectl get pods
NAME READY STATUS RESTARTS AGE
initpod02 1/1 Running 0 85m
pod01-node1 1/1 Running 0 12s

我们可以发现,我们仅仅只是就将yaml文件移动到了这个目录下,他就创建出来了pod,我们现在尝试删除这个yaml文件呢

[root@node1 manifests]# ls
nginx.yml
[root@node1 manifests]# rm nginx.yml
rm: remove regular file 'nginx.yml'? y
[root@node1 manifests]#
# 我们回到master节点查看
[root@master k8s]# kubectl get pods
NAME READY STATUS RESTARTS AGE
initpod02 1/1 Running 0 88m

发现pod已经没有了

当然,我们也可以自己手动指定他读取的目录,只需要在node节点上修改kubelet的配置文件

# 在[Service]段落里面的Environment里面加上参数 --pod-manifest-path=你想要的目录就好了
[root@master k8s]# vim /usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf
1 # Note: This dropin only works with kubeadm and kubelet v1.11+
2 [Service]
3 Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/e tc/kubernetes/kubelet.conf"
# 我是在这个Environment里加的
4 Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml --pod-manifest-path=/etc/kubernetes/test"
5 # This is a file that "kubeadm init" and "kubeadm join" generates at runtime, populating the KUBELET_KUBEADM_ARGS variable dynamically
6 EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env
7 # This is a file that the user can use for overrides of the kubelet args as a last resort. Preferably, the user sh ould use
8 # the .NodeRegistration.KubeletExtraArgs object in the configuration files instead. KUBELET_EXTRA_ARGS should be s ourced from this file.
9 EnvironmentFile=-/etc/sysconfig/kubelet
10 ExecStart=
11 ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARG

然后我们重启服务,创建目录

[root@node1 manifests]# mkdir /etc/kubernetes/test
[root@node1 manifests]# systemctl daemon-reload
[root@node1 manifests]# systemctl restart kubelet.service
# 然后我们将nginx.yaml复制到test目录下,看看效果
[root@node1 test]# cd /etc/kubernetes/test
[root@node1 test]# cp /root/nginx.yml .
# 回到master查看pod
[root@master k8s]# kubectl get pods
NAME READY STATUS RESTARTS AGE
initpod02 1/1 Running 0 95m
pod01-node1 1/1 Running 0 5s

发现pod也是被创建出来了,一样的,删除文件pod也会被删除,像这样的,yaml文件在,pod就在,yaml文件没了,pod就没了,是不是有一种人在塔在的感觉,这种就是静态pod

pod的调度策略(将pod指派给特定节点)

在我们创建pod的时候,k8s底层会进行一系列的选举,调度机制,来选举出最适合创建这个pod的节点

我们现在来创建4个pod,来看看他是怎么调度的

[root@master k8s]# kubectl run pod01 --image=nginx --image-pull-policy=IfNotPresent
pod/pod01 created
[root@master k8s]# kubectl run pod02 --image=nginx --image-pull-policy=IfNotPresent
pod/pod02 created
[root@master k8s]# kubectl run pod03 --image=nginx --image-pull-policy=IfNotPresent
pod/pod03 created
[root@master k8s]# kubectl run pod04 --image=nginx --image-pull-policy=IfNotPresent
pod/pod04 created
[root@master k8s]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod01 1/1 Running 0 28s 10.244.166.148 node1 <none> <none>
pod02 1/1 Running 0 24s 10.244.166.149 node1 <none> <none>
pod03 1/1 Running 0 10s 10.244.104.43 node2 <none> <none>
pod04 1/1 Running 0 6s 10.244.104.44 node2 <none> <none>
[root@master k8s]#

正好2个在node1,2个在node2,那我们再来搞2个,他是不是应该也是负载均衡呢

[root@master k8s]# kubectl run pod05 --image=nginx --image-pull-policy=IfNotPresent
pod/pod05 created
[root@master k8s]# kubectl run pod06 --image=nginx --image-pull-policy=IfNotPresent
pod/pod06 created
[root@master k8s]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod01 1/1 Running 0 3m10s 10.244.166.148 node1 <none> <none>
pod02 1/1 Running 0 3m6s 10.244.166.149 node1 <none> <none>
pod03 1/1 Running 0 2m52s 10.244.104.43 node2 <none> <none>
pod04 1/1 Running 0 2m48s 10.244.104.44 node2 <none> <none>
pod05 1/1 Running 0 8s 10.244.166.150 node1 <none> <none>
pod06 1/1 Running 0 4s 10.244.104.45 node2 <none> <none>

跟我们猜想的一样,他好像确实是一个node1一个node2,当然你在你的机器上可能并不是这样,这是因为我的两个node都是克隆出来的,配置完全一样

那我们如果想指定在某个node上创建呢,可以做到吗?可以的。如何实现呢?通过标签

标签的格式就是键值对的方式,比如

aa=bb,cc=dd

我们也可以查看标签

# 查看pod标签
[root@master k8s]# kubectl get pods --show-labels
NAME READY STATUS RESTARTS AGE LABELS
pod01 1/1 Running 0 9m2s run=pod01
pod02 1/1 Running 0 8m58s run=pod02
pod03 1/1 Running 0 8m44s run=pod03
pod04 1/1 Running 0 8m40s run=pod04
pod05 1/1 Running 0 6m run=pod05
pod06 1/1 Running 0 5m56s run=pod06
# 查看node标签
[root@master k8s]# kubectl get nodes --show-labels
NAME STATUS ROLES AGE VERSION LABELS
master Ready control-plane 34h v1.26.0 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=master,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node.kubernetes.io/exclude-from-external-load-balancers=
node1 Ready <none> 34h v1.26.0 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=node1,kubernetes.io/os=linux
node2 Ready <none> 34h v1.26.0 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=node2,kubernetes.io/os=linux

那这标签是怎么实现能指定调度pod呢,我们这样想一想,如果我在node上打上一个标签,cloud=666,然后我们在创建pod的时候也给他这个标签,他是否会被调度到这个node上呢

我们来看看

# 给主机添加标签
[root@master k8s]# kubectl label nodes node2 cloud=666
node/node2 labeled

现在我们生成一个yaml文件,修改他的标签

[root@master k8s]# kubectl run pod07 --image=nginx --image-pull-policy=IfNotPresent --dry-run=client -o yaml > label.yaml

然后我们修改这个文件

在第6行这里直接修改

  1 apiVersion: v1
2 kind: Pod
3 metadata:
4 creationTimestamp: null
# 这个是默认的,我们将他修改成cloud: 666
5 labels:
6 run: pod07
7 name: pod07
8 spec:
9 containers:
10 - image: nginx
11 imagePullPolicy: IfNotPresent
12 name: pod07
13 resources: {}
14 dnsPolicy: ClusterFirst
15 restartPolicy: Always
16 status: {}

改完之后的文件是这样的

apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
cloud: "666"
name: pod07
spec:
containers:
- image: nginx
imagePullPolicy: IfNotPresent
name: pod07
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
# 需要在spec下面加上节点选择,然后给他一个标签
nodeSelector:
cloud: "666"
status: {}

现在我们的节点标签是cloud=666,然后yaml文件里的node选择的标签也是cloud=666,那么他是不是一定会调度到node2呢?我们看看

[root@master k8s]# kubectl apply -f label.yaml
pod/pod07 created
# 然后我们进去将pod的name改成pod08
# 再apply一次
[root@master k8s]# kubectl apply -f label.yaml
pod/pod08 created
[root@master k8s]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod01 1/1 Running 0 23m 10.244.166.148 node1 <none> <none>
pod02 1/1 Running 0 22m 10.244.166.149 node1 <none> <none>
pod03 1/1 Running 0 22m 10.244.104.43 node2 <none> <none>
pod04 1/1 Running 0 22m 10.244.104.44 node2 <none> <none>
pod05 1/1 Running 0 20m 10.244.166.150 node1 <none> <none>
pod06 1/1 Running 0 19m 10.244.104.45 node2 <none> <none>
pod07 1/1 Running 0 26s 10.244.104.46 node2 <none> <none>
pod08 1/1 Running 0 3s 10.244.104.47 node2 <none> <none>

我们可以看见新启动的2个pod都是在node2上了,说明标签起作用了,不相信的话你还可以继续创建,他一定都是在node2上

所以我们可以通过这样的方式来指定pod在哪个节点上跑

取消标签,这个是非常容易的

# 直接在你想要删掉的标签的key后面加上一个-就好了
[root@master k8s]# kubectl label nodes node2 cloud-
node/node2 unlabeled

k8s初始化pod-pod标签的更多相关文章

  1. k8s学习 - 概念 - Pod

    k8s学习 - 概念 - Pod 这篇继续看概念,主要是 Pod 这个概念,这个概念非常重要,是 k8s 集群的最小单位. 怎么才算是理解好 pod 了呢,基本上把 pod 的所有 describe ...

  2. Kubernetes学习笔记(二):Pod、标签、注解

    pod与容器 一个pod是一组紧密相关的容器,它们总是一起运行在同一个节点上,以及同一个LInux命名空间中. 每个pod拥有自己的ip,包含若干个容器.pod分布在不同的节点上. 为什么需要pod ...

  3. k8s 中的 Pod 细节了解

    k8s中Pod的理解 基本概念 k8s 为什么使用 Pod 作为最小的管理单元 如何使用 Pod 1.自主式 Pod 2.控制器管理的 Pod 静态 Pod Pod的生命周期 Pod 如何直接暴露服务 ...

  4. k8s如何管理Pod(rc、rs、deployment)

    是豆荚,可以把容器想像成豆荚里的豆子,把一个或多个关系紧密的豆子包在一起就是豆荚(一个Pod).在k8s中我们不会直接操作容器,而是把容器包装成Pod再进行管理(关于Pod,大家可以参考第十期的分享“ ...

  5. k8s几种pod的控制器

    replicationcontroller 选择器 模版 副本数   如果更改选择器,则会创建新的pod 如果更改pod的标签,那么也会创建新的pod进行替换,但是老的pod不会被删除 如果更改模版, ...

  6. K8s之实践Pod深入理解

      K8s之实践Pod深入理解 1.同一pod下的nginx+php+mysql nginx+php+mysql.yaml文件 --- apiVersion: v1 kind: Secret meta ...

  7. 如何为k8s中的pod配置QoS等级?

    1.概述 本文介绍如何为pod分配特定的QoS等级. 我们知道,在k8s的环境中,通过使用QoS等级来做决定,在资源紧张的时候,将哪些的pod进行驱逐,或者说如何对pod进行调度. OK,话不多说,让 ...

  8. 案例分享 生产环境逐步迁移至k8s集群 - pod注册到consul

    #案例分享 生产环境逐步迁移至k8s集群 - pod注册到consul #项目背景 多套业务系统, 所有节点注册到consul集群,方便统一管理 使用consul的dns功能, 所有节点hostnam ...

  9. k8s暂停一个pod

    模拟k8s暂停一个服务:kubectl scale --replicas=0 deployment/[deployment]  -n [namespace](如要恢复设置参数--replicas=1即 ...

  10. K8S调度之pod亲和性

    目录 Pod Affinity Pod亲和性调度 pod互斥性调度 Pod Affinity 通过<K8S调度之节点亲和性>,我们知道怎么在调度的时候让pod灵活的选择node,但有些时候 ...

随机推荐

  1. C++20起支持的一个小特性

    注释掉的为传统的写法,从C++20起支持default关键字修饰的写法,即使是成员变量有多个的时候也支持,减轻了程序员的心智负担.

  2. 深入理解 python 虚拟机:原来虚拟机是这么实现闭包的

    深入理解 python 虚拟机:原来虚拟机是这么实现闭包的 在本篇文章当中主要从虚拟机层面讨论函数闭包是如何实现的,当能够从设计者的层面去理解闭包就再也不用死记硬背一些闭包的概念了,因为如果你理解闭包 ...

  3. replace批量替换、表删除数据查询用法

    一. select * from baec_file where bacti='1'order by baec01; select baec02,REPLACE(baec02,'白班','A班') f ...

  4. Go字符串实战操作大全!

    在本篇文章中,我们深入探讨了Go语言中字符串的魅力和深度.从基础定义.操作.字符编码到复杂的类型转换,每个环节都带有实例和代码示例来深化理解.通过这些深入的解析,读者不仅能够掌握字符串在Go中的核心概 ...

  5. Codeforces Round 823 (Div. 2)C

    C. Minimum Notation 思路:我们可以进行的操作时将一个位置的数删除然后在任意位置处添加一个比当前数大1并且小于9的数,所以我们的操作只会让一个数变大,我们统计一个最大值的后缀,贪心的 ...

  6. WPF --- TextBox的输入校验

    引言 在WPF应用程序开发中,数据校验是确保用户输入数据的正确性和完整性的重要一环. 之前在做一些参数配置功能时,最是头疼各种参数校验,查阅一些资料后,我总结了数据校验方式有两种: Validatio ...

  7. 确定性有限状态自动机 DFA

    前言 在计算理论中,确定有限状态自动机或确定有限自动机(英语:deterministic finite automaton, DFA)是一个能实现状态转移的自动机.对于一个给定的属于该自动机的状态和一 ...

  8. freeswitch的一个性能问题

    概述 freeswitch是一款简单好用的VOIP开源软交换平台. 在fs的使用过程中,会遇到各种各样的问题,各种问题中,性能问题是最头疼的. 最近在测试某些场景的时候,压测会造成fs的内存占用持续升 ...

  9. odoo17.0 快递鸟模块

    快递鸟是国内使用较为广泛的快递集成查询平台之一,提供了600+的物流公司对接接口,是比较不错的物流查询服务选择.随着odoo17.0的发布,我们最近也将快递鸟模块升级到了17.0.下面我们来详细看一下 ...

  10. WPF应用开发之控件动态内容展示

    在我们开发一些复杂信息的时候,由于需要动态展示一些相关信息,因此我们需要考虑一些控件内容的动态展示,可以通过动态构建控件的方式进行显示,如动态选项卡展示不同的信息,或者动态展示一个自定义控件的内容等等 ...