基于阿里云 Terway 网络的 Kubernetes 集群实践 - k8s-kb - 博客园

mikel阅读(619)

来源: 基于阿里云 Terway 网络的 Kubernetes 集群实践 – k8s-kb – 博客园

作者:BGbiao ,来源:简书,原文链接

背景:众所周知的是在构建一个Kubernetes集群时,容器网络通常会使用一个独立的私有子网来构建Kubernetes集群内部的pod网络和service网络,但在实际的业务场景中,没有企业会在一段时间内将内部全部的服务都迁移到Kubernetes集群中(因为涉及到业务架构以及整体业务的可靠性),因而会产生一些Kubernetes集群内部服务和集群外部服务互相调用的场景,当然如果是HTTP服务,我们可以采用LVS、Nginx、HAProxy之类的代理工具工具进行集群内外的流量转发,但如果是TCP服务,比如使用Dubbo框架时,生产者和消费者需要直连,当生产者和消费者不在一个可以互联互通的网络下会比较麻烦,这也就是为什么大厂在规模化使用Kubernetes时首先需要解决的就是网络问题的原因了。比如我们在数科的时候就采用的是Contiv+BGP的模式来实现容器网络和容器外网络的互联互通的,而这通常需要一个比较专业的SDN团队来构建和维护。而作为创业公司通常会使用公有云来承载自己的业务,这种轻资产模式的好处就是底层会有专业的团队来提供保障,因此考虑到业务需求我们采用了阿里云的terway网络插件来实现内部的Kubernetes集群网络.

现有网络插件

  • Flannel: Flannel是最早CoreOS团队开源的网络插件,用于让集群中不同节点创建的容器都具有集群内全局唯一的网络(集群外无法感知),也是当前Kubernetes开源方案中比较成熟的方案,支持HostGW和VXLAN模式
  • Calico: Calico是一个纯3层的数据中心网络方案,支持IPIP和BGP模式,后者可以无缝集成像OpenStack这种IaaS云架构,能够提供可控的VM、容器、裸机之间的IP通信,但是需要网络设备对BGP的支持(阿里云vpc子网内应该是不支持BGP的); 同时可以支持基于iptables的网络策略控制
  • Contiv: Contiv是思科开源的用于跨虚拟机、裸机、公有云或私有云的异构容器部署的开源容器网络架构,可支持2层、3层网络(通常也需要BGP的支持)
  • Terway: Terway是阿里云开源的基于VPC网络的CNI插件,支持VPC和ENI模式,后者可实现容器网络使用vpc子网网络

以上就是当前开源Kubernetes集群中使用较多的集中网络方案,我们的业务需求中也是需要打通容器内外的网络,因此在成本、效率以及稳定性上优先选择采用阿里云的Terway网络方案来满足我们的Kubernetes集群需求.

基于阿里云ECS搭建Terway网络的Kubernetes集群

注意: 阿里云容器服务ACK默认也支持两种网络,Flannel和Terway,前者和开源插件基本一致,后者支持VPC模式和ENI模式,VPC模式可实现容器网络使用vpc内交换机子网地址,但是默认无法和其他交换机下的ecs主机通信,ENI模式会给pod容器组分配一块弹性网卡来实现和集群外网络的互联互通,但Terway网络下的ENI模式需要部分特殊机型才可以支持。

由于ACK下Terway的ENI模式对机型的要求,我们采用购买ECS来自己搭建单节点集群测试Terway网络下容器的互联互通.

前提条件:

  • 已经创建了VPC子网
  • 在VPC子网下创建2个虚拟交换机(模拟Kubernetes集群网络和ECS网络)
  • 分别在两个子网购买两台ECS主机(模拟ECS到容器的互联互通)

注意: Terway网络插件官方验证过的os镜像为Centos 7.4/7.6,购买ecs时需要注意

1. 使用kubeadm安装k8s单节点集群

注意: 因为要使用terway网络将pod和ecs网络打通,因此需要将内核参数rp_filter全部设置为0(对数据包源地址不进行校验)

复制代码
# 更新yum源并安装k8s相关组件
$ yum update
$ cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg
        http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF

$ yum clean all
$ yum install kubelet kubeadm kubectl --disableexcludes=kubernetes -y
$ yum install docker -y


# 启动kubelet
## 此时kubelet会无限重试,因为会链接apiserver
$ systemctl restart kubelet

# 启动docker
## 注意:需要注意kubelet中的cgroupfs类型要和docker的cgroupfs一致
$ systemctl restart docker

# 查看kubeadm 启动集群时所需镜像
# 注意:kubeadm默认使用的是谷歌的镜像仓库,可将镜像仓库换成阿里云镜像仓库
# 将k8s.gcr.io 替换成registry.cn-hangzhou.aliyuncs.com/google_containers 即可
$ kubeadm config images list
k8s.gcr.io/kube-apiserver:v1.16.2
k8s.gcr.io/kube-controller-manager:v1.16.2
k8s.gcr.io/kube-scheduler:v1.16.2
k8s.gcr.io/kube-proxy:v1.16.2
k8s.gcr.io/pause:3.1
k8s.gcr.io/etcd:3.3.15-0
k8s.gcr.io/coredns:1.6.2

# 初始化集群
## 注意:初始化时需要指定vpc的子网,否则后期可能会发现无法识别vpc子网
$ kubeadm  init  --pod-network-cidr=172.16.48.0/20
....
....
Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 172.16.62.70:6443 --token j4b3xp.78izi2bmitxxx \
    --discovery-token-ca-cert-hash sha256:fd1ff50cbabd4fb22cb9a866052fbdc0db7da662168cda702exxxxxxxx

# 接下来按照上述提示创建配置文件
$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config

# 查看k8s的node节点(当前处于NotReady状态,因为kubelet还没有成功启动)
$ # kubectl version
Client Version: version.Info{Major:"1", Minor:"16", GitVersion:"v1.16.2", GitCommit:"c97fe5036ef3df2967d086711e6c0c405941e14b", GitTreeState:"clean", BuildDate:"2019-10-15T19:18:23Z", GoVersion:"go1.12.10", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"16", GitVersion:"v1.16.2", GitCommit:"c97fe5036ef3df2967d086711e6c0c405941e14b", GitTreeState:"clean", BuildDate:"2019-10-15T19:09:08Z", GoVersion:"go1.12.10", Compiler:"gc", Platform:"linux/amd64"}

$ kubectl  get nodes
NAME                      STATUS     ROLES    AGE     VERSION
izbp18diszrt8m41b2fbpsz   NotReady   master   7m19s   v1.16.2
复制代码

2. 给k8s集群创建terway网络

注意: 使用kubeadm创建的k8s集群是v1.16的,官方提供的yaml文件中需要稍微修改下DaemonSet的相关部分。

复制代码
# 给集群创建k8s的cni网络插件,也就是前面说的terway插件
# 需要修改阿里云相关的配置(ak,as,subnet,security_group)
$ curl -O https://raw.githubusercontent.com/BGBiao/k8s-ansible-playbooks/master/manifest/cni/terway/podnetwork.yaml

# 修改podnetwork.yaml中的配置(指定阿里云的ak和as认证信息以及vpc子网和安全组信息)
$ cat podnetwork.yaml
...
...
  eni_conf: |
    {
      "version": "1",
      "access_key": "your ak",
      "access_secret": "your as",
      "service_cidr": "your vpc subnet",
      "security_group": "your 安全组id",
      "max_pool_size": 5,
      "min_pool_size": 0
    }
....
....
          - name: Network
            value: "your vpc subnet"
....

# 创建terway网络
$ kubectl apply -f podnetwork.yaml
serviceaccount/terway created
clusterrole.rbac.authorization.k8s.io/terway-pod-reader created
clusterrolebinding.rbac.authorization.k8s.io/terway-binding created
configmap/eni-config created
daemonset.apps/terway created
customresourcedefinition.apiextensions.k8s.io/felixconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/bgpconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ippools.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/hostendpoints.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/clusterinformations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/globalnetworkpolicies.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/globalnetworksets.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/networkpolicies.crd.projectcalico.org created

# 查看cni相关容器以及node状态
$ kubectl  get nodes
NAME                      STATUS   ROLES    AGE   VERSION
izbp18diszrt8m41b2fbpsz   Ready    master   28m   v1.16.2

$ kubectl  get pods -A | grep terway
kube-system   terway-b9vm8                                      2/2     Running   0          6m53s
复制代码

至此,我们就已经完成了kubernetes的terway网络单节点集群,接下来就可以尝试让k8s集群中的pod来使用vpc的网络了,以便可以实现k8s集群内部的容器网络和其他ecs主机的网络是平行的.

3. 测试terway网络

注意: 我们使用kubeadm构建的k8s单节点集群,而kubeadm默认给master节点设置了taint,因此测试前需要去除taint。

复制代码
# 去除taint
$ kubectl taint nodes --all node-role.kubernetes.io/master-
node/izbp18diszrt8m41b2fbpsz untainted

# 默认创建一个vpc模式的deployment
$ kubectl  apply -f https://raw.githubusercontent.com/BGBiao/k8s-ansible-playbooks/master/manifest/cni/terway/nginx.yaml
namespace/myapp configured
deployment.apps/nginx-test created

# 可以看到容器网络地址其实是指定的vpc子网内地址
$ kubectl  get pods -n myapp  -o wide
NAME                         READY   STATUS    RESTARTS   AGE     IP            NODE                      NOMINATED NODE   READINESS GATES
nginx-test-d56c87dd9-26mzs   1/1     Running   0          2m40s   172.16.48.5   izbp18diszrt8m41b2fbpsz   <none>           <none>
nginx-test-d56c87dd9-hp2rv   1/1     Running   0          2m40s   172.16.48.4   izbp18diszrt8m41b2fbpsz   <none>           <none>

$ curl 172.16.48.4 -I
HTTP/1.1 200 OK
Server: nginx/1.17.5
Date: Sat, 26 Oct 2019 08:21:28 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 22 Oct 2019 14:30:00 GMT
Connection: keep-alive
ETag: "5daf1268-264"
Accept-Ranges: bytes

$ curl 172.16.48.5 -I
HTTP/1.1 200 OK
Server: nginx/1.17.5
Date: Sat, 26 Oct 2019 08:21:31 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 22 Oct 2019 14:30:00 GMT
Connection: keep-alive
ETag: "5daf1268-264"
Accept-Ranges: bytes
复制代码

可以发现,在集群内部使用terway网络已经没有任何问题了,但是我们在其他ECS主机去访问pod网络时发现依然无法访问(因为默认使用的是terway的VPC模式,其实就是类似于calico的模式了.这个时候就需要用到eni模式了,即给k8s节点增加eni弹性网卡,然后pod的网络流量统一通过node节点的eni网卡传输,此时就可以很好的和整个内网vpc打通了)

4. 测试ENI模式

注意: 在上面的nginx配置中增加limits: aliyun/eni: N即可,需要注意的是N表示node节点上eni弹性网卡的数量,该数量取决于阿里云ecs不同规格对eni的限制。

复制代码
# 注意:
# 由于实验中采用的是4c8g的k8s单节点集群,因此只能创建2个弹性网卡,这也就意味着如果不增加任何网络配置,该node节点最多只能运行2个和整个VPC网络中其他ecs主机互联互通的pod

$ cat nginx.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-v2
  namespace: myapp
spec:
  revisionHistoryLimit: 10
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  replicas: 2
  selector:
    matchLabels:
      app: nginx-v2
      profile: prod
  template:
    metadata:
      labels:
        app: nginx-v2
        profile: prod
    spec:
      containers:
      - name: nginx-v2
        image: nginx:latest
        imagePullPolicy: IfNotPresent
        resources:
          requests:
            cpu: 200m
            memory: 215Mi
          limits:
            cpu: 200m
            memory: 215Mi
            aliyun/eni: 1


# 创建带eni的pod
$ kubectl  apply -f nginx.yaml
deployment.apps/nginx-v2 configured

# 查看pod状态
$ kubectl  get pods -n myapp -o wide
NAME                         READY   STATUS    RESTARTS   AGE   IP             NODE                      NOMINATED NODE   READINESS GATES
nginx-test-d56c87dd9-26mzs   1/1     Running   0          19m   172.16.48.5    izbp18diszrt8m41b2fbpsz   <none>           <none>
nginx-test-d56c87dd9-hp2rv   1/1     Running   0          19m   172.16.48.4    izbp18diszrt8m41b2fbpsz   <none>           <none>
nginx-v2-7548466fc8-d4klv    1/1     Running   0          61s   172.16.62.74   izbp18diszrt8m41b2fbpsz   <none>           <none>
nginx-v2-7548466fc8-x7ft9    1/1     Running   0          61s   172.16.62.75   izbp18diszrt8m41b2fbpsz   <none>           <none>

# 在k8snode节点访问
$ curl 172.16.62.75 -I
HTTP/1.1 200 OK
Server: nginx/1.17.5
Date: Sat, 26 Oct 2019 08:38:20 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 22 Oct 2019 14:30:00 GMT
Connection: keep-alive
ETag: "5daf1268-264"
Accept-Ranges: bytes

$ curl 172.16.62.74 -I
HTTP/1.1 200 OK
Server: nginx/1.17.5
Date: Sat, 26 Oct 2019 08:38:23 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 22 Oct 2019 14:30:00 GMT
Connection: keep-alive
ETag: "5daf1268-264"
Accept-Ranges: bytes

# 此时发现创建的带eni和不带eni的两个pod在k8s集群内部已经完全可以访问
复制代码

5. 测试集群内外部网络互联互通

注意: k8s集群使用的是vpc网络,因此默认集群访问外部ECS网络默认是没有问题,这里主要测试外部ECS网络是否可以直连pod网络进行通信。

复制代码
# 在同vpc环境下其他ecs主机上访问
# 首先分别ping 上述四个pod的网络(可以发现eni模式下容器默认可以ping通)
$ for i in 172.16.48.5 172.16.48.4 172.16.62.74 172.16.62.75 ;do ping -c 1 -w 1 $i;done
PING 172.16.48.5 (172.16.48.5) 56(84) bytes of data.

--- 172.16.48.5 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 999ms

PING 172.16.48.4 (172.16.48.4) 56(84) bytes of data.

--- 172.16.48.4 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms

PING 172.16.62.74 (172.16.62.74) 56(84) bytes of data.
64 bytes from 172.16.62.74: icmp_seq=1 ttl=64 time=0.782 ms

--- 172.16.62.74 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.782/0.782/0.782/0.000 ms
PING 172.16.62.75 (172.16.62.75) 56(84) bytes of data.
64 bytes from 172.16.62.75: icmp_seq=1 ttl=64 time=0.719 ms

--- 172.16.62.75 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.719/0.719/0.719/0.000 ms

# 测试nginx服务(依然是带eni的网络可达)
$ for i in 172.16.48.5 172.16.48.4 172.16.62.74 172.16.62.75 ;do curl --connect-timeout 1 -I  $i;done
curl: (28) Connection timed out after 1001 milliseconds
curl: (28) Connection timed out after 1001 milliseconds
HTTP/1.1 200 OK
Server: nginx/1.17.5
Date: Sat, 26 Oct 2019 08:44:21 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 22 Oct 2019 14:30:00 GMT
Connection: keep-alive
ETag: "5daf1268-264"
Accept-Ranges: bytes

HTTP/1.1 200 OK
Server: nginx/1.17.5
Date: Sat, 26 Oct 2019 08:44:21 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 22 Oct 2019 14:30:00 GMT
Connection: keep-alive
ETag: "5daf1268-264"
Accept-Ranges: bytes
复制代码

此时,我们查看该node节点上的网卡信息时可以看到,增加了两块辅助网卡。

6. 其他问题

注意:前面我们提到过,如果使用eni模式,不同的ECS规格可以绑定的ENI弹性网卡是有限的,也就是说可以创建互联互通的容器是有限的,我们这里验证下。

复制代码
# 如果我们这个时候再创建带eni的pod时,就会发现无法创建成功(因为4c8g的ecs最大只支持两个eni)
$ kubectl  apply -f nginx-v3.yaml
deployment.apps/nginx-v3 created
[root@iZbp18diszrt8m41b2fbpsZ ~]# kubectl  get pods -n myapp -o wide
NAME                         READY   STATUS    RESTARTS   AGE   IP             NODE                      NOMINATED NODE   READINESS GATES
nginx-test-d56c87dd9-26mzs   1/1     Running   0          48m   172.16.48.5    izbp18diszrt8m41b2fbpsz   <none>           <none>
nginx-test-d56c87dd9-hp2rv   1/1     Running   0          48m   172.16.48.4    izbp18diszrt8m41b2fbpsz   <none>           <none>
nginx-v2-7548466fc8-d4klv    1/1     Running   0          29m   172.16.62.74   izbp18diszrt8m41b2fbpsz   <none>           <none>
nginx-v2-7548466fc8-x7ft9    1/1     Running   0          29m   172.16.62.75   izbp18diszrt8m41b2fbpsz   <none>           <none>
nginx-v3-79dd8fb956-4ghgb    0/1     Pending   0          2s    <none>         <none>                    <none>           <none>
nginx-v3-79dd8fb956-str2k    0/1     Pending   0          2s    <none>         <none>                    <none>           <none>

# 查看Pending的详情
$ kubectl  describe pods -n myapp nginx-v3-79dd8fb956-4ghgb
....
....
Events:
  Type     Reason            Age        From               Message
  ----     ------            ----       ----               -------
  Warning  FailedScheduling  <unknown>  default-scheduler  0/1 nodes are available: 1 Insufficient aliyun/eni.
复制代码

可以发现,当使用terway网络的ENI模式时,如果该ecs可支持的弹性网卡达到限制,k8s就会调度失败。

所以问题就来了,通常情况下,我们是希望使用k8s来弹性扩容,我们会希望k8s节点上运行更多的pod,但用了terway网络之后我们发现,创建和k8s集群外ecs主机通信的pod数量竟然受eni的限制,这可得了?

其实不用担心,阿里云同学的回复是,这种情况下在vpc上设置静态路由即可实现node节点上的多pod和集群外ecs主机互通,此时ecs主机上的eni仅相当于是整个容器的网络出口,到这里其实我们就可以放心了,因为使用terway后,及时不用eni模式,pod网络也是全局唯一的,这个时候适当增加一些静态路由,即可实现整个vpc内k8s容器网络和容器外的ecs主机网络互联互通,很好的解决了我们一开始的问题。

注意:阿里云容器服务ACK的terway网络模式下的集群会默认创建一些路由规则,因此当你使用ACK集群时,只要购买了支持terway规格的节点,默认创建的容器都可以实现和外部ecs主机的互联互通,此时,该ecs上创建的弹性网卡将作为节点上k8s容器的网络出口,而ecs主机本身的eth0将仅作为管理网络而存在,感兴趣的同学可以点击阅读原文尝试使用阿里云ACK的terway网络模式。

.Net微服务实战之Kubernetes的搭建与使用 - 陈珙 - 博客园

mikel阅读(749)

来源: .Net微服务实战之Kubernetes的搭建与使用 – 陈珙 – 博客园

系列文章

前言

说到微服务就得扯到自动化运维,然后别人就不得不问你用没用上K8S。无论是概念上还是在实施搭建时,K8S的门槛比Docker Compose、Docker Swarm高了不少。我自己也经过了多次的实践,整理出一套顺利部署的流程。

我这次搭建花了一共整整4个工作实践与一个工作日写博客,中间有一个网络问题导致reset了集群重新搭了一次,完成后结合了Jenkins使用,还是成就感满满的。如果对大家有用,还请点个推荐与关注。

基本概念

Kubectl

kubectl用于运行Kubernetes集群命令的管理工具,Kubernetes kubectl 与 Docker 命令关系可以查看这里

http://docs.kubernetes.org.cn/70.html

Kubeadm

kubeadm 是 kubernetes 的集群安装工具,能够快速安装 kubernetes 集群,相关命令有以下:

kubeadm init

kubeadm join

Kubelet

kubelet是主要的节点代理,它会监视已分配给节点的pod,具体功能:

  • 安装Pod所需的volume。
  • 下载Pod的Secrets。
  • Pod中运行的 docker(或experimentally,rkt)容器。
  • 定期执行容器健康检查。

Pod

Pod是Kubernetes创建或部署的最小(最简单)的基本单位,一个Pod代表集群上正在运行的一个进程,它可能由单个容器或多个容器共享组成的资源。

一个Pod封装一个应用容器(也可以有多个容器),存储资源、一个独立的网络IP以及管理控制容器运行方式的策略选项。

Pods提供两种共享资源:网络和存储。

网络

每个Pod被分配一个独立的IP地址,Pod中的每个容器共享网络命名空间,包括IP地址和网络端口。Pod内的容器可以使用localhost相互通信。当Pod中的容器与Pod 外部通信时,他们必须协调如何使用共享网络资源(如端口)。

存储

Pod可以指定一组共享存储volumes。Pod中的所有容器都可以访问共享volumes,允许这些容器共享数据。volumes 还用于Pod中的数据持久化,以防其中一个容器需要重新启动而丢失数据。

Service

一个应用服务在Kubernetes中可能会有一个或多个Pod,每个Pod的IP地址由网络组件动态随机分配(Pod重启后IP地址会改变)。为屏蔽这些后端实例的动态变化和对多实例的负载均衡,引入了Service这个资源对象。

Kubernetes ServiceTypes 允许指定一个需要的类型的 Service,默认是 ClusterIP 类型。

Type 的取值以及行为如下:

  • ClusterIP:通过集群的内部 IP 暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的 ServiceType。
  • NodePort:通过每个 Node 上的 IP 和静态端口(NodePort)暴露服务。NodePort 服务会路由到 ClusterIP 服务,这个 ClusterIP 服务会自动创建。通过请求 <NodeIP>:<NodePort>,可以从集群的外部访问一个 NodePort 服务。
  • LoadBalancer:使用云提供商的负载局衡器,可以向外部暴露服务。外部的负载均衡器可以路由到 NodePort 服务和 ClusterIP 服务。
  • ExternalName:通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容(例如, foo.bar.example.com)。 没有任何类型代理被创建,这只有 Kubernetes 1.7 或更高版本的 kube-dns 才支持。

其他详细的概念请移步到 http://docs.kubernetes.org.cn/227.html

物理部署图

 

Docker-ce 1.19安装

在所有需要用到kubernetes服务器上安装docker-ce

卸载旧版本 docker

yum remove docker docker-common docker-selinux dockesr-engine -y
升级系统软件
yum upgrade -y
安装必要的一些系统工具
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
添加docker-ce软件源
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
更新并安装 docker-ce
yum makecache fast
yum install docker-ce-19.03.12 -y
添加docker国内镜像源
vim /etc/docker/daemon.json
 
{
"exec-opts": ["native.cgroupdriver=systemd"],
"registry-mirrors" : [
    "http://ovfftd6p.mirror.aliyuncs.com",
    "http://registry.docker-cn.com",
    "http://docker.mirrors.ustc.edu.cn",
    "http://hub-mirror.c.163.com"
  ],
  "insecure-registries" : [
    "registry.docker-cn.com",
    "docker.mirrors.ustc.edu.cn"
  ],
  "debug" : true,
  "experimental" : true
}

启动服务

systemctl start docker
systemctl enable docker

安装kubernetes-1.18.3

所有需要用到kubernetes的服务器都执行以下指令。

添加阿里kubernetes源
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
安装并启动
yum install kubeadm-1.18.3 kubectl-1.18.3 kubelet-1.18.3

启动kubelet

systemctl enable kubelet
systemctl start kubelet

在Master设置环境变量,在/etc/profile中配置
vim /etc/profile
在最后添加如下配置
export KUBECONFIG=/etc/kubernetes/admin.conf

执行命令使其起效

source /etc/profile

初始化k8s集群

在master节点(server-a)进行初始化集群

开放端口

firewall-cmd --permanent --zone=public --add-port=6443/tcp
firewall-cmd --permanent --zone=public --add-port=10250/tcp
firewall-cmd --reload
关闭swap
vim /etc/fstab
#注释swap那行
 
swapoff -a

设置iptables规则

echo 1 > /proc/sys/net/bridge/bridge-nf-call-iptables
echo 1 > /proc/sys/net/bridge/bridge-nf-call-ip6tables

初始化

kubeadm init --kubernetes-version=1.18.3  --apiserver-advertise-address=192.168.88.138   --image-repository registry.aliyuncs.com/google_containers  --service-cidr=10.10.0.0/16 --pod-network-cidr=10.122.0.0/16 --ignore-preflight-errors=Swap

pod-network-cidr参数的为pod网段:,apiserver-advertise-address参数为本机IP。

  如果中途执行有异常可以通过 kubeadm reset 后重新init。
初始化成功执行下面指令
 mkdir -p $HOME/.kube
 sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
 sudo chown $(id -u):$(id -g) $HOME/.kube/config
查看node和pod信息
kubectl get node
kubectl get pod --all-namespaces

安装flannel组件

在master节点(server-a)安装flannel组件

找个梯子下载kube-flannel.yml文件

https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

下载不了也没关系,我复制给到大家:

---
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: psp.flannel.unprivileged
  annotations:
    seccomp.security.alpha.kubernetes.io/allowedProfileNames: docker/default
    seccomp.security.alpha.kubernetes.io/defaultProfileName: docker/default
    apparmor.security.beta.kubernetes.io/allowedProfileNames: runtime/default
    apparmor.security.beta.kubernetes.io/defaultProfileName: runtime/default
spec:
  privileged: false
  volumes:
    - configMap
    - secret
    - emptyDir
    - hostPath
  allowedHostPaths:
    - pathPrefix: "/etc/cni/net.d"
    - pathPrefix: "/etc/kube-flannel"
    - pathPrefix: "/run/flannel"
  readOnlyRootFilesystem: false
  # Users and groups
  runAsUser:
    rule: RunAsAny
  supplementalGroups:
    rule: RunAsAny
  fsGroup:
    rule: RunAsAny
  # Privilege Escalation
  allowPrivilegeEscalation: false
  defaultAllowPrivilegeEscalation: false
  # Capabilities
  allowedCapabilities: ['NET_ADMIN', 'NET_RAW']
  defaultAddCapabilities: []
  requiredDropCapabilities: []
  # Host namespaces
  hostPID: false
  hostIPC: false
  hostNetwork: true
  hostPorts:
  - min: 0
    max: 65535
  # SELinux
  seLinux:
    # SELinux is unused in CaaSP
    rule: 'RunAsAny'
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: flannel
rules:
  - apiGroups: ['extensions']
    resources: ['podsecuritypolicies']
    verbs: ['use']
    resourceNames: ['psp.flannel.unprivileged']
  - apiGroups:
      - ""
    resources:
      - pods
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - nodes
    verbs:
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - nodes/status
    verbs:
      - patch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: flannel
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: flannel
subjects:
- kind: ServiceAccount
  name: flannel
  namespace: kube-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: flannel
  namespace: kube-system
---
kind: ConfigMap
apiVersion: v1
metadata:
  name: kube-flannel-cfg
  namespace: kube-system
  labels:
    tier: node
    app: flannel
data:
  cni-conf.json: |
    {
      "name": "cbr0",
      "cniVersion": "0.3.1",
      "plugins": [
        {
          "type": "flannel",
          "delegate": {
            "hairpinMode": true,
            "isDefaultGateway": true
          }
        },
        {
          "type": "portmap",
          "capabilities": {
            "portMappings": true
          }
        }
      ]
    }
  net-conf.json: |
    {
      "Network": "10.244.0.0/16",
      "Backend": {
        "Type": "vxlan"
      }
    }
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: kube-flannel-ds-amd64
  namespace: kube-system
  labels:
    tier: node
    app: flannel
spec:
  selector:
    matchLabels:
      app: flannel
  template:
    metadata:
      labels:
        tier: node
        app: flannel
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: kubernetes.io/os
                    operator: In
                    values:
                      - linux
                  - key: kubernetes.io/arch
                    operator: In
                    values:
                      - amd64
      hostNetwork: true
      priorityClassName: system-node-critical
      tolerations:
      - operator: Exists
        effect: NoSchedule
      serviceAccountName: flannel
      initContainers:
      - name: install-cni
        image: quay.io/coreos/flannel:v0.12.0-amd64
        command:
        - cp
        args:
        - -f
        - /etc/kube-flannel/cni-conf.json
        - /etc/cni/net.d/10-flannel.conflist
        volumeMounts:
        - name: cni
          mountPath: /etc/cni/net.d
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      containers:
      - name: kube-flannel
        image: quay.io/coreos/flannel:v0.12.0-amd64
        command:
        - /opt/bin/flanneld
        args:
        - --ip-masq
        - --kube-subnet-mgr
        resources:
          requests:
            cpu: "100m"
            memory: "50Mi"
          limits:
            cpu: "100m"
            memory: "50Mi"
        securityContext:
          privileged: false
          capabilities:
            add: ["NET_ADMIN", "NET_RAW"]
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        volumeMounts:
        - name: run
          mountPath: /run/flannel
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      volumes:
        - name: run
          hostPath:
            path: /run/flannel
        - name: cni
          hostPath:
            path: /etc/cni/net.d
        - name: flannel-cfg
          configMap:
            name: kube-flannel-cfg
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: kube-flannel-ds-arm64
  namespace: kube-system
  labels:
    tier: node
    app: flannel
spec:
  selector:
    matchLabels:
      app: flannel
  template:
    metadata:
      labels:
        tier: node
        app: flannel
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: kubernetes.io/os
                    operator: In
                    values:
                      - linux
                  - key: kubernetes.io/arch
                    operator: In
                    values:
                      - arm64
      hostNetwork: true
      priorityClassName: system-node-critical
      tolerations:
      - operator: Exists
        effect: NoSchedule
      serviceAccountName: flannel
      initContainers:
      - name: install-cni
        image: quay.io/coreos/flannel:v0.12.0-arm64
        command:
        - cp
        args:
        - -f
        - /etc/kube-flannel/cni-conf.json
        - /etc/cni/net.d/10-flannel.conflist
        volumeMounts:
        - name: cni
          mountPath: /etc/cni/net.d
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      containers:
      - name: kube-flannel
        image: quay.io/coreos/flannel:v0.12.0-arm64
        command:
        - /opt/bin/flanneld
        args:
        - --ip-masq
        - --kube-subnet-mgr
        resources:
          requests:
            cpu: "100m"
            memory: "50Mi"
          limits:
            cpu: "100m"
            memory: "50Mi"
        securityContext:
          privileged: false
          capabilities:
             add: ["NET_ADMIN", "NET_RAW"]
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        volumeMounts:
        - name: run
          mountPath: /run/flannel
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      volumes:
        - name: run
          hostPath:
            path: /run/flannel
        - name: cni
          hostPath:
            path: /etc/cni/net.d
        - name: flannel-cfg
          configMap:
            name: kube-flannel-cfg
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: kube-flannel-ds-arm
  namespace: kube-system
  labels:
    tier: node
    app: flannel
spec:
  selector:
    matchLabels:
      app: flannel
  template:
    metadata:
      labels:
        tier: node
        app: flannel
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: kubernetes.io/os
                    operator: In
                    values:
                      - linux
                  - key: kubernetes.io/arch
                    operator: In
                    values:
                      - arm
      hostNetwork: true
      priorityClassName: system-node-critical
      tolerations:
      - operator: Exists
        effect: NoSchedule
      serviceAccountName: flannel
      initContainers:
      - name: install-cni
        image: quay.io/coreos/flannel:v0.12.0-arm
        command:
        - cp
        args:
        - -f
        - /etc/kube-flannel/cni-conf.json
        - /etc/cni/net.d/10-flannel.conflist
        volumeMounts:
        - name: cni
          mountPath: /etc/cni/net.d
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      containers:
      - name: kube-flannel
        image: quay.io/coreos/flannel:v0.12.0-arm
        command:
        - /opt/bin/flanneld
        args:
        - --ip-masq
        - --kube-subnet-mgr
        resources:
          requests:
            cpu: "100m"
            memory: "50Mi"
          limits:
            cpu: "100m"
            memory: "50Mi"
        securityContext:
          privileged: false
          capabilities:
             add: ["NET_ADMIN", "NET_RAW"]
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        volumeMounts:
        - name: run
          mountPath: /run/flannel
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      volumes:
        - name: run
          hostPath:
            path: /run/flannel
        - name: cni
          hostPath:
            path: /etc/cni/net.d
        - name: flannel-cfg
          configMap:
            name: kube-flannel-cfg
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: kube-flannel-ds-ppc64le
  namespace: kube-system
  labels:
    tier: node
    app: flannel
spec:
  selector:
    matchLabels:
      app: flannel
  template:
    metadata:
      labels:
        tier: node
        app: flannel
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: kubernetes.io/os
                    operator: In
                    values:
                      - linux
                  - key: kubernetes.io/arch
                    operator: In
                    values:
                      - ppc64le
      hostNetwork: true
      priorityClassName: system-node-critical
      tolerations:
      - operator: Exists
        effect: NoSchedule
      serviceAccountName: flannel
      initContainers:
      - name: install-cni
        image: quay.io/coreos/flannel:v0.12.0-ppc64le
        command:
        - cp
        args:
        - -f
        - /etc/kube-flannel/cni-conf.json
        - /etc/cni/net.d/10-flannel.conflist
        volumeMounts:
        - name: cni
          mountPath: /etc/cni/net.d
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      containers:
      - name: kube-flannel
        image: quay.io/coreos/flannel:v0.12.0-ppc64le
        command:
        - /opt/bin/flanneld
        args:
        - --ip-masq
        - --kube-subnet-mgr
        resources:
          requests:
            cpu: "100m"
            memory: "50Mi"
          limits:
            cpu: "100m"
            memory: "50Mi"
        securityContext:
          privileged: false
          capabilities:
             add: ["NET_ADMIN", "NET_RAW"]
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        volumeMounts:
        - name: run
          mountPath: /run/flannel
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      volumes:
        - name: run
          hostPath:
            path: /run/flannel
        - name: cni
          hostPath:
            path: /etc/cni/net.d
        - name: flannel-cfg
          configMap:
            name: kube-flannel-cfg
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: kube-flannel-ds-s390x
  namespace: kube-system
  labels:
    tier: node
    app: flannel
spec:
  selector:
    matchLabels:
      app: flannel
  template:
    metadata:
      labels:
        tier: node
        app: flannel
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: kubernetes.io/os
                    operator: In
                    values:
                      - linux
                  - key: kubernetes.io/arch
                    operator: In
                    values:
                      - s390x
      hostNetwork: true
      priorityClassName: system-node-critical
      tolerations:
      - operator: Exists
        effect: NoSchedule
      serviceAccountName: flannel
      initContainers:
      - name: install-cni
        image: quay.io/coreos/flannel:v0.12.0-s390x
        command:
        - cp
        args:
        - -f
        - /etc/kube-flannel/cni-conf.json
        - /etc/cni/net.d/10-flannel.conflist
        volumeMounts:
        - name: cni
          mountPath: /etc/cni/net.d
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      containers:
      - name: kube-flannel
        image: quay.io/coreos/flannel:v0.12.0-s390x
        command:
        - /opt/bin/flanneld
        args:
        - --ip-masq
        - --kube-subnet-mgr
        resources:
          requests:
            cpu: "100m"
            memory: "50Mi"
          limits:
            cpu: "100m"
            memory: "50Mi"
        securityContext:
          privileged: false
          capabilities:
             add: ["NET_ADMIN", "NET_RAW"]
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        volumeMounts:
        - name: run
          mountPath: /run/flannel
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      volumes:
        - name: run
          hostPath:
            path: /run/flannel
        - name: cni
          hostPath:
            path: /etc/cni/net.d
        - name: flannel-cfg
          configMap:
            name: kube-flannel-cfg

先拉取依赖镜像

 docker pull  quay.io/coreos/flannel:v0.12.0-amd64

把上面文件保存到服务器然后执行下面命令

kubectl apply -f kube-flannel.yml

安装dashboard

在master节点(server-a)安装dashboard组件

继续用梯子下载recommended.yml文件

https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.3/aio/deploy/recommended.yaml

没梯子的可以复制下方原文件

# Copyright 2017 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

apiVersion: v1
kind: Namespace
metadata:
  name: kubernetes-dashboard

---

apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard

---

kind: Service
apiVersion: v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
spec:
  ports:
    - port: 443
      targetPort: 8443
  selector:
    k8s-app: kubernetes-dashboard

---

apiVersion: v1
kind: Secret
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-certs
  namespace: kubernetes-dashboard
type: Opaque

---

apiVersion: v1
kind: Secret
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-csrf
  namespace: kubernetes-dashboard
type: Opaque
data:
  csrf: ""

---

apiVersion: v1
kind: Secret
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-key-holder
  namespace: kubernetes-dashboard
type: Opaque

---

kind: ConfigMap
apiVersion: v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-settings
  namespace: kubernetes-dashboard

---

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
rules:
  # Allow Dashboard to get, update and delete Dashboard exclusive secrets.
  - apiGroups: [""]
    resources: ["secrets"]
    resourceNames: ["kubernetes-dashboard-key-holder", "kubernetes-dashboard-certs", "kubernetes-dashboard-csrf"]
    verbs: ["get", "update", "delete"]
    # Allow Dashboard to get and update 'kubernetes-dashboard-settings' config map.
  - apiGroups: [""]
    resources: ["configmaps"]
    resourceNames: ["kubernetes-dashboard-settings"]
    verbs: ["get", "update"]
    # Allow Dashboard to get metrics.
  - apiGroups: [""]
    resources: ["services"]
    resourceNames: ["heapster", "dashboard-metrics-scraper"]
    verbs: ["proxy"]
  - apiGroups: [""]
    resources: ["services/proxy"]
    resourceNames: ["heapster", "http:heapster:", "https:heapster:", "dashboard-metrics-scraper", "http:dashboard-metrics-scraper"]
    verbs: ["get"]

---

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
rules:
  # Allow Metrics Scraper to get metrics from the Metrics server
  - apiGroups: ["metrics.k8s.io"]
    resources: ["pods", "nodes"]
    verbs: ["get", "list", "watch"]

---

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: kubernetes-dashboard
subjects:
  - kind: ServiceAccount
    name: kubernetes-dashboard
    namespace: kubernetes-dashboard

---

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: kubernetes-dashboard
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: kubernetes-dashboard
subjects:
  - kind: ServiceAccount
    name: kubernetes-dashboard
    namespace: kubernetes-dashboard

---

kind: Deployment
apiVersion: apps/v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: kubernetes-dashboard
  template:
    metadata:
      labels:
        k8s-app: kubernetes-dashboard
    spec:
      containers:
        - name: kubernetes-dashboard
          image: kubernetesui/dashboard:v2.0.3
          imagePullPolicy: Always
          ports:
            - containerPort: 8443
              protocol: TCP
          args:
            - --auto-generate-certificates
            - --namespace=kubernetes-dashboard
            # Uncomment the following line to manually specify Kubernetes API server Host
            # If not specified, Dashboard will attempt to auto discover the API server and connect
            # to it. Uncomment only if the default does not work.
            # - --apiserver-host=http://my-address:port
          volumeMounts:
            - name: kubernetes-dashboard-certs
              mountPath: /certs
              # Create on-disk volume to store exec logs
            - mountPath: /tmp
              name: tmp-volume
          livenessProbe:
            httpGet:
              scheme: HTTPS
              path: /
              port: 8443
            initialDelaySeconds: 30
            timeoutSeconds: 30
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            runAsUser: 1001
            runAsGroup: 2001
      volumes:
        - name: kubernetes-dashboard-certs
          secret:
            secretName: kubernetes-dashboard-certs
        - name: tmp-volume
          emptyDir: {}
      serviceAccountName: kubernetes-dashboard
      nodeSelector:
        "kubernetes.io/os": linux
      # Comment the following tolerations if Dashboard must not be deployed on master
      tolerations:
        - key: node-role.kubernetes.io/master
          effect: NoSchedule

---

kind: Service
apiVersion: v1
metadata:
  labels:
    k8s-app: dashboard-metrics-scraper
  name: dashboard-metrics-scraper
  namespace: kubernetes-dashboard
spec:
  ports:
    - port: 8000
      targetPort: 8000
  selector:
    k8s-app: dashboard-metrics-scraper

---

kind: Deployment
apiVersion: apps/v1
metadata:
  labels:
    k8s-app: dashboard-metrics-scraper
  name: dashboard-metrics-scraper
  namespace: kubernetes-dashboard
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: dashboard-metrics-scraper
  template:
    metadata:
      labels:
        k8s-app: dashboard-metrics-scraper
      annotations:
        seccomp.security.alpha.kubernetes.io/pod: 'runtime/default'
    spec:
      containers:
        - name: dashboard-metrics-scraper
          image: kubernetesui/metrics-scraper:v1.0.4
          ports:
            - containerPort: 8000
              protocol: TCP
          livenessProbe:
            httpGet:
              scheme: HTTP
              path: /
              port: 8000
            initialDelaySeconds: 30
            timeoutSeconds: 30
          volumeMounts:
          - mountPath: /tmp
            name: tmp-volume
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            runAsUser: 1001
            runAsGroup: 2001
      serviceAccountName: kubernetes-dashboard
      nodeSelector:
        "kubernetes.io/os": linux
      # Comment the following tolerations if Dashboard must not be deployed on master
      tolerations:
        - key: node-role.kubernetes.io/master
          effect: NoSchedule
      volumes:
        - name: tmp-volume
          emptyDir: {}
第39行修改,端口范围30000-32767
spec:
  type: NodePort
  ports:
    - port: 443
      targetPort: 8443
      nodePort: 30221
  selector:
    k8s-app: kubernetes-dashboard

第137行开始,修改账户权限,主要三个参数,kind: ClusterRoleBinding,roleRef-kind: ClusterRole,roleRef-name: cluster-admin

---

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: kubernetes-dashboard
    namespace: kubernetes-dashboard

---

保存到服务器后执行以下命令

kubectl apply -f recommended.yaml

等待一段时间启动成功后,https://ip+nodePort,查看UI

Token通过下面指令获取

kubectl -n kubernetes-dashboard get secret

kubectl describe secrets -n kubernetes-dashboard kubernetes-dashboard-token-kfcp2  | grep token | awk 'NR==3{print $2}'

加入Worker节点

在server-b与server-c执行下面操作

把上面init后的那句join拷贝过来,如果忘记了可以在master节点执行下面指令:

kubeadm token list

openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'

通过返回的数据拼装成下面指令

kubeadm join 192.168.88.138:6443 --token 2zebwy.1549suwrkkven7ow  --discovery-token-ca-cert-hash sha256:c61af74d6e4ba1871eceaef4e769d14a20a86c9276ac0899f8ec6b08b89f532b

查看节点信息

kubectl get node

部署Web应用

在master节点(sever-a)执行下面操作

部署应用前建议有需要的朋友到【.Net微服务实战之CI/CD】看看如何搭建docker私有仓库,后面需要用到,搭建后私有库后执行下面指令

kubectl create secret docker-registry docker-registry-secret --docker-server=192.168.88.141:6000 --docker-username=admin --docker-password=123456789

docker-server就是docker私有仓库的地址

下面是yaml模板,注意imagePullSecrets-name与上面的命名的一致,其余的可以查看yaml里的注释

apiVersion: apps/v1
kind: Deployment # Deployment为多个Pod副本
metadata:
  name: testdockerswarm-deployment
  labels:
    app: testdockerswarm-deployment
spec:
  replicas: 2 # 实例数量
  selector:
    matchLabels: # 定义该部署匹配哪些Pod
      app: testdockerswarm
  minReadySeconds: 3 # 可选,指定Pod可以变成可用状态的最小秒数,默认是0
  strategy:
    type: RollingUpdate # 部署策略类型,使用RollingUpdate可以保证部署期间服务不间断
    rollingUpdate:
      maxUnavailable: 1 # 部署时最大允许停止的Pod数量
      maxSurge: 1 # 部署时最大允许创建的Pod数量
  template: # 用来指定Pod的模板,与Pod的定义类似
    metadata:
      labels: # Pod标签,与上面matchLabels对应
        app: testdockerswarm
    spec:
      imagePullSecrets:
        - name: docker-registry-secret
      containers:
        - name: testdockerswarm
          image: 192.168.88.141:6000/testdockerswarm
          imagePullPolicy: Always # Always每次拉去新镜像
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: testdockerswarm-service
  labels:  
    name: testdockerswarm-service
spec:
  selector:
    app: testdockerswarm #与template-labels参数pod标签一致
  ports:
    - protocol: TCP
      port: 80 #clusterIP开放的端口
      targetPort: 80 #container开放的端口,与containerPort一致
      nodePort: 31221 # 所有的节点都会开放此端口,此端口供外部调用。
  type: NodePort

把yaml文件保存到服务器后执行下面命令

kubectl create -f testdockerswarm.yml

整个搭建部署的过程基本上到这里结束了。

访问

可以通过指令kubectl get service得到ClusterIP,分别在server-c和sever-b执行curl 10.10.184.184

也可以通过执行kubectl get pods -o wide得到pod ip,在server-c执行curl 10.122.2.5 和 server-b执行curl 10.122.1.7

也可以在外部访问 server-c和server-b的 ip + 31221

如果节点有异常可以通过下面指令排查

journalctl -f -u kubelet.service | grep -i error -C 500

如果Pod无法正常running可以通过下面指令查看

kubectl describe pod testdockerswarm-deployment-7bc647d87d-qwvzm

 

Kubernetes Controller详解 - deachiy - 博客园

mikel阅读(1012)

来源: Kubernetes Controller详解 – deachiy – 博客园


运行容器化应用是Kubernetes最重要的核心功能。为满足不同的业务需要,Kubernetes提供了多种Controller,主要包括Deployment、DaemonSet、Job、CronJob等。
1、创建资源的两种方式
创建资源主要有通过命令行配置参数和通过配置文件这两种方式。
通过命令行主要是使用kubectl命令来进行创建,主要可能用到的是kubectl run和kubectl create,具体的用法我们可以在命令后面加上–-help参数来查看帮助文档。
这种方式的好处就是简单快捷,部署的速度比较快,但是遇到要求比较复杂多样的资源部署,后面就要附带一大串参数,容易出错,所以这种方式一般来说比较适用于小规模的简单资源部署或者是上线前的简单测试
通过配置文件则主要是json格式或yaml格式的文件,好处是可以详细配置各种参数,保留的配置文件还可以用到其他的集群上进行大规模的部署操作,缺点就是部署比较麻烦,并且需要一定的门槛(要求对json或yaml有一定的了解)
配置文件主要是通过kubectl apply -f和kubectl create -f来进行配置。
2、Deployment
2.1 cli部署
我们先使用命令行(cli)创建一个deployment,名字(NAME)是nginx-clideployment,使用的镜像(image)版本为1.17,创建的副本(replicas)数量为3。
kubectl run nginx-clideployment –image=nginx:1.17 –replicas=3
我们查看一下部署是否成功:

这里的kubectl get rs中的rs其实就是ReplicaSet(RS)
我们还可以发现ReplicaSet的命名就是在我们指定的NAME后面加上了一串哈希数值。
-.
想要查看更详细的pod情况,我们可以这样:
kubectl get pod -o wide

这里我们可以发现三个pod被k8s自动的分配到了三个节点上而实现集群中的负载均衡(LB),而这里的IP是创建pod的时候进行随机分配的,我们并不能预知。
如果想要查看某一个pod部署之后的情况,我们可以这样:
kubectl describe pods nginx-clideployment-5696d55d9d-gdtrt
如果我们只是输入nginx-clideployment的话就会把所有相关的nginx-clideployment都列出来。

这里的信息很多,我们可以看到namespace是使用的默认default(一般都是default,除非是数十人以上在同时使用这个集群,否则一般不建议新建namespace来区分pod,大多数情况下使用label即可区分各类pod。)
Controlled By:则说明了这个deployment是由ReplicaSet控制的。
Conditions:则表明了当前的pod状况

Volumes称之为卷,这个概念我们暂时还没有接触到,等到我们的宿主机需要与容器内的服务进行数据交互的时候再进行了解。
Events则相当于log,记录了pod运行的所有情况,如果遇到了运行不正常的情况,我们也可以查看这里来了解详情。

2.2 yaml配置文件部署
接下来我们尝试使用yaml文件来进行部署,新建一个文件,命名为nginx-yamldeployment.yml

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-yamldeployment
spec:
replicas: 5
template:
metadata:
labels:
app: web_server
spec:
containers:
– name: nginx
image: nginx:1.17.1
apiVersion是当前配置格式的版本。
kind是要创建的资源类型,这里是Deployment。
metadata是该资源的元数据,name是必需的元数据项。
spec部分是该Deployment的规格说明。
replicas 指明副本数量,默认为1。
template 定义Pod的模板,这是配置文件的重要部分。
metadata定义Pod的元数据,至少要定义一个label。label的key和value可以任意指定。
spec 描述Pod的规格,此部分定义Pod中每一个容器的属性,name和image是必需的。
接下来我们使用kubectl apply指令进行部署。
kubectl apply -f nginx-yamldeployment.yml


我们可以看到这里是已经运行正常了,那么如果我们想要修改这个deployment的属性,比如说副本数量从5改为6,那么我们可以直接编辑刚刚的nginx-yamldeployment.yml文件,然后再执行kubectl apply -f nginx-yamldeployment.yml,但是如果文件找不到了,我们可以使用另外的方法:
kubectl edit deployments.apps nginx-yamldeployment
这样子我们就能直接编辑这个deployment的yaml配置文件,编辑的操作方式和vim相同,修改完成后会自动生效。

我们修改保存退出后,可以看到这里已经生效了。

对于使用命令行创建的deployment,我们可以使用命令行来进行修改,也可以直接kubectl edit来编辑对应的yaml配置文件。

3、DaemonSet
我们先来看一下官网对DaemonSet的解释:
DaemonSet 确保全部(或者某些)节点上运行一个 Pod 的副本。当有节点加入集群时,也会为他们新增一个 Pod 。 当有节点从集群移除时,这些 Pod 也会被回收。删除 DaemonSet 将会删除它创建的所有 Pod。
使用 DaemonSet 的一些典型用法:
运行集群存储 daemon,例如在每个节点上运行 glusterd、ceph。
在每个节点上运行日志收集 daemon,例如fluentd、logstash。
在每个节点上运行监控 daemon,例如 Prometheus Node Exporter、collectd、Datadog 代理、New Relic 代理,或 Ganglia gmond。
一个简单的用法是在所有的节点上都启动一个 DaemonSet,将被作为每种类型的 daemon 使用。 一个稍微复杂的用法是单独对每种 daemon 类型使用多个 DaemonSet,但具有不同的标志,和/或对不同硬件类型具有不同的内存、CPU要求。
实际上,k8s本身的一些系统组件服务就是以DaemonSet的形式运行在各个节点上的。

4、Job
4.1 部署job
Job创建一个或多个Pod并确保指定数量的Pod成功终止。当pod成功完成后,Job会跟踪成功的完成情况。达到指定数量的成功完成时,Job完成。
删除Job将清理它创建的Pod。
一个简单的例子是创建一个Job对象,以便可靠地运行一个Pod并成功完成指定任务。如果第一个Pod失败或被删除(例如由于节点硬件故障或节点重启),Job对象将启动一个新的Pod。
我们还可以使用Job并行运行多个Pod。
直接照搬官网的解释可能会有些难以理解,我们来运行一个例子就能很好的说明情况了。
我们先新建一个job的配置文件,命名为pijob.yml,这个任务是将pi计算到小数点后两千位,然后再打印出来。我们运行一下看看。

apiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
template:
spec:
containers:
– name: pi
image: perl
command: [“perl”, “-Mbignum=bpi”, “-wle”, “print bpi(2000)”]
restartPolicy: Never
backoffLimit: 4

我们查看它的详细情况
kubectl describe jobs/pi

这里可以看到,运行了37s后成功计算出结果。

查看pod也可以看到它的状态是Completed,说明已经完成了。

查看创建的pod的名称

pods=$(kubectl get pods –selector=job-name=pi –output=jsonpath='{.items[*].metadata.name}’)
echo $pods

查看pod的运行结果

kubectl logs $pods

4.2 job运行错误
我们现在来看一下运行失败的情况,我们把command里面的路径改错,使得它无法正常运行。

注意这里的restartPolicy: Never意味着pod运行失败了也不会重启。但是这个时候job会检测到运行失败,然后再新建一个pod来执行这个任务。
backoffLimit: 4意味着最多只会新建4个pod,避免一直失败一直新建从而耗尽系统资源。

我们查看event可以看到,确实是由于路径修改错误而导致无法正常运行。

接下来我们把restartPolicy: 改为 OnFailure
这时候我们可以看到并没有启动多个pod,二是对发生错误的pod进行重启操作。

4.3 job并行化
在多线程早已普及的今天,很多任务我们都可以使用并行化来进行加速运行,这里也不例外。
我们在配置文件中的spec中加入 completions: 和parallelism: 。

图中表示需要运行12个,每次并行运行6个。

从图中我们可以看到确实是每次运行6个(运行时间相同说明同时开始运行)。
等待一段时候之后,我们再次查看而已看到任务已经顺利完成了。

5、CronJob
熟悉linux的同学一定不会对cron感到陌生,因为cron就是用来管理linux中的定时任务的工具,所以CronJob我们可以理解为定时版的Job,其定时任务的编写格式也和Cron相似。关于Cron可以点击这里查看小七之前的博客。
需要注意的是,CronJob的时间以启动该CronJob任务的Master节点的时间为准。
Cronjob只负责创建与其计划相匹配的Job,而Job则负责管理它所代表的Pod。也就是说,CronJob只负责创建Job,具体的管理操作还是由Job来负责。
这里是CronJob的官方文档。
我们新建一个hellocronjob.yml来查看一下它的工作情况

apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: hello
spec:
schedule: “*/1 * * * *”
jobTemplate:
spec:
template:
spec:
containers:
– name: hello
image: busybox
args:
– /bin/sh
– -c
– date; echo Hello from the Kubernetes cluster
restartPolicy: OnFailure
这个定时任务的操作就是每分钟输出一次时间和Hello from the Kubernetes cluster。

查看cronjob的运行状态

kubectl get cronjobs.batch
kubectl get jobs.batch –watch

我们随便查看其中的一个log可以看到输出的结果:

基于redis的延迟消息队列设计 - peachyy - 博客园

mikel阅读(711)

来源: 基于redis的延迟消息队列设计 – peachyy – 博客园

需求背景

  • 用户下订单成功之后隔20分钟给用户发送上门服务通知短信
  • 订单完成一个小时之后通知用户对上门服务进行评价
  • 业务执行失败之后隔10分钟重试一次

    类似的场景比较多 简单的处理方式就是使用定时任务 假如数据比较多的时候 有的数据可能延迟比较严重,而且越来越多的定时业务导致任务调度很繁琐不好管理。

队列设计

目前可以考虑使用rabbitmq来满足需求 但是不打算使用,因为目前太多的业务使用了另外的MQ中间件。

开发前需要考虑的问题?

  • 及时性 消费端能按时收到
  • 同一时间消息的消费权重
  • 可靠性 消息不能出现没有被消费掉的情况
  • 可恢复 假如有其他情况 导致消息系统不可用了 至少能保证数据可以恢复
  • 可撤回 因为是延迟消息 没有到执行时间的消息支持可以取消消费
  • 高可用 多实例 这里指HA/主备模式并不是多实例同时一起工作
  • 消费端如何消费

    当然初步选用redis作为数据缓存的主要原因是因为redis自身支持zset的数据结构(score 延迟时间毫秒) 这样就少了排序的烦恼而且性能还很高,正好我们的需求就是按时间维度去判定执行的顺序 同时也支持map list数据结构。

简单定义一个消息数据结构

private String topic;/***topic**/
private String id;/***自动生成 全局惟一 snowflake**/
private String bizKey;
private long delay;/***延时毫秒数**/
private int priority;//优先级
private long ttl;/**消费端消费的ttl**/
private String body;/***消息体**/
private long createTime=System.currentTimeMillis();
private int status= Status.WaitPut.ordinal();

运行原理:

  1. Map来存储元数据。id作为key,整个消息结构序列化(json/…)之后作为value,放入元消息池中。
  2. id放入其中(有N个)一个zset有序列表中,以createTime+delay+priority作为score。修改状态为正在延迟中
  3. 使用timer实时监控zset有序列表中top 10的数据 。 如果数据score<=当前时间毫秒就取出来,根据topic重新放入一个新的可消费列表(list)中,在zset中删除已经取出来的数据,并修改状态为待消费
  4. 客户端获取数据只需要从可消费队列中获取就可以了。并且状态必须为待消费 运行时间需要<=当前时间的 如果不满足 重新放入zset列表中,修改状态为正在延迟。如果满足修改状态为已消费。或者直接删除元数据。

客户端

因为涉及到不同程序语言的问题,所以当前默认支持http访问方式。

  1. 添加延时消息添加成功之后返回消费唯一ID POST /push {…..消息体}
  2. 删除延时消息 需要传递消息ID GET /delete?id=
  3. 恢复延时消息 GET /reStore?expire=true|false expire是否恢复已过期未执行的消息。
  4. 恢复单个延时消息 需要传递消息ID GET /reStore/id
  5. 获取消息 需要长连接 GET /get/topic

用nginx暴露服务,配置为轮询 在添加延迟消息的时候就可以流量平均分配。

目前系统中客户端并没有采用HTTP长连接的方式来消费消息,而是采用MQ的方式来消费数据这样客户端就可以不用关心延迟消息队列。只需要在发送MQ的时候拦截一下 如果是延迟消息就用延迟消息系统处理。

消息可恢复

实现恢复的原理 正常情况下一般都是记录日志,比如mySQLbinlog等。

这里我们直接采用mySQL数据库作为记录日志。

目前打算创建以下2张表:

  1. 消息表 字段包括整个消息体
  2. 消息流转表 字段包括消息ID、变更状态、变更时间、zset扫描线程Name、host/ip

定义zset扫描线程Name是为了更清楚的看到消息被分发到具体哪个zset中。前提是zset的key和监控zset的线程名称要有点关系 这里也可以是zset key。

举个栗子

假如redis服务器宕机了,重启之后发现数据也没有了。所以这个恢复是很有必要的,只需要从表1也就是消息表中把消息状态不等于已消费的数据全部重新分发到延迟队列中去,然后同步一下状态就可以了。

当然恢复单个任务也可以这么干。

关于高可用

分布式协调还是选用zookeeper吧。

如果有多个实例最多同时只能有1个实例工作 这样就避免了分布式竞争锁带来的坏处,当然如果业务需要多个实例同时工作也是支持的,也就是一个消息最多只能有1个实例处理,可以选用zookeeper或者redis就能实现分布式锁了。

最终做了一下测试多实例同时运行,可能因为会涉及到锁的问题性能有所下降,反而单机效果很好。所以比较推荐基于docker的主备部署模式。

扩展

支持zset队列个数可配置 避免大数据带来高延迟的问题。

目前存在日志和redis元数据有可能不一致的问题 如mySQL挂了,写日志不会成功。

设计图:

 

另外分享一个不完整简陋的开源版本  https://github.com/peachyy/sdmq.git  后期会进行模块拆分 优化

rabbitMQ实现延迟消息队列 - 简书

mikel阅读(3234)

来源: rabbitMQ实现延迟消息队列 – 简书

一、延迟消息适应场景

一般延迟队列用于特定事件发生后隔一段时间需要做特定处理的场景,下面举几个常见的栗子

1.电商系统中,若用户下单后30min不支付,自动取消订单
2.用户登录APP浏览特定商品20min后还没下单,自动推送商品评测信息的消息并发放商品相关优惠券

二、rabbitMQ的延迟消息

Rabbitmq本身是没有延迟队列的,要实现延迟消息,一般有两种方式:

1.通过Rabbitmq本身队列的特性来实现,需要使用Rabbitmq的死信交换机(Exchange)和消息的存活时间TTL(Time To Live)
2.在rabbitmq 3.5.7及以上的版本提供了一个插件(rabbitmq-delayed-message-exchange)来实现延迟队列功能。同时插件依赖Erlang/OPT 18.0及以上

一个个来看

三、rabbitMQ不使用插件实现延迟消息

3.1 原理图解

rabbitMQ延迟消息原理.png

若想不借助插件实现rabbitMQ的延迟消息,实际就是利用一个没有消费者的Queue1,等待消息过期后,通过交换机转发到Queue2来进行消费,消息的延迟时间就是消息在Queue1中的存活时间

3.2 在一个传统的spring项目中配置rabbitMQ的延迟消息

3.2.1 创建一个自动过期的消息队列(Queue1)

此队列没有消费类,可以通过x-message-ttl设置消息过期的时间,默认单位是毫秒,通过x-dead-letter-exchange设置死信后转发的交换机,通过x-dead-letter-routing-key来设置死信交换机中,真正需要转发的绑定的key,例如一个延迟5分钟的延迟队列的配置可以为:

 <rabbit:queue id="delayFiveMinuteQueue"  durable="true" auto-delete="false" exclusive="false" name="delayFiveMinuteQueue">
        <rabbit:queue-arguments>
            <!-- 设置延迟队列的过期时间 -->
            <entry key="x-message-ttl" value="300000" value-type="java.lang.Integer"/>
            <entry key="x-dead-letter-exchange" value="mq-exchange" />
            <entry key="x-dead-letter-routing-key" value="delayConsumerQueue" />
        </rabbit:queue-arguments>
</rabbit:queue>
3.2.2 创建转发后的消息队列(Queue2)

此队列为转发后的队列,故需要有消费类,其配置和一般的队列保持一致,例如

<!-- 延迟消息的消费队列 -->
<rabbit:queue id="delayConsumerQueue" durable="true" auto-delete="false" exclusive="false"  name="delayConsumerQueue"/>
3.2.3在交换机上进行绑定

交换机需要绑定队列和转发的key之间的关系,故需要注意的是Queue1的x-dead-letter-routing-key一定要和交换机中Queue2绑定的key保持一致,例如上述两个队列绑定后的配置示例为:

<rabbit:direct-exchange name="mq-exchange" durable="true" auto-delete="false" id="mq-exchange">
        <rabbit:bindings>
            <rabbit:binding queue="delayConsumerQueue" key="delayConsumerQueue" />
            <rabbit:binding queue="delayFiveMinuteQueue" key="delayFiveMinuteQueue" />
        </rabbit:bindings>
</rabbit:direct-exchange>

3.3不使用插件实现延迟消息的局限性

可以看到如果不使用插件,延迟消息的延迟时间是依赖于Queue1的x-message-ttl的,也就是说,需要支持多少种延迟的时间,就得提前设置好多少个无消费类的Queue,而且由于转发绑定的Queue2需要配到交换机中,比较死板,而真实的业务中消费类肯定是不一样的,故我真正在实现的时候在发到Queue1之前把消息体及需要消费的Queue(假设名称为Queue3)进行了落库,Queue2中进行消费时,实际是查库把消息读出来然后发送到了Queue3

而如果使用插件就没有以上的局限

四、rabbitMQ使用插件实现延迟消息

4.1 插件简介

在rabbitmq 3.5.7及以上的版本提供了一个插件(rabbitmq-delayed-message-exchange)来实现延迟队列功能。同时插件依赖Erlang/OPT 18.0及以上。

插件源码地址:
https://github.com/rabbitmq/rabbitmq-delayed-message-exchange

插件下载地址:
https://bintray.com/rabbitmq/community-plugins/rabbitmq_delayed_message_exchange

4.2 插件使用方式

4.2.1 安装

进入插件安装目录

{rabbitmq-server}/plugins/(可以查看一下当前已存在的插件)

下载插件

wget https://bintray.com/rabbitmq/community-plugins/download_file?file_path=rabbitmq_delayed_message_exchange-0.0.1.ez

(如果下载的文件名称不规则就手动重命名一下如:rabbitmq_delayed_message_exchange-0.0.1.ez)

4.2.2 启用插件

rabbitmq-plugins enable rabbitmq_delayed_message_exchange

(关闭插件)
rabbitmq-plugins disable rabbitmq_delayed_message_exchange

4.2.3 插件使用

在需要发送延迟消息队列的项目中,声明一个x-delayed-message类型的交换机来使用delayed-messaging特性,注意这个交换机并不是rabbitmq本身的,而是插件提供的,一定要是x-delayed-message类型,绑定的queue就是正常的queue即可,不需要额外多余的queue(这是和不用插件方式的最大区别及好处),例如

<rabbit:direct-exchange name="mq-exchange-delayed" durable="true" auto-delete="false" id="mq-exchange-delayed" delayed="true">
    <rabbit:bindings>
         <rabbit:binding queue="demoQueue" key="demoQueue"/>
    </rabbit:bindings>
</rabbit:direct-exchange>

消息发送时,在header添加”x-delay”参数来控制消息的延时时间,如果使用的是spring-rabbit中的RabbitTemplate,只需要通过messageProperties.setDelay(delay)方法set上延迟时间即可(单位为毫秒)

作者:周维的笔记
链接:https://www.jianshu.com/p/7e5f0742c8e3
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Redis可以做哪些事? - Java旅途 - 博客园

mikel阅读(706)

来源: Redis可以做哪些事? – Java旅途 – 博客园

Redis是一种基于键值对的NoSQL数据库,它的值主要由string(字符串),hash(哈希),list(列表),set(集合),zset(有序集合)五种基本数据结构构成,除此之外还支持一些其他的数据结构和算法。key都是由字符串构成的,那么这五种数据结构的使用场景有哪些?一起来看看!

一 字符串

字符串类型是Redis最基础的数据结构,字符串类型可以是JSONXML甚至是二进制的图片等数据,但是最大值不能超过512MB。

1.1 内部编码

Redis会根据当前值的类型和长度决定使用哪种内部编码来实现。

字符串类型的内部编码有3种:

  1. int:8个字节的长整型。
  2. embstr:小于等于39个字节的字符串。
  3. raw:大于39个字节的字符串。

1.2 使用场景

1.2.1 缓存

在web服务中,使用MySQL作为数据库,Redis作为缓存。由于Redis具有支撑高并发的特性,通常能起到加速读写和降低后端压力的作用。web端的大多数请求都是从Redis中获取的数据,如果Redis中没有需要的数据,则会从MySQL中去获取,并将获取到的数据写入redis。

1.2.2 计数

Redis中有一个字符串相关的命令incr keyincr命令对值做自增操作,返回结果分为以下三种情况:

  • 值不是整数,返回错误
  • 值是整数,返回自增后的结果
  • key不存在,默认键为0,返回1

比如文章的阅读量,视频的播放量等等都会使用redis来计数,每播放一次,对应的播放量就会加1,同时将这些数据异步存储到数据库中达到持久化的目的。

1.2.3 共享Session

在分布式系统中,用户的每次请求会访问到不同的服务器,这就会导致session不同步的问题,假如一个用来获取用户信息的请求落在A服务器上,获取到用户信息后存入session。下一个请求落在B服务器上,想要从session中获取用户信息就不能正常获取了,因为用户信息的session在服务器A上,为了解决这个问题,使用redis集中管理这些session,将session存入redis,使用的时候直接从redis中获取就可以了。

1.2.4 限速

为了安全考虑,有些网站会对IP进行限制,限制同一IP在一定时间内访问次数不能超过n次。

二 哈希

Redis中,哈希类型是指一个键值对的存储结构。

2.1 内部编码

哈希类型的内部编码有两种:

  • ziplist(压缩列表):当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个)同时所有值都小于hash-max-ziplist-value配置(默认64字节)时使用。ziplist使用更加紧凑的结构实现多个元素的连续存储,所以比hashtable更加节省内存。
  • hashtable(哈希表):当ziplist不能满足要求时,会使用hashtable。

2.2 使用场景

由于hash类型存储的是一个键值对,比如数据库有以下一个用户表结构

id name age
1 Java旅途 18

将以上信息存入redis,用表明:id作为key,用户属性作为值:

hset user:1 name Java旅途 age 18

使用哈希存储会比字符串更加方便直观

三 列表

列表类型用来存储多个有序的字符串,一个列表最多可以存储2^32-1个元素,列表的两端都可以插入和弹出元素。

3.1 内部编码

列表的内部编码有两种:

  • ziplist(压缩列表):当哈希类型元素个数小于list-max-ziplist-entries配置(默认512个)同时所有值都小于list-max-ziplist-value配置(默认64字节)时使用。ziplist使用更加紧凑的结构实现多个元素的连续存储,所以比hashtable更加节省内存。
  • linkedlist(链表):当ziplist不能满足要求时,会使用linkedlist。

3.2 使用场景

3.2.1 消息队列

列表用来存储多个有序的字符串,既然是有序的,那么就满足消息队列的特点。使用lpush+rpop或者rpush+lpop实现消息队列。除此之外,redis支持阻塞操作,在弹出元素的时候使用阻塞命令来实现阻塞队列。

3.2.2 栈

由于列表存储的是有序字符串,满足队列的特点,也就能满足栈先进后出的特点,使用lpush+lpop或者rpush+rpop实现栈。

3.2.3 文章列表

因为列表的元素不但是有序的,而且还支持按照索引范围获取元素。因此我们可以使用命令lrange key 0 9分页获取文章列表

四 集合

集合类型也可以保存多个字符串元素,与列表不同的是,集合中不允许有重复元素并且集合中的元素是无序的。一个集合最多可以存储2^32-1个元素。

4.1 内部编码

集合类型的内部编码有两种:

  • intset(整数集合):当集合中的元素都是整数且元素个数小于set-max-intset-entries配置(默认512个)时,redis会选用intset来作为集合的内部实现,从而减少内存的使用。
  • hashtable(哈希表):当intset不能满足要求时,会使用hashtable。

4.2 使用场景

4.2.1 用户标签

例如一个用户对篮球、足球感兴趣,另一个用户对橄榄球、乒乓球感兴趣,这些兴趣点就是一个标签。有了这些数据就可以得到喜欢同一个标签的人,以及用户的共同感兴趣的标签。给用户打标签的时候需要①给用户打标签,②给标签加用户,需要给这两个操作增加事务。

  • 给用户打标签
sadd user:1:tags tag1 tag2
  • 给标签添加用户
sadd tag1:users user:1

sadd tag2:users user:1

使用交集(sinter)求两个user的共同标签

sinter user:1:tags user:2:tags

4.2.2 抽奖功能

集合有两个命令支持获取随机数,分别是:

  • 随机获取count个元素,集合元素个数不变

srandmember key [count]

  • 随机弹出count个元素,元素从集合弹出,集合元素个数改变

spop key [count]

用户点击抽奖按钮,参数抽奖,将用户编号放入集合,然后抽奖,分别抽一等奖、二等奖,如果已经抽中一等奖的用户不能参数抽二等奖则使用spop,反之使用srandmember

五 有序集合

有序集合和集合一样,不能有重复元素。但是可以排序,它给每个元素设置一个score作为排序的依据。最多可以存储2^32-1个元素。

5.1 内部编码

有序集合类型的内部编码有两种:

  • ziplist(压缩列表):当有序集合的元素个数小于list-max-ziplist-entries配置(默认128个)同时所有值都小于list-max-ziplist-value配置(默认64字节)时使用。ziplist使用更加紧凑的结构实现多个元素的连续存储,更加节省内存。
  • skiplist(跳跃表):当不满足ziplist的要求时,会使用skiplist。

5.2 使用场景

5.2.1 排行榜

用户发布了n篇文章,其他人看到文章后给喜欢的文章点赞,使用score来记录点赞数,有序集合会根据score排行。流程如下

用户发布一篇文章,初始点赞数为0,即score为0

zadd user:article 0 a

有人给文章a点赞,递增1

zincrby user:article 1 a

查询点赞前三篇文章

zrevrangebyscore user:article 0 2

查询点赞后三篇文章

zrangebyscore user:article 0 2

5.2.2 延迟消息队列

下单系统,下单后需要在15分钟内进行支付,如果15分钟未支付则自动取消订单。将下单后的十五分钟后时间作为score,订单作为value存入redis,消费者轮询去消费,如果消费的大于等于这笔记录的score,则将这笔记录移除队列,取消订单。

总结

在开发中,字符串类型是用的最多的数据类型,导致我们忽视了redis的其他四种数据类型,在具体场景下选择具体的数据类型对提升redis性能有非常大的帮助。redis虽然支持消息队列的实现,但是并不支持ack。所以redis实现的消息队列不能保证消息的可靠性,除非自己实现消息确认机制,不过这非常麻烦,所以如果是重要的消息还是推荐使用专门的消息队列去做。

怎么实现员工和工资大数据分析,echarts+js实现 - 气宇轩昂_2017 - 博客园

mikel阅读(990)

来源: 怎么实现员工和工资大数据分析,echarts+js实现 – 气宇轩昂_2017 – 博客园

前言

现如今市场上的人事系统五花八门,可做了大数据分析的人事系统少之又少,最近本人花了一个星期好好研究了大数据展示方面的内容,图表主要用的是echarts来实现的,官网地址:https://echarts.apache.org/zh/index.html

下面两张图片展示出我实现的员工和工资的大数据分析界面:

员工大数据分析中心

工资大数据分析中心

如何实现漂亮的图表

地图实现

首先引入js文件,去官网可以下载到这几个文件,引入到项目中就好了。

<script type="text/javascript" src="~/showdata/js/jquery.js"></script>
<script type="text/javascript" src="~/showdata/js/echarts.min.js"></script>
<script type="text/javascript" src="~/showdata/js/china.js"></script>
<script type="text/javascript" src="~/showdata/js/area_echarts_hr.js">

图层引入

<div class="map4" id="map_1"></div>

地图实现的js

$(function () {
map();
})
var lightProvince = [];
var provinceMap = {
台湾: ‘taiwan’,
河北: ‘hebei’,
山西: ‘shanxi’,
辽宁: ‘liaoning’,
吉林: ‘jilin’,
黑龙江: ‘heilongjiang’,
江苏: ‘jiangsu’,
浙江: ‘zhejiang’,
安徽: ‘anhui’,
福建: ‘fujian’,
江西: ‘jiangxi’,
山东: ‘shandong’,
河南: ‘henan’,
湖北: ‘hubei’,
湖南: ‘hunan’,
广东: ‘guangdong’,
海南: ‘hainan’,
四川: ‘sichuan’,
贵州: ‘guizhou’,
云南: ‘yunnan’,
陕西: ‘shanxi1’,
甘肃: ‘gansu’,
青海: ‘qinghai’,
新疆: ‘xinjiang’,
广西: ‘guangxi’,
内蒙古: ‘neimenggu’,
宁夏: ‘ningxia’,
西藏: ‘xizang’,
北京: ‘beijing’,
天津: ‘tianjin’,
上海: ‘shanghai’,
重庆: ‘chongqing’,
香港: ‘xianggang’,
澳门: ‘aomen’
};
var py_provinceMap = {
china: ‘全国’,
hebei: ‘河北’,
shanxi: ‘山西’,
liaoning: ‘辽宁’,
jilin: ‘吉林’,
heilongjiang: ‘黑龙江’,
jiangsu: ‘江苏’,
zhejiang: ‘浙江’,
anhui: ‘安徽’,
fujian: ‘福建’,
jiangxi: ‘江西’,
shandong: ‘山东’,
henan: ‘河南’,
hubei: ‘湖北’,
hunan: ‘湖南’,
guangdong: ‘广东’,
hainan: ‘海南’,
sichuan: ‘四川’,
guizhou: ‘贵州’,
yunnan: ‘云南’,
shanxi: ‘陕西’,
gansu: ‘甘肃’,
qinghai: ‘青海’,
xinjiang: ‘新疆’,
guangxi: ‘广西’,
neimenggu: ‘内蒙古’,
ningxia: ‘宁夏’,
xizang: ‘西藏’,
beijing: ‘北京’,
tianjin: ‘天津’,
shanghai: ‘上海’,
chongqing: ‘重庆’
};
var cityMap = {
北京市: ‘110100’,
天津市: ‘120100’,
上海市: ‘310100’,
重庆市: ‘500100’,
崇明县: ‘310200’,
湖北省直辖县市: ‘429000’,
铜仁市: ‘522200’,
毕节市: ‘522400’,
石家庄市: ‘130100’,
唐山市: ‘130200’,
秦皇岛市: ‘130300’,
邯郸市: ‘130400’,
邢台市: ‘130500’,
保定市: ‘130600’,
张家口市: ‘130700’,
承德市: ‘130800’,
沧州市: ‘130900’,
廊坊市: ‘131000’,
衡水市: ‘131100’,
太原市: ‘140100’,
大同市: ‘140200’,
阳泉市: ‘140300’,
长治市: ‘140400’,
晋城市: ‘140500’,
朔州市: ‘140600’,
晋中市: ‘140700’,
运城市: ‘140800’,
忻州市: ‘140900’,
临汾市: ‘141000’,
吕梁市: ‘141100’,
呼和浩特市: ‘150100’,
包头市: ‘150200’,
乌海市: ‘150300’,
赤峰市: ‘150400’,
通辽市: ‘150500’,
鄂尔多斯市: ‘150600’,
呼伦贝尔市: ‘150700’,
巴彦淖尔市: ‘150800’,
乌兰察布市: ‘150900’,
兴安盟: ‘152200’,
锡林郭勒盟: ‘152500’,
阿拉善盟: ‘152900’,
沈阳市: ‘210100’,
大连市: ‘210200’,
鞍山市: ‘210300’,
抚顺市: ‘210400’,
本溪市: ‘210500’,
丹东市: ‘210600’,
锦州市: ‘210700’,
营口市: ‘210800’,
阜新市: ‘210900’,
辽阳市: ‘211000’,
盘锦市: ‘211100’,
铁岭市: ‘211200’,
朝阳市: ‘211300’,
葫芦岛市: ‘211400’,
长春市: ‘220100’,
吉林市: ‘220200’,
四平市: ‘220300’,
辽源市: ‘220400’,
通化市: ‘220500’,
白山市: ‘220600’,
松原市: ‘220700’,
白城市: ‘220800’,
延边朝鲜族自治州: ‘222400’,
哈尔滨市: ‘230100’,
齐齐哈尔市: ‘230200’,
鸡西市: ‘230300’,
鹤岗市: ‘230400’,
双鸭山市: ‘230500’,
大庆市: ‘230600’,
伊春市: ‘230700’,
佳木斯市: ‘230800’,
七台河市: ‘230900’,
牡丹江市: ‘231000’,
黑河市: ‘231100’,
绥化市: ‘231200’,
大兴安岭地区: ‘232700’,
南京市: ‘320100’,
无锡市: ‘320200’,
徐州市: ‘320300’,
常州市: ‘320400’,
苏州市: ‘320500’,
南通市: ‘320600’,
连云港市: ‘320700’,
淮安市: ‘320800’,
盐城市: ‘320900’,
扬州市: ‘321000’,
镇江市: ‘321100’,
泰州市: ‘321200’,
宿迁市: ‘321300’,
杭州市: ‘330100’,
宁波市: ‘330200’,
温州市: ‘330300’,
嘉兴市: ‘330400’,
湖州市: ‘330500’,
绍兴市: ‘330600’,
金华市: ‘330700’,
衢州市: ‘330800’,
舟山市: ‘330900’,
台州市: ‘331000’,
丽水市: ‘331100’,
合肥市: ‘340100’,
芜湖市: ‘340200’,
蚌埠市: ‘340300’,
淮南市: ‘340400’,
马鞍山市: ‘340500’,
淮北市: ‘340600’,
铜陵市: ‘340700’,
安庆市: ‘340800’,
黄山市: ‘341000’,
滁州市: ‘341100’,
阜阳市: ‘341200’,
宿州市: ‘341300’,
六安市: ‘341500’,
亳州市: ‘341600’,
池州市: ‘341700’,
宣城市: ‘341800’,
福州市: ‘350100’,
厦门市: ‘350200’,
莆田市: ‘350300’,
三明市: ‘350400’,
泉州市: ‘350500’,
漳州市: ‘350600’,
南平市: ‘350700’,
龙岩市: ‘350800’,
宁德市: ‘350900’,
南昌市: ‘360100’,
景德镇市: ‘360200’,
萍乡市: ‘360300’,
九江市: ‘360400’,
新余市: ‘360500’,
鹰潭市: ‘360600’,
赣州市: ‘360700’,
吉安市: ‘360800’,
宜春市: ‘360900’,
抚州市: ‘361000’,
上饶市: ‘361100’,
济南市: ‘370100’,
青岛市: ‘370200’,
淄博市: ‘370300’,
枣庄市: ‘370400’,
东营市: ‘370500’,
烟台市: ‘370600’,
潍坊市: ‘370700’,
济宁市: ‘370800’,
泰安市: ‘370900’,
威海市: ‘371000’,
日照市: ‘371100’,
莱芜市: ‘371200’,
临沂市: ‘371300’,
德州市: ‘371400’,
聊城市: ‘371500’,
滨州市: ‘371600’,
菏泽市: ‘371700’,
郑州市: ‘410100’,
开封市: ‘410200’,
洛阳市: ‘410300’,
平顶山市: ‘410400’,
安阳市: ‘410500’,
鹤壁市: ‘410600’,
新乡市: ‘410700’,
焦作市: ‘410800’,
濮阳市: ‘410900’,
许昌市: ‘411000’,
漯河市: ‘411100’,
三门峡市: ‘411200’,
南阳市: ‘411300’,
商丘市: ‘411400’,
信阳市: ‘411500’,
周口市: ‘411600’,
驻马店市: ‘411700’,
省直辖县级行政区划: ‘469000’,
武汉市: ‘420100’,
黄石市: ‘420200’,
十堰市: ‘420300’,
宜昌市: ‘420500’,
襄阳市: ‘420600’,
鄂州市: ‘420700’,
荆门市: ‘420800’,
孝感市: ‘420900’,
荆州市: ‘421000’,
黄冈市: ‘421100’,
咸宁市: ‘421200’,
随州市: ‘421300’,
恩施土家族苗族自治州: ‘422800’,
长沙市: ‘430100’,
株洲市: ‘430200’,
湘潭市: ‘430300’,
衡阳市: ‘430400’,
邵阳市: ‘430500’,
岳阳市: ‘430600’,
常德市: ‘430700’,
张家界市: ‘430800’,
益阳市: ‘430900’,
郴州市: ‘431000’,
永州市: ‘431100’,
怀化市: ‘431200’,
娄底市: ‘431300’,
湘西土家族苗族自治州: ‘433100’,
广州市: ‘440100’,
韶关市: ‘440200’,
深圳市: ‘440300’,
珠海市: ‘440400’,
汕头市: ‘440500’,
佛山市: ‘440600’,
江门市: ‘440700’,
湛江市: ‘440800’,
茂名市: ‘440900’,
肇庆市: ‘441200’,
惠州市: ‘441300’,
梅州市: ‘441400’,
汕尾市: ‘441500’,
河源市: ‘441600’,
阳江市: ‘441700’,
清远市: ‘441800’,
东莞市: ‘441900’,
中山市: ‘442000’,
潮州市: ‘445100’,
揭阳市: ‘445200’,
云浮市: ‘445300’,
南宁市: ‘450100’,
柳州市: ‘450200’,
桂林市: ‘450300’,
梧州市: ‘450400’,
北海市: ‘450500’,
防城港市: ‘450600’,
钦州市: ‘450700’,
贵港市: ‘450800’,
玉林市: ‘450900’,
百色市: ‘451000’,
贺州市: ‘451100’,
河池市: ‘451200’,
来宾市: ‘451300’,
崇左市: ‘451400’,
海口市: ‘460100’,
三亚市: ‘460200’,
三沙市: ‘460300’,
成都市: ‘510100’,
自贡市: ‘510300’,
攀枝花市: ‘510400’,
泸州市: ‘510500’,
德阳市: ‘510600’,
绵阳市: ‘510700’,
广元市: ‘510800’,
遂宁市: ‘510900’,
内江市: ‘511000’,
乐山市: ‘511100’,
南充市: ‘511300’,
眉山市: ‘511400’,
宜宾市: ‘511500’,
广安市: ‘511600’,
达州市: ‘511700’,
雅安市: ‘511800’,
巴中市: ‘511900’,
资阳市: ‘512000’,
阿坝藏族羌族自治州: ‘513200’,
甘孜藏族自治州: ‘513300’,
凉山彝族自治州: ‘513400’,
贵阳市: ‘520100’,
六盘水市: ‘520200’,
遵义市: ‘520300’,
安顺市: ‘520400’,
黔西南布依族苗族自治州: ‘522300’,
黔东南苗族侗族自治州: ‘522600’,
黔南布依族苗族自治州: ‘522700’,
昆明市: ‘530100’,
曲靖市: ‘530300’,
玉溪市: ‘530400’,
保山市: ‘530500’,
昭通市: ‘530600’,
丽江市: ‘530700’,
普洱市: ‘530800’,
临沧市: ‘530900’,
楚雄彝族自治州: ‘532300’,
红河哈尼族彝族自治州: ‘532500’,
文山壮族苗族自治州: ‘532600’,
西双版纳傣族自治州: ‘532800’,
大理白族自治州: ‘532900’,
德宏傣族景颇族自治州: ‘533100’,
怒江傈僳族自治州: ‘533300’,
迪庆藏族自治州: ‘533400’,
拉萨市: ‘540100’,
昌都地区: ‘542100’,
山南地区: ‘542200’,
日喀则地区: ‘542300’,
那曲地区: ‘542400’,
阿里地区: ‘542500’,
林芝地区: ‘542600’,
西安市: ‘610100’,
铜川市: ‘610200’,
宝鸡市: ‘610300’,
咸阳市: ‘610400’,
渭南市: ‘610500’,
延安市: ‘610600’,
汉中市: ‘610700’,
榆林市: ‘610800’,
安康市: ‘610900’,
商洛市: ‘611000’,
兰州市: ‘620100’,
嘉峪关市: ‘620200’,
金昌市: ‘620300’,
白银市: ‘620400’,
天水市: ‘620500’,
武威市: ‘620600’,
张掖市: ‘620700’,
平凉市: ‘620800’,
酒泉市: ‘620900’,
庆阳市: ‘621000’,
定西市: ‘621100’,
陇南市: ‘621200’,
临夏回族自治州: ‘622900’,
甘南藏族自治州: ‘623000’,
西宁市: ‘630100’,
海东地区: ‘632100’,
海北藏族自治州: ‘632200’,
黄南藏族自治州: ‘632300’,
海南藏族自治州: ‘632500’,
果洛藏族自治州: ‘632600’,
玉树藏族自治州: ‘632700’,
海西蒙古族藏族自治州: ‘632800’,
银川市: ‘640100’,
石嘴山市: ‘640200’,
吴忠市: ‘640300’,
固原市: ‘640400’,
中卫市: ‘640500’,
乌鲁木齐市: ‘650100’,
克拉玛依市: ‘650200’,
吐鲁番地区: ‘652100’,
哈密地区: ‘652200’,
昌吉回族自治州: ‘652300’,
博尔塔拉蒙古自治州: ‘652700’,
巴音郭楞蒙古自治州: ‘652800’,
阿克苏地区: ‘652900’,
克孜勒苏柯尔克孜自治州: ‘653000’,
喀什地区: ‘653100’,
和田地区: ‘653200’,
伊犁哈萨克自治州: ‘654000’,
塔城地区: ‘654200’,
阿勒泰地区: ‘654300’,
自治区直辖县级行政区划: ‘659000’,
台湾省: ‘710000’,
香港特别行政区: ‘810100’,
澳门特别行政区: ‘820000’
};

var lightColor = {
河北: ‘#ffa259’,
山西: ‘#fe6845’,
辽宁: ‘#fa4252’,
吉林: ‘#3fc5f0’,
黑龙江: ‘#c72c41’,
江苏: ‘#f4efd3’,
浙江: ‘#c72c41’,
安徽: ‘#e13a9d’,
福建: ‘#e13a9d’,
江西: ‘#cf56a1’,
山东: ‘#fa697c’,
河南: ‘#a3f7bf’,
湖北: ‘#3ed4ff’,
湖南: ‘#9dab86’,
广东: ‘#a6c84c’,
海南: ‘#ffa259’,
四川: ‘#cfb495’,
贵州: ‘#f09595’,
云南: ‘#4f98ca’,
陕西: ‘#617be3’,
甘肃: ‘#9aceff’,
青海: ‘#9aceff’,
新疆: ‘#02a8a8’,
广西: ‘#fbdff0’,
内蒙古: ‘#42e6a4’,
宁夏: ‘#02a8a8’,
西藏: ‘#f6c3e5’,
北京: ‘#a278b5’,
天津: ‘#d6e5fa’,
上海: ‘#fbe3b9’,
重庆: ‘#bac7a7’
};

var geoCoordMap = {
‘新疆’: [86.22, 44.30],
‘九江’: [116.00, 29.70],
‘新乡’: [116.402217, 35.311657],
‘ ‘: [79.92, 37.12],
‘ ‘: [86.85, 47.70],
‘若羌县’: [88.17, 39.02],
‘上海’: [121.4648, 31.2891],
‘东莞’: [113.8953, 22.901],
‘东营’: [118.7073, 37.5513],
‘中山’: [113.4229, 22.478],
‘临汾’: [111.4783, 36.1615],
‘临沂’: [118.3118, 35.2936],
‘丹东’: [124.541, 40.4242],
‘丽水’: [119.5642, 28.1854],
‘乌鲁木齐’: [87.9236, 43.5883],
‘佛山’: [112.8955, 23.1097],
‘保定’: [115.0488, 39.0948],
‘兰州’: [103.5901, 36.3043],
‘包头’: [110.3467, 41.4899],
‘北京’: [116.4551, 40.2539],
‘北海’: [109.314, 21.6211],
‘南京’: [118.8062, 31.9208],
‘南宁’: [108.479, 23.1152],
‘南昌’: [116.0046, 28.6633],
‘南通’: [121.1023, 32.1625],
‘厦门’: [118.1689, 24.6478],
‘台州’: [121.1353, 28.6688],
‘合肥’: [117.29, 32.0581],
‘呼和浩特’: [111.4124, 40.4901],
‘咸阳’: [108.4131, 34.8706],
‘哈尔滨’: [127.9688, 45.368],
‘唐山’: [118.4766, 39.6826],
‘嘉兴’: [120.9155, 30.6354],
‘大同’: [113.7854, 39.8035],
‘大连’: [122.2229, 39.4409],
‘天津’: [117.4219, 39.4189],
‘太原’: [112.3352, 37.9413],
‘威海’: [121.9482, 37.1393],
‘宁波’: [121.5967, 29.6466],
‘宝鸡’: [107.1826, 34.3433],
‘宿迁’: [118.5535, 33.7775],
‘常州’: [119.4543, 31.5582],
‘广州’: [113.5107, 23.2196],
‘廊坊’: [116.521, 39.0509],
‘延安’: [109.1052, 36.4252],
‘张家口’: [115.1477, 40.8527],
‘徐州’: [117.5208, 34.3268],
‘德州’: [116.6858, 37.2107],
‘惠州’: [114.6204, 23.1647],
‘成都’: [103.9526, 30.7617],
‘扬州’: [119.4653, 32.8162],
‘承德’: [117.5757, 41.4075],
‘拉萨’: [91.1865, 30.1465],
‘无锡’: [120.3442, 31.5527],
‘日照’: [119.2786, 35.5023],
‘昆明’: [102.9199, 25.4663],
‘杭州’: [119.5313, 29.8773],
‘枣庄’: [117.323, 34.8926],
‘柳州’: [109.3799, 24.9774],
‘株洲’: [113.5327, 27.0319],
‘武汉’: [114.3896, 30.6628],
‘汕头’: [117.1692, 23.3405],
‘江门’: [112.6318, 22.1484],
‘沈阳’: [123.1238, 42.1216],
‘沧州’: [116.8286, 38.2104],
‘河源’: [114.917, 23.9722],
‘泉州’: [118.3228, 25.1147],
‘泰安’: [117.0264, 36.0516],
‘泰州’: [120.0586, 32.5525],
‘济南’: [117.1582, 36.8701],
‘济宁’: [116.8286, 35.3375],
‘海口’: [110.3893, 19.8516],
‘淄博’: [118.0371, 36.6064],
‘淮安’: [118.927, 33.4039],
‘深圳’: [114.5435, 22.5439],
‘清远’: [112.9175, 24.3292],
‘温州’: [120.498, 27.8119],
‘渭南’: [109.7864, 35.0299],
‘湖州’: [119.8608, 30.7782],
‘湘潭’: [112.5439, 27.7075],
‘滨州’: [117.8174, 37.4963],
‘潍坊’: [119.0918, 36.524],
‘烟台’: [120.7397, 37.5128],
‘玉溪’: [101.9312, 23.8898],
‘珠海’: [113.7305, 22.1155],
‘盐城’: [120.2234, 33.5577],
‘盘锦’: [121.9482, 41.0449],
‘石家庄’: [114.4995, 38.1006],
‘福州’: [119.4543, 25.9222],
‘秦皇岛’: [119.2126, 40.0232],
‘绍兴’: [120.564, 29.7565],
‘聊城’: [115.9167, 36.4032],
‘肇庆’: [112.1265, 23.5822],
‘舟山’: [122.2559, 30.2234],
‘苏州’: [120.6519, 31.3989],
‘莱芜’: [117.6526, 36.2714],
‘菏泽’: [115.6201, 35.2057],
‘营口’: [122.4316, 40.4297],
‘葫芦岛’: [120.1575, 40.578],
‘衡水’: [115.8838, 37.7161],
‘衢州’: [118.6853, 28.8666],
‘西宁’: [101.4038, 36.8207],
‘西安’: [109.1162, 34.2004],
‘贵阳’: [106.6992, 26.7682],
‘连云港’: [119.1248, 34.552],
‘邢台’: [114.8071, 37.2821],
‘邯郸’: [114.4775, 36.535],
‘郑州’: [113.4668, 34.6234],
‘鄂尔多斯’: [108.9734, 39.2487],
‘重庆’: [107.7539, 30.1904],
‘金华’: [120.0037, 29.1028],
‘铜川’: [109.0393, 35.1947],
‘银川’: [106.3586, 38.1775],
‘镇江’: [119.4763, 31.9702],
‘长春’: [125.8154, 44.2584],
‘长沙’: [113.0823, 28.2568],
‘长治’: [112.8625, 36.4746],
‘阳泉’: [113.4778, 38.0951],
‘青岛’: [120.4651, 36.3373],
‘韶关’: [113.7964, 24.7028]
};

var provinces = [‘shanghai’, ‘hebei’, ‘shanxi’, ‘neimenggu’, ‘liaoning’, ‘jilin’, ‘heilongjiang’, ‘jiangsu’, ‘zhejiang’, ‘anhui’, ‘fujian’, ‘jiangxi’, ‘shandong’, ‘henan’, ‘hubei’, ‘hunan’, ‘guangdong’, ‘guangxi’, ‘hainan’, ‘sichuan’, ‘guizhou’, ‘yunnan’, ‘xizang’, ‘shanxi1’, ‘gansu’, ‘qinghai’, ‘ningxia’, ‘xinjiang’, ‘beijing’, ‘tianjin’, ‘chongqing’, ‘xianggang’, ‘aomen’, ‘taiwan’]
var provincesText = [‘上海’, ‘河北’, ‘山西’, ‘内蒙古’, ‘辽宁’, ‘吉林’, ‘黑龙江’, ‘江苏’, ‘浙江’, ‘安徽’, ‘福建’, ‘江西’, ‘山东’, ‘河南’, ‘湖北’, ‘湖南’, ‘广东’, ‘广西’, ‘海南’, ‘四川’, ‘贵州’, ‘云南’, ‘西藏’, ‘陕西’, ‘甘肃’, ‘青海’, ‘宁夏’, ‘新疆’, ‘北京’, ‘天津’, ‘重庆’, ‘香港’, ‘澳门’, ‘台湾’]
var planePath = ‘path://M.6,1318.313v-89.254l-319.9-221.799l0.073-208.063c0.521-84.662-26.629-121.796-63.961-121.491c-37.332-0.305-64.482,36.829-63.961,121.491l0.073,208.063l-319.9,221.799v89.254l330.343-157.288l12.238,241.308l-134.449,92.931l0.531,42.034l175.125-42.917l175.125,42.917l0.531-42.034l-134.449-92.931l12.238-241.308L1705’;
var convertData = function (data) {
var res = [];
for (var i = 0; i < data.length; i++) {
var dataItem = data[i];
var fromCoord = geoCoordMap[dataItem.fromName];
var toCoord = geoCoordMap[dataItem.toName];
if (fromCoord && toCoord) {
res.push({
fromName: dataItem.fromName,
toName: dataItem.toName,
coords: [fromCoord, toCoord]
});
}
}
return res;
};
function SetMap() {
var url = ‘../showdata/mapset.html’;
$(“#iframeWin”).show();
//var name = ‘add’;
//var iWidth = 500;
//var iHeight = 500;
////获得窗口的垂直位置
//var iTop = (window.screen.availHeight – 30 – iHeight) / 2;
////获得窗口的水平位置
//var iLeft = (window.screen.availWidth – 10 – iWidth) / 2;
//window.open(url, name, ‘height=’ + iHeight + ‘,,innerHeight=’ + iHeight + ‘,width=’ + iWidth + ‘,innerWidth=’ + iWidth + ‘,top=’ + iTop + ‘,left=’ + iLeft + ‘,status=no,toolbar=no,menubar=no,location=no,resizable=no,scrollbars=0,titlebar=no’);
};
var series = [];
var mapSelected = ‘china’;
var showMapText = false;
var count = 0;
var seriesIndex;
var dataLength;
var dataIndex;
var timeTicket;
var geoData = {};
var mapJsonData;
var color = [‘#ffa259’, ‘#fe6845’, ‘#fa4252’, ‘#3fc5f0’, ‘#c72c41’, ‘#f4efd3’, ‘#c72c41’, ‘#e13a9d’, ‘#e13a9d’, ‘#ffd800’, ‘#51eaea’, ‘#a3f7bf’, ‘#3ed4ff’, ‘#ffa022’, ‘#a6c84c’];
var chartOption = {
title: {
show: true,
text: ‘实时货量流向图’,
target: null,
subtext: ‘地图设置’,
//sublink: ‘../showdata/mapset.html’,
sublink: ‘JavaScript:SetMap();’,
subtarget: ‘self’,
x: ‘center’,
y: ‘top’,
textAlign: null,
backgroundColor: ‘rgba(0,0,0,0)’,
borderColor: ‘#ccc’,
borderWidth: 0,
padding: 5,
itemGap: 10,
textStyle: {
fontSize: 30,
fontStyle: ‘normal’,
fontWeight: ‘normal’,
color: ‘white’
}
},
tooltip: {
trigger: ‘item’,
},
legend: {
orient: ‘vertical’,
top: ‘bottom’,
left: ‘right’,
data: [],
textStyle: {
color: ‘#fff’
},
selectedMode: ‘single’
},
geo: {
label: {
normal: {
show: showMapText,
color: ‘white’,
fontSize: 13
},
emphasis: {
show: false
}
},
roam: false,
layoutCenter: [‘50%’, ‘50%’],
layoutSize: “110%”,
itemStyle: {
normal: {
areaColor: ‘#101f32’,
borderColor: ‘#43d0d6’
},
emphasis: {
areaColor: ‘#617be3’
}
},
regions: []
},
series: []
};

var isProvince = function (name) {
return provincesText.some(function (province) {
return province === name
})
};
var isCity = function (name) {
var cityvalue = cityMap[name];
if (cityvalue != undefined) {
return true;
}
return false;
};
var facheList = function () {
series.push({
type: ‘lines’,
zlevel: 12,
effect: {
show: true,
period: 3,
trailLength: 0.7,
color: ‘#fff’,
symbol: ‘arrow’,
symbolSize: 8,
xAxisIndex: 0,
yAxisIndex: 0,
polarIndex: 0,
geoIndex: 0,
calendarIndex: 0,
},
lineStyle: {
normal: {
color: ‘white’,
width: 1,
opacity: 0.8,
curveness: 0.2
}
},
data: convertData(geoData.facheList)
}, {
type: ‘lines’,
zlevel: 12,
effect: {
show: true,
period: 3,
trailLength: 0,
symbol: planePath,
symbolSize: 8
},
lineStyle: {
normal: {
color: ‘#9b45e4’,
width: 1,
opacity: 0.6,
curveness: 0.2
}
},
data: convertData(geoData.facheList)
});

playFaCheList(geoData.facheList);
};
var cityList = function () {
for (let i = 0; i < geoData.cityList.length; i++) {
var ci = geoData.cityList[i];
series.push({
name: ci.name,
type: ‘effectScatter’,
coordinateSystem: ‘geo’,
zlevel: 2,
rippleEffect: {
period: 4,
scale: 2.5,
brushType: ‘stroke’
},
label: {
normal: {
//show: true,
position: ‘right’,
offset: [1, 0],
formatter: ‘{b}’
}
},
symbolSize: 10,
itemStyle: {
normal: {
color: ‘#00ffff’
},
emphasis: {
color: ‘green’
}
},
tooltip: {
trigger: ‘item’,
padding: 0,
enterable: true,
transitionDuration: 1,
textStyle: {
color: ‘#000’,
decoration: ‘none’,
},
showDelay: 2,
formatter: function (params) {
var tipHtml = ”;
tipHtml = ‘<div style=”width:200px;height:120px;background:rgba(22,80,158,0.8);border:1px solid rgba(7,166,255,0.7)”>’
+ ‘<div style=”width:100%;height:40px;line-height:40px;border-bottom:2px solid rgba(7,166,255,0.7);padding:0 20px”>’ + ‘<i style=”display:inline-block;width:8px;height:8px;background:#16d6ff;border-radius:40px;”>’ + ‘</i>’
+ ‘<span style=”margin-left:10px;color:#fff;font-size:16px;”>’ + params.name + ‘</span>’ + ‘</div>’
+ ‘<div style=”padding:10px”>’
+ ‘<p style=”color:#fff;font-size:15px;”>发货库存:’ + params.data.fhkc + ‘</p>’
+ ‘<p style=”color:#fff;font-size:15px;”>在途库存:’ + params.data.ztkc + ‘</p>’
+ ‘<p style=”color:#fff;font-size:15px;”>到货库存:’ + params.data.dhkc + ‘</p>’
+ ‘</div>’ + ‘</div>’;
return tipHtml;
},
axisPointer: {
show: true,
type: ‘cross’,
lineStyle: {
type: ‘dashed’,
width: 1
}
}
},
data: [{
name: ci.name,
value: geoCoordMap[ci.name],
fhkc: ci.fhkc,
ztkc: ci.ztkc,
dhkc: ci.dhkc
}]
});
};
};
var playList = function () {
series.push({
name: ”,
type: ‘effectScatter’,
coordinateSystem: ‘geo’,
zlevel: -2,
rippleEffect: {
period: 0,
scale: 0,
brushType: ‘stroke’
},
label: {
normal: {
show: false,
position: ‘right’,
offset: [5, 0],
formatter: ‘{b}’
}
},
symbolSize: 0,
itemStyle: {
normal: {
color: ‘#ffa259’
},
emphasis: {
color: ‘green’
}
},
tooltip: {
trigger: ‘item’,
padding: 0,
enterable: true,
transitionDuration: 1,
textStyle: {
color: ‘#000’,
decoration: ‘none’,
},
showDelay: 2,
formatter: function (params) {
var tipHtml = ”;
tipHtml = ‘<div style=”width:200px;height:120px;background:rgba(22,80,158,0.8);border:1px solid rgba(7,166,255,0.7)”>’
+ ‘<div style=”width:100%;height:40px;line-height:40px;border-bottom:2px solid rgba(7,166,255,0.7);padding:0 20px”>’ + ‘<i style=”display:inline-block;width:8px;height:8px;background:#16d6ff;border-radius:40px;”>’ + ‘</i>’
+ ‘<span style=”margin-left:10px;color:#fff;font-size:16px;”>’ + params.name + ‘</span>’ + ‘</div>’
+ ‘<div style=”padding:10px”>’
+ ‘<p style=”color:#fff;font-size:15px;”>发货库存:’ + params.data.fhkc + ‘</p>’
+ ‘<p style=”color:#fff;font-size:15px;”>在途库存:’ + params.data.ztkc + ‘</p>’
+ ‘<p style=”color:#fff;font-size:15px;”>到货库存:’ + params.data.dhkc + ‘</p>’
+ ‘</div>’ + ‘</div>’;
return tipHtml;
},
axisPointer: {
show: true,
type: ‘cross’,
lineStyle: {
type: ‘dashed’,
width: 1
}
}
},
data: geoData.cityList.map(function (ci) {
return {
name: ci.name,
value: geoCoordMap[ci.name],
fhkc: ci.fhkc,
ztkc: ci.ztkc,
dhkc: ci.dhkc
};
})
});
};

var seriesData = function () {

series = [];

facheList();

cityList();

//playList();

count = 0;
timeTicket = null;
seriesIndex = series.length – 1;
dataLength = series[seriesIndex].data.length;
dataIndex = 0;

chartOption.series = series;
};

var loadMap = function (param) {
var url = “../showdata/js/map/” + param + “.js”;
$.ajax({
url: url,
dataType: “json”
}).done(function (data) {
mapJsonData = data;
echarts.registerMap(param, mapJsonData);

if (param != ‘china’ && param != ‘全国’) {
showMapText = true;
}
else {
showMapText = false;
}

chartOption.geo.map = param;
chartOption.geo.label.normal.show = showMapText;

for (let i = 0; i < lightProvince.length; i++) {
let city = lightProvince[i];
chartOption.geo.regions.push({
name: city,
itemStyle: {
normal: {
areaColor: lightColor[city]
}
},
label: {
normal: {
show: true
},
emphasis: {
show: true
},
}
});
}
myMapChart.hideLoading();
myMapChart.setOption(chartOption);
});
};

var playFaCheList = function (data) {
//var html = ‘<!DOCTYPE html><html><head> <meta charset=”utf-8″> <meta name=”author” content=”” /> <style type=”text/css”> *{margin:10px;padding:10px;border:0px; }body{font-size:20px;color:”white”;} #demo{ overflow:hidden; height:100px; width:280px; margin:90px auto; position:relative; } #demo1{ height:auto; text-align:left; } #demo1 li{ list-style-type:none; height:22px; text-align:left; text-indent:15px; } </style></head><body><div id=”demo”> <ul id=”demo1″> ‘
//for (let i = 0 ; i < data.length; i++) {
// var item = data[i];
// html += ‘<li>’ + item.fromName + ‘=>’ + item.toName + ‘</li>’
//}
//html += ‘</li> </ul> <div id=”demo2″></div></div><script type=”text/JavaScript”> var speed=80 ; var demo=document.getElementById(“demo”); var demo1=document.getElementById(“demo1”); function Marquee(){ if(demo.scrollTop>=demo1.offsetHeight){ demo.scrollTop=0; } else{ demo.scrollTop=demo.scrollTop+1; } };var MyMar=setInterval(Marquee,speed); demo.onmouSEOver=function(){clearInterval(MyMar)}; demo.onmouSEOut=function(){MyMar=setInterval(Marquee,speed)};</script></body></html>’
//var x = document.getElementById(“iframe_play_fc”);
//x.srcdoc = html;
var ul = document.getElementById(“fachelist_ul”);
for (let i = 0 ; i < data.length; i++) {
let item = data[i];
let li = document.createElement(“li”);
li.innerHTML = ‘<li><a href=”#”>’
+ (i + 1) + ‘ . ‘
+ item.billdate + ‘ ‘
//+ item.inonevehicleflag
+ ‘ 车牌:’ + item.vehicleno + ‘ ‘
+ ‘<span style=”color:#ff8ba7″>’ + item.fromName + ‘=》’
+ item.full_toName + ‘</span> ‘
+ ‘</a></li>’;

ul.appendChild(li);
}
}
function map() {

myMapChart = echarts.getInstanceByDom(document.getElementById(‘map_1’));
if (myMapChart == undefined) {
myMapChart = echarts.init(document.getElementById(‘map_1’));
}

myMapChart.showLoading({
text: ‘正在加载数据…..’,
color: ‘#fff’,
textColor: ‘#fff’,
maskColor: ‘#46b3e6’,
zlevel: 0
});

$.ajax({
url: “/BI/GetgeoCoordData”,
type: “GET”,
data: { cache: false },
dataType: ‘json’,
cache: false,
success: function (result) {
if (result.status) {
if (result.data.length > 0) {
var data = JSON.parse(result.data);
geoCoordMap = $.extend(geoCoordMap, data);
lightProvince = data.provinceList;

$.ajax({
url: “/BI/GetgeoData”,
type: “GET”,
data: { cache: false },
dataType: ‘json’,
cache: false,
success: function (result) {
if (result.status) {
if (result.data.length > 0) {
var data = JSON.parse(result.data);
geoData = data;

seriesData();

$.ajax({
url: “/BI/GetProvinceCode”,
type: “GET”,
dataType: ‘json’,
success: function (result) {
if (result.status) {
if (result.data.length > 0) {
if (result.data == ‘china’) {
mapSelected = ‘china’;
}
else {
mapSelected = py_provinceMap[result.data];
}
loadMap(result.data);
}
}
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
return;
},
dataType: “json”
}).fail(function (jqXHR, textStatus) {
console.log(“Ajax Error: “, textStatus);
});

window.addEventListener(“resize”, function () {
myMapChart.resize();
});

var setProvinceCode = function (provinceCode) {
$.ajax({
url: “/BI/SetProvinceCode”,
type: “GET”,
data: { provinceCode: provinceCode },
dataType: ‘json’,
cache: false,
success: function (result) {
return;
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
return;
},
dataType: “json”
}).fail(function (jqXHR, textStatus) {
console.log(“Ajax Error: “, textStatus);
});
};

myMapChart.on(‘click’, function (ev) {
if (isProvince(ev.name)) {
mapSelected = ev.name
console.log(mapSelected)
loadMap(provinceMap[ev.name]);
}
else if (isCity(ev.name)) {
mapSelected = ev.name;
loadMap(cityMap[ev.name]);
}
else {
mapSelected = ‘china’
loadMap(mapSelected)
}
});

//timeTicket && clearInterval(timeTicket);
//timeTicket = setInterval(function () {
// myMapChart.dispatchAction({
// type: ‘showTip’,
// seriesIndex: seriesIndex,
// dataIndex: dataIndex
// });
// count++;
// dataIndex++;
// if (dataIndex >= dataLength) {
// dataIndex = 0;
// }
//}, 5000);

//myMapChart.on(‘mouSEOver’, function (params) {
// console.log(params)
// clearInterval(timeTicket);
// myMapChart.dispatchAction({
// type: ‘showTip’,
// seriesIndex: seriesIndex,
// dataIndex: dataIndex
// });
// count++;
// dataIndex++;
// if (dataIndex >= dataLength) {
// dataIndex = 0;
// }
//}, 5000);

//myMapChart.on(‘mouseout’, function (params) {
// timeTicket && clearInterval(timeTicket);
// timeTicket = setInterval(function () {
// myMapChart.dispatchAction({
// type: ‘showTip’,
// seriesIndex: seriesIndex,
// dataIndex: dataIndex
// });
// count++;
// dataIndex++;
// if (dataIndex >= dataLength) {
// dataIndex = 0;
// }
// }, 5000)
//});
}
}
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
return;
},
dataType: “json”
}).fail(function (jqXHR, textStatus) {
console.log(“Ajax Error: “, textStatus);
});
}
}
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
return;
},
dataType: “json”
}).fail(function (jqXHR, status) {
console.log(“Ajax Error: “, status);
});
}

最后既可实现如上效果,点中相应的图例还进行预览。

图表

以这个图表为例,这个是柱状图。

同样先引入div图层

<div class="allnav" id="echart5_1"></div>

js调用实现

复制代码
  //员工学历分布
    function echarts_GetXLData() {
        var myChart = echarts.init(document.getElementById('echart5_1'));

        var itemStyle = {
            normal: {
                color: new echarts.graphic.LinearGradient(
                    0, 1, 0, 0, [{
                        offset: 0,
                        color: '#2af598'
                    }, {
                        offset: 1,
                        color: '#009efd'
                    }]
                ),
                barBorderRadius: 4
            },
            emphasis: {
                color: new echarts.graphic.LinearGradient(
                    0, 1, 0, 0, [{
                        offset: 0,
                        color: '#2af598'
                    }, {
                        offset: 1,
                        color: '#009efd'
                    }]
                ),
                barBorderRadius: 4
            }
        };

        // 指定图表的配置项和数据
        var option = {
            options: []
        };
        myChart.setOption(option);

        $.ajax({
            url: "/paymentWelfare/GetSalaryBigDataBYXL",
            type: "GET",
            data: { cache: false },
            dataType: 'json',
            cache: false,
            success: function (result) {
                if (result.status) {
                    if (result.data.length > 0) {
                        var data = JSON.parse(result.data);
                        var namearr = [];
                        var valuearr = [];
                        for (let i in data) {
                            namearr.push(data[i].name);
                            valuearr.push(data[i].value);
                        }
                        myChart.setOption({
                            tooltip: {
                                trigger: 'axis',
                                textStyle: {
                                    color: "#ffffff"
                                },
                                formatter: function (data) {
                                    var x = data[0];
                                    if (x == undefined) {
                                        return "";
                                    }
                                    return x.name + "<br/>" + x.seriesName + ":" + x.data;
                                }
                            },
                            legend: {
                                show: true,
                                x: 'right',
                                top: 15,
                                data: ['学历'],
                                textStyle: {
                                    color: 'white',
                                    fontSize: 15
                                }
                            },
                            //calculable: true,
                            grid: {
                                y: 40,
                                y2: 80,
                                left: '12%',
                                right: '4%',
                                bottom: '10%'
                            },
                            xAxis: [{
                                type: 'category',
                                axisLabel: {
                                    interval: 0,
                                    rotate: 0,
                                    textStyle: { fontSize: 12, color: '#ffffff' }
                                },
                                axisLine: {
                                    lineStyle: {
                                        color: 'white',
                                        width: 1
                                    }
                                },
                                data: namearr
                            }],
                            yAxis: [{
                                "axisTick": {
                                    "show": false
                                },
                                //"splitLine": {
                                //    "show": false
                                //},
                                type: 'value',
                                nameTextStyle: {
                                    color: "white",
                                    fontSize: 15,
                                    padding: 10
                                },
                                axisLabel: {
                                    textStyle: { color: 'white', fontSize: 12 },
                                    title: { textStyle: { color: 'white' } }
                                },
                                axisLine: {
                                    lineStyle: {
                                        color: 'white',
                                        width: 1
                                    }
                                },
                                splitLine: {
                                    lineStyle: {
                                        type: 'dashed',
                                        color: '#0d48e0'
                                    }
                                },
                            }],
                            series: [{
                                name: '学历',
                                yAxisIndex: 0,
                                type: 'bar',
                                itemStyle: itemStyle,
                                barWidth: 25,
                                label: {
                                    normal: {
                                        show: true,
                                        position: 'top',
                                        //formatter: '{c}' + '万元',
                                        color: 'white'
                                    }
                                },
                                data: valuearr
                            }]
                        });
                    }
                }
            },
            error: function (XMLHttpRequest, textStatus, errorThrown) {
                return;
            },
            dataType: "json"
        }).fail(function (jqXHR, textStatus) {
            console.log("Ajax Error: ", textStatus);
        });

        window.addEventListener("resize", function () {
            myChart.resize();
        });
    }
复制代码

后端代码调用,获取数据

复制代码
        /// <summary>
        /// 学历
        /// </summary>
        /// <param name="cache"></param>
        /// <returns></returns>
        public ActionResult GetSalaryBigDataBYXL(bool cache = false)
        {
            string fileName = "xl.json";

            if (cache == true)
            {
                string jsonData = GetData(fileName);
                if (string.IsNullOrEmpty(jsonData) == false)
                {
                    return Json(new { status = true, data = jsonData }, JsonRequestBehavior.AllowGet);
                }
            }

            DataTable dt = GetHrBigData().Tables[3];
            List<nameValue> data = new List<nameValue> { };

            for (int i = 0; i < dt.Rows.Count; i++)
            {
                data.Add(new nameValue(dt.GetFieldValue<string>("name", i).ToStr(),
                    decimal.Round(dt.GetFieldValue<decimal>("count", i).ToDecimal(), 1)));
            }

            string json = data.ToJson();
            SaveData(fileName, json);

            return Json(new { status = true, data = json }, JsonRequestBehavior.AllowGet);
        }
复制代码

以上完成既可实现漂亮的图表了,其他饼图,折线图等等即同理实现,更新的实现效果多看看官网的帮助文档,你也可以实现漂亮的图表了,一起来交流学习吧!

消息滚动

 <div class="xpanel xpanel-r-t">
                        <div class="title"><span>员工提醒信息播报</span></div>
                        <div class="scrollDiv" id="fachelist_div" style="height:70%;">
                            <ul id="fachelist_ul"></ul>
                        </div>
                    </div>
复制代码
 (function ($) {
            $.fn.extend({
                Scroll: function (opt, callback) {
                    //参数初始化
                    if (!opt) var opt = {};
                    var _this = this.eq(0).find("ul:first");
                    var lineH = _this.find("li:first").height(),
                        //line = opt.line ? parseInt(opt.line, 10) : parseInt(this.height() / lineH, 10),
                        line = 1,
                        speed = opt.speed ? parseInt(opt.speed, 10) : 2000,
                        timer = opt.timer ? parseInt(opt.timer, 10) : 3000;
                    if (line == 0) line = 1;
                    var upHeight = 0 - line * lineH;
                    scrollUp = function () {
                        _this.animate({
                            marginTop: upHeight
                        }, speed, function () {
                            for (i = 1; i <= line; i++) {
                                _this.find("li:first").appendTo(_this);
                            }
                            _this.css({
                                marginTop: 0
                            });
                        });
                    }
                    _this.hover(function () {
                        clearInterval(timerID);
                    }, function () {
                        timerID = setInterval("scrollUp()", timer);
                    }).mouseout();
                }
            });
        })(jQuery);

        $(document).ready(function () {
            $("#fachelist_div").Scroll({
                line: 4,
                speed: 500,
                timer: 4000
            });
        });
复制代码

js文件获取数据代码

复制代码
    //消息提醒数据
    function echarts_GetMsgData() {
        var ul = document.getElementById("fachelist_ul");
        $.ajax({
            url: "/paymentWelfare/GetSalaryBigDataBYMsg",
            type: "GET",
            data: { cache: false },
            dataType: 'json',
            cache: false,
            success: function (result) {
                if (result.status) {
                    if (result.data.length > 0) {
                        var data = JSON.parse(result.data);
                        for (let i = 0 ; i < data.length; i++) {
                            let item = data[i];
                            let li = document.createElement("li");
                            li.innerHTML = '<li>' + '<span style="color:#FFFFFF">'
                                + (i + 1) + ' . '
                               + item.name + ' ' + '</span> '
                                + '</li>';
                            ul.appendChild(li);
                        }
                    }
                }
            },
            error: function (XMLHttpRequest, textStatus, errorThrown) {
                return;
            },
            dataType: "json"
        }).fail(function (jqXHR, textStatus) {
            console.log("Ajax Error: ", textStatus);
        });
    }
复制代码

后台获取数据

复制代码
        /// <summary>
        /// 获取消息
        /// </summary>
        /// <param name="cache"></param>
        /// <returns></returns>
        public ActionResult GetSalaryBigDataBYMsg(bool cache = false)
        {
            string fileName = "msg.json";

            if (cache == true)
            {
                string jsonData = GetData(fileName);
                if (string.IsNullOrEmpty(jsonData) == false)
                {
                    return Json(new { status = true, data = jsonData }, JsonRequestBehavior.AllowGet);
                }
            }

            DataTable dt = GetHrBigData().Tables[5];
            List<nameValue> data = new List<nameValue> { };

            for (int i = 0; i < dt.Rows.Count; i++)
            {
                data.Add(new nameValue(dt.GetFieldValue<string>("name", i).ToStr(),
                    decimal.Round(dt.GetFieldValue<decimal>("count", i).ToDecimal(), 1)));
            }

            string json = data.ToJson();

            SaveData(fileName, json);

            return Json(new { status = true, data = json }, JsonRequestBehavior.AllowGet);
        }
复制代码

以上完成既可以实现消息的滚屏显示

以上从代码实现的角度介绍了我是怎么实现的这样一个效果的过程,工资大数据实现同人事一样,都是相同的控件去实现。

结束语

做好大数据分析任重道远,本人也是今年在这方面投入了大力气去学习,之前用WPF实现了一个,可以看我上一篇写的博文,就有介绍过,实现以上两个界面的大数据是用BS来实现的,图表全部用的是echarts,实现出来的图表费用漂亮,其实在做好这样的一个效果出来首先还得去网上找一个好的模板,再去做调整,完成从一个全新开发可能难度较大,样式布局啥的都不会这么专业,我其实是省去了前面部分的工作了,只花了三四天时间既完成了以上效果,速度还是挺快的,公司领导层对这效果也是非常满意的,以后会加强在这方面的研发,让公司的业务,财务数据都能很好在图表方面进行呈现出来,这样客户也会非常喜欢的。

以上个人愚见,一起加强学习进步!

附件:工资模块实现的最初模板。

https://files.cnblogs.com/files/luoyuhao/echartssjmoban8947202005210009.zip

企业级数据大屏设计如何实现,div+html+echarts - 气宇轩昂_2017 - 博客园

mikel阅读(711)

来源: 企业级数据大屏设计如何实现,div+html+echarts – 气宇轩昂_2017 – 博客园

大屏是什么?

大屏设计是最近比较流行的概念,一般按照功能来分有几种:

1. 可交互的触摸屏,大多运用在互动教学课程或者报告演示现场,用户可结合交互操作来阐述具体内容。设计师需要对交互形式和传达内容作统一思考设计。

 

2. 为某些特定的大型活动设计的专属大屏,比如说产品发布会、双11购物狂欢节。这类大屏需要结合音乐、场景、动效、灯光等多方面元素统一烘托,最终效果酷炫震撼,所有展现的内容都是定制化设计和开发的。

 

3.专为企业提供服务的可视化数据大屏,这一类大屏在商业中有其应用价值,它的特性是数据展示能力强,用一些较为常见的图表,如柱状图、饼图等来展现业务情况,使客户快速读懂数据背后的含义。

 

数据大屏为客户提供了政务、电商、客服、安全、金融等多个应用场景。

 

数据大屏和数据报告的区别是什么?

先来看一下业务场景中的报告和大屏是如何展现的:

报告

 

大屏

1.用户

数据报告的用户一般是数据分析师,分析师制作完报告,会把结果反馈给业务人员或者公司高层,为决策提供参考。而大屏的用户会更广,整个企业内的有关人员甚至所有员工都能够看大屏。

 

2.交互

一份完整的报告一般包括详细的分析过程,用户无法在不交互的情况下了解完整数据,比如说筛选、钻取、查看详情等等,都是很常用的分析功能。而大屏依靠视觉、动效,来传递有效信息,用户并不需要交互即可直观迅速了解内容。

 

3.时间

报告偏向展示一段时间内的数据,某些企业甚至能通过报告的日期筛选查询到从接入系统开始所有的历史数据。大屏更多起到的是监测职能,反馈的是实时信息,显示的是当下的数据。

 

4.展示

报告的阅读场景决定了它展示的侧重点在“精确”和“完整”,要达到这两点,不可避免有时候会牺牲一些视觉表现。例如:数据项过多的情况下,视觉感受比较拥挤。

 

对于大屏来说,即使只有当下的数据,用户也很难在短时间内get到关键,所以重要的是如何在有限的时间内传递出有效信息,即如何迅速提炼出重点数据并展示,即使这样会牺牲数据的完整性。

如果用一句话总结数据大屏的设计的关键:重点突出大于面面俱到。

 

大屏产生的过程

大部分人制作大屏的方式,只是一种图表的堆砌,先把需要的单个图表做完,然后简单地罗列组合在一起,最后改变一下整体颜色,就完成了。整个过程虽然不能说错,但并没有把大屏的优势发挥出来。一个完整的大屏设计过程应该包括以下步骤:

 

1.提炼信息

首先,我们要对数据进行分析,得出自己的结论。同样一份数据,因为不同的角度和思考方式,可能得出的观点很不一样。例如同样都是关于销售额的数据,有人希望知道各地销售额对比,有人希望了解销售额排名前五的商品类型,拿来数据就画图会让设计显得杂乱无章,读者也不知道要读什么数据。

所以在大屏设计之前,需要先和客户确认他们想要传达给目标用户的重点,这个重点是他们希望用户在读完这个大屏之后能够理解并记住的主要信息,很多公司都错误的认为,把多个数据塞进一个大屏中可以帮助提高公司的专业度,实际上这只能显示他们有很多数据。

为什么重点这么重要,试想一下,看大屏的人可能只会驻足在屏幕前一分钟,他们和大屏仅有的互动就是快速扫过整张图,在这一分钟内,到底能记住多少信息?设计师在每个设计环节都要牢牢记住这一点。

可以尝试着问客户两个问题:

(1)如果整个大屏只能展示一个最重要的信息,你希望是什么?

(2)你希望展现这些信息的理由是什么?通过客户的回答,你能了解他希望用户的关注点在哪里,从而提炼出设计重点。如果只有一个重点,放在最显眼的位置,如果有几个重点,尽量集中放置,吸引视觉焦点。

没有什么比加粗高亮数字更简单直接,保险大屏想传递的重心在于

(1)保费总额 。

(2)各地贡献的保费情况。

 

网络安全大屏,首先让用户通过直接的数字感知总体安全情况,其次详细查看当前攻击发生的源头和目的地。

 

整个客户服务的场景中,员工们是非常忙碌的,可能只有偶尔休息的间隙查看一下实时的服务情况,最希望传达给客服的信息集中设计在左半屏,

(1)呼叫量。

(2)满意度。其他信息的优先级相对较低。

 

 

2.选择图表

明确需要表达的信息和主题后,需要根据这个信息的数据关系,决定选择何种图表类型,以及要对图表作何种特别处理。

图表种类各式各样,有些图表很难界定是属于哪种关系,我见过讲图表关系和图表功能比较好的平台是Ant v的墨者学院,有兴趣的小伙伴可以了解一下。

Ant v把数据关系分成了9个大类,当你确定了某个数据关系类型后,就可以根据该数据的使用场景查找出相对应的图表和使用建议,并在其中进行选择。

以一份购物城数据为例:

 

3.制作图表

 

当确定了要使用哪些图表做图后,开始进入制作流程,影响最终图表展现效果的元素一般分为两个层面:

 

非数据层:

 

不受数据影响样式的元素,比如说背景、网格线、外边框等等。这类元素起到的是辅助阅读作用,但如果不加处理全部放出,视觉上会显得杂乱和不够简洁,干扰到你真正想展示的信息。对于这类元素,应该尽量隐藏和弱化。

 

隐藏

 

·      去除不必要的背景填充

 

·      去掉无意义的颜色变化

 

·      去掉不必要的外框

 

弱化

 

·      坐标轴淡色或隐藏

 

·      网格线淡色或隐藏

数据层:

受数据影响样式的元素,比如说柱状图的柱条长度,柱条颜色,柱条展示个数,气泡图气泡大小等等,这类元素的展示效果和图表本身的数据息息相关,一旦图表本身的数据比较极端,有可能会使得最终视觉展现不尽如人意,我们无法改变具体的数据,是否就完全无法操控这些元素了呢?

这里挑选了几个大屏中应用较多的图表,总结了其数据层样式的调整方式。

 

调整范围

 

·      截断超大值

当某一个值特别大时,绘制出的条形远远长于其他类别,导致其他条形被压缩,不便于比较。某条特别长,也可能会影响到整个大屏的排版平衡,可以采用截断选项的方式。

 

·      数据可以不从0开始

很多数据可视化工具里都有“数轴是否包括零”这一个选项,用户可通过这个功能来控制坐标轴的显示范围,例如下图,折线的波动范围比较小,走势起伏不明显,这时可以选择数据不从零开始,清晰地看出了折线的走势情况。当然,如果在平时的数据报告中,这样显示有夸大差异的嫌疑,不建议频繁使用。

 

避免重叠

 

·      避免负值被遮盖

当一些数值有负数时,标签和柱条离得较远,不便于阅读,如果标签紧贴柱条,又会发生重叠,比较好的方式是标签根据柱条的方向分别显示在坐标轴的两侧。

 

 

·      轴标签太长可横向放置

 

当轴标签太长时,虽然斜放可以避免重叠,但歪着头查看内容在浏览大屏的场景下对用户不是很友好,可以考虑把柱条横向放置,把标签置于柱条空隙之间。

 

精简数据项

 

·      饼图分类5~7项

在做数据报告时,不管有多少数据项,为了完整和精确性,所有的内容都会显示出来,但在大屏中,如此满的数据展示,不但忽略了视觉体验,还会让用户抓不住重点,对于饼图来说,建议扇区个数不要超过5个,例如保留占比前5的扇区,剩下的非重点数据全部归到“其他”。

·      保留前五和后五

如果柱状图的数据项过多,展示时会过于密集,建议先把数据项按照数值大小排序,然后将中间用户可能不是最关心的柱条折叠起来,只保留前五和后五的数据项。

强调重点

 

·      视觉高亮重要信息

先来看一张对比图,虽然左图颜色更加丰富,但是没有重点,视觉传达给用户的信息是没有主次的,而右图很明显想传达:这个数值有异常!请关注我!在大屏中,为了在短时间内让用户get到关键信息,应该尽量排除其他不重要的数据项干扰。

 

折线图中,只高亮重要数据点比每个节点都标注更能传递有效信息

 

在饼图中,因为颜色块大小代表占比多少,所以高亮的方式并不一定适用,我们可以通过分离某一块扇区达到强调重点的效果。

 

·      尽量减少图例

 

大屏的图表中应该尽量避免图例,图例会让用户不断在数据项和颜色块之间往返比对,耗费时间,还容易忘记重点,由于柱条个数经特殊处理后并不会很多,所以数据项名称可以直接标注。

 

总结

如果要对大屏设计的特点作一个总结:全屏时突出重点图表,单表时突出重点数据。把握好这点,至少可以避过大部分的坑。但一个高水准的大屏,还少不了一些细节的把控。比如:

 

1.  大屏风格是否符合业务主题,是热烈?是专业?是冷静?

 

2.  是否需要一些个性化的控件:例如时间器、轮播欢迎语等。

 

3.  是针对固定屏的定制化开发,还是考虑延展性的模块纵横栅格布局,对不同屏的适配是如何?

 

4.  现场投放大屏后,内容是否方便阅读,动效是否符合预期,色差是是否需要调整等等。

 

由于篇幅原因,这里不一一详细展开,如果有小伙伴对我们大屏感兴趣,可以加微信群:1092912327。

 

 

大屏是我们用来分享、沟通、传播信息的有效途径之一。它将会进化成一种新的媒体形式,在品牌推广、政务接待、商业沟通、数据监控等各个场景发挥重要作用。本文主要整理了一些大屏设计过程中的方法和原则,希望能够为大家提供一些借鉴思路。

写了几篇实现思路文章:

https://www.cnblogs.com/luoyuhao/p/13541260.html 怎么实现员工和工资大数据分析,echarts+js实现

https://www.cnblogs.com/luoyuhao/p/13867911.html SaaS系统怎么做物流行业年度经营报告,MVC+js+echarts实现

让Web API支持Protocol Buffers - 秋夜 - 博客园

mikel阅读(681)

来源: 让Web API支持Protocol Buffers – 秋夜 – 博客园

现在我们Web API项目基本上都是使用的Json作为通信的格式,随着移动互联网的兴起,Web API不仅其他系统可以使用,手机端也可以使用,但是手机端也有相对特殊的地方,网络通信除了wifi,还有蜂窝网络比如2G/3G,当手机处于这种网络环境下并且在一些偏僻或者有建筑物阻挡的地方,网络会变得非常差,之前我有测试过ProtoBuf和Json在序列化和反序列化上性能的对比,通过测试发现ProtoBuf在序列化和反序列化以及序列化后字节的长度和其他的框架相比都有很大的优势,所以这篇文章准备让Web API也支持Protocol Buffers。

要让Web API支持ProtoBuf就需在Web API的HttpConfigurationFormatters中注册MediaTypeFormatter,这里已经有现成的解决方案,我们直接使用。
1. 从nuget上下载WebApiContrib.Formatting.ProtoBuf.
2. 修改配置,新增config.Formatters.Add(new ProtoBufFormatter());

复制代码
        public static void Register(HttpConfiguration config)
        {
            // Web API 配置和服务

            // Web API 路由
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{action}/{id}",   //修改路由规则
                defaults: new { id = RouteParameter.Optional }
            );

            config.Formatters.Add(new ProtoBufFormatter());
        }
复制代码
完成上面两步, 我们的Web API就已经支持了ProtoBuf,我们可以新建一个Controller来进行测试。

复制代码
 public class UserController : ApiController
    {

        /// <summary>
        /// 注册用户
        /// </summary>
        /// <param name="userDto"></param>
        /// <returns></returns>
        public string Regist(UserDto userDto)
        {
            if (!string.IsNullOrEmpty(userDto.UserName) && !string.IsNullOrEmpty(userDto.Password))
            {
                return "regist success";
            }
            return "regis error";
        }

        //登陆
        public string Login(UserLoginDto userLoginDto)
        {
            if (userLoginDto.UserName == "sa")
            {
                return "login success";
            }
            return "loign fail";
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <param name="userName"></param>
        /// <returns></returns>
        public UserDto Get(string userName)
        {
            if (!string.IsNullOrEmpty(userName))
            {
                return new UserDto()
                {
                    UserName = "sa",
                    Password = "123",
                    Subscription = new List<string>() {"news", "picture"}
                };
            }
            return null;
        }
    }
复制代码
这里对实体也要进行修改。

复制代码
 [ProtoContract]
    public class UserDto
    {
        [ProtoMember(1)]
        public string UserName { get; set; }

        [ProtoMember(2)]
        public string Password { get; set; }

        [ProtoMember(3)]
        public List<string> Subscription { get; set; }
    }

  [ProtoContract]
    public class UserLoginDto
    {
        [ProtoMember(1)]
        public string UserName { get; set; }

        [ProtoMember(2)]
        public string Password { get; set; }
    }
复制代码
新建一个测试客户端,控制台就可以,引用WebApiContrib.Formatting.ProtoBuf和Web Api Client
将我们刚才创建的Dto复制到客户端,测试代码如下:

复制代码
  static void Main(string[] args)
        {
            var client = new HttpClient { BaseAddress = new Uri("http://192.168.16.9:8090/") };
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-protobuf"));

            Regist(client);
            Console.WriteLine("===================================");
            Login(client);
            Console.WriteLine("===================================");
            GetUser(client);
            Console.WriteLine("===================================");
            Console.ReadLine();
        }

        private static void Regist(HttpClient client)
        {
            //注册
            var userDto = new UserDto()
            {
                UserName = "sa",
                Password = "123456",
                Subscription = new List<string>() { "news", "picture" }
            };

            HttpResponseMessage response = client.PostAsync("api/User/Regist", userDto, new ProtoBufFormatter()).Result;

            if (response.IsSuccessStatusCode)
            {
                var p = response.Content.ReadAsAsync<string>(new[] { new ProtoBufFormatter() }).Result;
                Console.WriteLine(p);
            }
        }

        private static void Login(HttpClient client)
        {

            //登陆
            var userlogin = new UserLoginDto()
            {
                UserName = "sa",
                Password = "123456",
            };

            HttpResponseMessage response = client.PostAsync("api/User/login", userlogin, new ProtoBufFormatter()).Result;
            if (response.IsSuccessStatusCode)
            {
                var p = response.Content.ReadAsAsync<string>(new[] { new ProtoBufFormatter() }).Result;
                Console.WriteLine(p);
            }
        }

        private static void GetUser(HttpClient client)
        {

            HttpResponseMessage response = client.GetAsync("api/User/Get?userName=sa").Result;

            if (response.IsSuccessStatusCode)
            {
                var p = response.Content.ReadAsAsync<UserDto>(new[] { new ProtoBufFormatter() }).Result;

                Console.WriteLine(p.UserName + " " + p.Password);
                foreach (var s in p.Subscription)
                {
                    Console.WriteLine(s);
                }
            }
        }
复制代码
运行结果如下:
如上我们已经可以看到输出成功

K8s核心原理(一)之API Server - 简书

mikel阅读(1305)

来源: K8s核心原理(一)之API Server – 简书

image.png

参考:《kubernetes权威指南》

1. API Server简介

k8s API Server提供了k8s各类资源对象(pod,RC,Service等)的增删改查及watch等HTTP Rest接口,是整个系统的数据总线和数据中心。

kubernetes API Server的功能:

  1. 提供了集群管理的REST API接口(包括认证授权、数据校验以及集群状态变更);
  2. 提供其他模块之间的数据交互和通信的枢纽(其他模块通过API Server查询或修改数据,只有API Server才直接操作etcd);
  3. 是资源配额控制的入口;
  4. 拥有完备的集群安全机制.

kube-apiserver工作原理图

2. 如何访问kubernetes API

k8s通过kube-apiserver这个进程提供服务,该进程运行在单个k8s-master节点上。默认有两个端口。

2.1. 本地端口

  1. 该端口用于接收HTTP请求;
  2. 该端口默认值为8080,可以通过API Server的启动参数“–insecure-port”的值来修改默认值;
  3. 默认的IP地址为“localhost”,可以通过启动参数“–insecure-bind-address”的值来修改该IP地址;
  4. 非认证或授权的HTTP请求通过该端口访问API Server。

2.2. 安全端口

  1. 该端口默认值为6443,可通过启动参数“–secure-port”的值来修改默认值;
  2. 默认IP地址为非本地(Non-Localhost)网络端口,通过启动参数“–bind-address”设置该值;
  3. 该端口用于接收HTTPS请求;
  4. 用于基于Tocken文件或客户端证书及HTTP Base的认证;
  5. 用于基于策略的授权;
  6. 默认不启动HTTPS安全访问控制。

2.3. 访问方式

Kubernetes REST API可参考https://kubernetes.io/docs/api-reference/v1.6/

2.3.1. curl

curl localhost:8080/api
curl localhost:8080/api/v1/pods
curl localhost:8080/api/v1/services
curl localhost:8080/api/v1/replicationcontrollers

2.3.2. Kubectl Proxy

Kubectl Proxy代理程序既能作为API Server的反向代理,也能作为普通客户端访问API Server的代理。通过master节点的8080端口来启动该代理程序。

kubectl proxy –port=8080 &

具体见kubectl proxy –help

[root@node5 ~]# kubectl proxy --help
To proxy all of the kubernetes api and nothing else, use:
kubectl proxy --api-prefix=/
To proxy only part of the kubernetes api and also some static files:
kubectl proxy --www=/my/files --www-prefix=/static/ --api-prefix=/api/
The above lets you 'curl localhost:8001/api/v1/pods'.
To proxy the entire kubernetes api at a different root, use:
kubectl proxy --api-prefix=/custom/
The above lets you 'curl localhost:8001/custom/api/v1/pods'
Usage:
 kubectl proxy [--port=PORT] [--www=static-dir] [--www-prefix=prefix] [--api-prefix=prefix] [flags]
Examples:
# Run a proxy to kubernetes apiserver on port 8011, serving static content from ./local/www/
$ kubectl proxy --port=8011 --www=./local/www/
# Run a proxy to kubernetes apiserver on an arbitrary local port.
# The chosen port for the server will be output to stdout.
$ kubectl proxy --port=0
# Run a proxy to kubernetes apiserver, changing the api prefix to k8s-api
# This makes e.g. the pods api available at localhost:8011/k8s-api/v1/pods/
$ kubectl proxy --api-prefix=/k8s-api
Flags:
 --accept-hosts="^localhost$,^127//.0//.0//.1$,^//[::1//]$": Regular expression for hosts that the proxy should accept.
 --accept-paths="^/.*": Regular expression for paths that the proxy should accept.
 --api-prefix="/": Prefix to serve the proxied API under.
 --disable-filter[=false]: If true, disable request filtering in the proxy. This is dangerous, and can leave you vulnerable to XSRF attacks, when used with an accessible port.
 -p, --port=8001: The port on which to run the proxy. Set to 0 to pick a random port.
 --reject-methods="POST,PUT,PATCH": Regular expression for HTTP methods that the proxy should reject.
 --reject-paths="^/api/.*/exec,^/api/.*/run": Regular expression for paths that the proxy should reject.
 -u, --unix-socket="": Unix socket on which to run the proxy.
 -w, --www="": Also serve static files from the given directory under the specified prefix.
 -P, --www-prefix="/static/": Prefix to serve static files under, if static file directory is specified.

Global Flags:
 --alsologtostderr[=false]: log to standard error as well as files
 --api-version="": The API version to use when talking to the server
 --certificate-authority="": Path to a cert. file for the certificate authority.
 --client-certificate="": Path to a client key file for TLS.
 --client-key="": Path to a client key file for TLS.
 --cluster="": The name of the kubeconfig cluster to use
 --context="": The name of the kubeconfig context to use
 --insecure-skip-tls-verify[=false]: If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure.
 --kubeconfig="": Path to the kubeconfig file to use for CLI requests.
 --log-backtrace-at=:0: when logging hits line file:N, emit a stack trace
 --log-dir="": If non-empty, write log files in this directory
 --log-flush-frequency=5s: Maximum number of seconds between log flushes
 --logtostderr[=true]: log to standard error instead of files
 --match-server-version[=false]: Require server version to match client version
 --namespace="": If present, the namespace scope for this CLI request.
 --password="": Password for basic authentication to the API server.
 -s, --server="": The address and port of the Kubernetes API server
 --stderrthreshold=2: logs at or above this threshold go to stderr
 --token="": Bearer token for authentication to the API server.
 --user="": The name of the kubeconfig user to use
 --username="": Username for basic authentication to the API server.
 --v=0: log level for V logs
 --vmodule=: comma-separated list of pattern=N settings for file-filtered logging

2.3.3. kubectl客户端

命令行工具kubectl客户端,通过命令行参数转换为对API Server的REST API调用,并将调用结果输出。

命令格式:kubectl [command] [options]

具体可参考k8s常用命令

2.3.4. 编程方式调用

使用场景:

1、运行在Pod里的用户进程调用kubernetes API,通常用来实现分布式集群搭建的目标。

2、开发基于kubernetes的管理平台,比如调用kubernetes API来完成Pod、Service、RC等资源对象的图形化创建和管理界面。可以使用kubernetes提供的Client Library。

具体可参考https://github.com/kubernetes/client-go

3. 通过API Server访问Node、Pod和Service

k8s API Server最主要的REST接口是资源对象的增删改查,另外还有一类特殊的REST接口—k8s Proxy API接口,这类接口的作用是代理REST请求,即kubernetes API Server把收到的REST请求转发到某个Node上的kubelet守护进程的REST端口上,由该kubelet进程负责响应。

3.1. Node相关接口

关于Node相关的接口的REST路径为:/api/v1/proxy/nodes/{name},其中{name}为节点的名称或IP地址。

/api/v1/proxy/nodes/{name}/pods/    #列出指定节点内所有Pod的信息
/api/v1/proxy/nodes/{name}/stats/   #列出指定节点内物理资源的统计信息
/api/v1/prxoy/nodes/{name}/spec/    #列出指定节点的概要信息

这里获取的Pod信息来自Node而非etcd数据库,两者时间点可能存在偏差。如果在kubelet进程启动时加–enable-Debugging-handles=true参数,那么kubernetes Proxy API还会增加以下接口:

/api/v1/proxy/nodes/{name}/run      #在节点上运行某个容器
/api/v1/proxy/nodes/{name}/exec     #在节点上的某个容器中运行某条命令
/api/v1/proxy/nodes/{name}/attach   #在节点上attach某个容器
/api/v1/proxy/nodes/{name}/portForward   #实现节点上的Pod端口转发
/api/v1/proxy/nodes/{name}/logs     #列出节点的各类日志信息
/api/v1/proxy/nodes/{name}/metrics  #列出和该节点相关的Metrics信息
/api/v1/proxy/nodes/{name}/runningpods  #列出节点内运行中的Pod信息
/api/v1/proxy/nodes/{name}/debug/pprof  #列出节点内当前web服务的状态,包括CPU和内存的使用情况

3.2. Pod相关接口

/api/v1/proxy/namespaces/{namespace}/pods/{name}/{path:*}      #访问pod的某个服务接口
/api/v1/proxy/namespaces/{namespace}/pods/{name}               #访问Pod
#以下写法不同,功能一样
/api/v1/namespaces/{namespace}/pods/{name}/proxy/{path:*}      #访问pod的某个服务接口
/api/v1/namespaces/{namespace}/pods/{name}/proxy               #访问Pod

3.3. Service相关接口

/api/v1/proxy/namespaces/{namespace}/services/{name}

Pod的proxy接口的作用:在kubernetes集群之外访问某个pod容器的服务(HTTP服务),可以用Proxy API实现,这种场景多用于管理目的,比如逐一排查Service的Pod副本,检查哪些Pod的服务存在异常问题。

4. 集群功能模块之间的通信

kubernetes API Server作为集群的核心,负责集群各功能模块之间的通信,集群内各个功能模块通过API Server将信息存入etcd,当需要获取和操作这些数据时,通过API Server提供的REST接口(GET/LIST/WATCH方法)来实现,从而实现各模块之间的信息交互。

4.1. kubelet与API Server交互

每个Node节点上的kubelet定期就会调用API Server的REST接口报告自身状态,API Server接收这些信息后,将节点状态信息更新到etcd中。kubelet也通过API Server的Watch接口监听Pod信息,从而对Node机器上的POD进行管理。

监听信息 kubelet动作 备注
新的POD副本被调度绑定到本节点 执行POD对应的容器的创建和启动逻辑
POD对象被删除 删除本节点上相应的POD容器
修改POD信息 修改本节点的POD容器

4.2. kube-controller-manager与API Server交互

kube-controller-manager中的Node Controller模块通过API Server提供的Watch接口,实时监控Node的信息,并做相应处理。

4.3. kube-scheduler与API Server交互

Scheduler通过API Server的Watch接口监听到新建Pod副本的信息后,它会检索所有符合该Pod要求的Node列表,开始执行Pod调度逻辑。调度成功后将Pod绑定到目标节点上。

4.4. 特别说明

为了缓解各模块对API Server的访问压力,各功能模块都采用缓存机制来缓存数据,各功能模块定时从API Server获取指定的资源对象信息(LIST/WATCH方法),然后将信息保存到本地缓存,功能模块在某些情况下不直接访问API Server,而是通过访问缓存数据来间接访问API Server。

9人点赞

作者:沉沦2014
链接:https://www.jianshu.com/p/a25e9e613f2c
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。