K8S安装部署

参考了各种资料,安装过程中还是遇到了各种的坑,这篇里面尽量避免了一些出现的问题,可能后来解决坑的过程中没有进行更新,部署成功后没有再重新部署以便,无可避免可能笔记中还是有些问题的。

修改hostname

sudo hostname et-k8s-master-130

1、环境
操作系统:Ubuntu 18.04.1 LTS
内核: 4.15.0-39-generic

查看方式:
cat /etc/issue
uname -a

2、关闭防火墙
$ sudo ufw disable
3、禁用selinux
ubuntu默认不安装selinux,假如安装了的话,按如下步骤禁用selinux
临时禁用:sudo setenforce 0
永久禁用:
$ sudo vi /etc/selinux/config
SELINUX=permissive

4、内核开启ipv4转发
$ sudo vim /etc/sysctl.conf
net.ipv4.ip_forward = 1 #开启ipv4转发,允许内置路由

$sudo sysctl -p

5、 防火墙修改FORWARD链默认策略

方案二 (推荐)
设置docker启动参数添加–iptables=false选项,使docker不再操作iptables,比如1.10版以上可编辑docker daemon默认配置文件/etc/docker/daemon.json:
{
“iptables”: false
}

6、禁用swap
$ sudo swapoff -a
同时还需要修改/etc/fstab文件,注释掉 SWAP 的自动挂载,防止机子重启后swap启用。

7、配置iptables参数,使得流经网桥的流量也经过iptables/netfilter防火墙
$ sudo tee /etc/sysctl.d/k8s.conf <<-‘EOF’
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF

$ sudo sysctl –system

8、安装docker
0) 卸载旧docker
$ sudo apt-get remove docker docker-engine docker.io

(中间设置了一次科大的源,不知道有没有起作用 http://www.runoob.com/docker/ubuntu-docker-install.html, 好像是科大的起作用了,阿里的没有起作用)

0) sudo apt-get update
1) 安装依赖,使得apt可以使用https
sudo apt-get -y install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/gpg | sudo apt-key add –

sudo add-apt-repository “deb [arch=amd64] https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu \
$(lsb_release -cs) stable”

sudo apt update

(https://ubuntu.pkgs.org/16.04/ubuntu-main-amd64/libltdl7_2.4.6-0.1_amd64.deb.html)
$ wget http://archive.ubuntu.com/ubuntu/pool/main/libt/libtool/libltdl7_2.4.6-2_amd64.deb
$ sudo dpkg -i libltdl7_2.4.6-2_amd64.deb

4)安装指定版本docker-ce
查看源中都有哪些版本:
$ apt-cache madison docker-ce
安装指定版本
sudo apt-get install -y docker-ce=18.06.1~ce~3-0~ubuntu (安装该版本)

5)启动并设置开机自启动docker
$ sudo systemctl enable docker && sudo systemctl start docker

6) 将当前登录用户加入docker用户组中
sudo usermod -aG docker ubuntu

9、docker启动参数配置
为docker做如下配置:
设置阿里云镜像库加速dockerhub的镜像。国内访问dockerhub不稳定,将对dockerhub的镜像拉取代理到阿里云镜像库
配上1.3.2的禁用iptables的设置
如果想让podIP可路由的话,设置docker不再对podIP做MASQUERADE,否则docker会将podIP这个源地址SNAT成nodeIP
设置docker存储驱动为overlay2(需要linux kernel版本在4.0以上,docker版本大于1.12)
根据业务规划修改容器实例存储根路径(默认路径是/var/lib/docker)
最终配置如下:

$ sudo tee /etc/docker/daemon.json <<-‘EOF’
{
“registry-mirrors”: [“https://7trvj11i.mirror.aliyuncs.com”],
“iptables”: false,
“ip-masq”: false,
“storage-driver”: “overlay2”,
“graph”: “/home/ubuntu/docker”
}
EOF

$ sudo systemctl restart docker

docker的所有启动参数可见:https://docs.docker.com/engine/reference/commandline/dockerd/
将xxxxxxxx替换成阿里云为你生成的镜像代理仓库前缀

为docker设置http代理
假如机器在内网环境无法直接访问外网的话,还需要为docker设置一个http_proxy。
$ sudo mkdir /etc/systemd/system/docker.service.d

$ sudo tee /etc/systemd/system/docker.service.d/http-proxy.conf <<-‘EOF’
[Service]
Environment=”HTTP_PROXY=http://xxx.xxx.xxx.xxx:xxxx”
Environment=”NO_PROXY=localhost,127.0.0.0/8″
EOF

$ sudo systemctl daemon-reload
$ sudo systemctl restart docker

==========================================
安装kubeadm、kubelet、kubectl (node机器仅仅需要安装 docker、kubelet)
改成科大的可以了
sudo cat < /etc/apt/sources.list.d/kubernetes.list
deb http://mirrors.ustc.edu.cn/kubernetes/apt kubernetes-xenial main
EOF
sudo apt-get update
(http://mirrors.ustc.edu.cn/kubernetes/apt/dists/kubernetes-xenial/main/)

sudo apt-get update && sudo apt-get install -y apt-transport-https curl
sudo curl -s https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | sudo apt-key add –
sudo tee /etc/apt/sources.list.d/kubernetes.list <<-‘EOF’
deb https://mirrors.aliyun.com/kubernetes/apt kubernetes-xenial main
EOF
sudo apt-get update

wget http://archive.ubuntu.com/ubuntu/pool/main/s/socat/socat_1.7.3.2-2ubuntu2_amd64.deb
sudo dpkg -i socat_1.7.3.2-2ubuntu2_amd64.deb

安装指定版本: (科大源可以)

sudo apt-get install -y kubelet=1.11.5-00 kubeadm=1.11.5-00 kubectl=1.11.5-00

$ kubeadm config images list –kubernetes-version=v1.11.5
k8s.gcr.io/kube-apiserver-amd64:v1.11.5
k8s.gcr.io/kube-controller-manager-amd64:v1.11.5
k8s.gcr.io/kube-scheduler-amd64:v1.11.5
k8s.gcr.io/kube-proxy-amd64:v1.11.5
k8s.gcr.io/pause:3.1
k8s.gcr.io/etcd-amd64:3.2.18
k8s.gcr.io/coredns:1.1.3

//image-process.sh
!/bin/bash
images=(kube-proxy-amd64:v1.11.5 kube-scheduler-amd64:v1.11.5 kube-controller-manager-amd64:v1.11.5 kube-apiserver-amd64:v1.11.5 etcd-amd64:3.2.18 pause:3.1)
for imageName in ${images[@]} ; do
docker pull mirrorgooglecontainers/$imageName
docker tag mirrorgooglecontainers/$imageName k8s.gcr.io/$imageName
docker rmi mirrorgooglecontainers/$imageName
done
docker pull coredns/coredns:1.1.3
docker tag coredns/coredns:1.1.3 k8s.gcr.io/coredns:1.1.3
docker rmi coredns/coredns:1.1.

使用: sudo /bin/bash ./image-process.sh 执行

(参考https://yq.aliyun.com/articles/498760)
sudo kubeadm init –apiserver-advertise-address=0.0.0.0 –pod-network-cidr=192.168.0.0/16 –service-cidr=10.233.0.0/16 –kubernetes-version=v1.11.5 –feature-gates=CoreDNS=true

kubeadm init –feature-gates=CoreDNS=true

[init] using Kubernetes version: v1.13.2
[preflight] running pre-flight checks
        [WARNING KubernetesVersion]: kubernetes version is greater than kubeadm version. Please consider to upgrade kubeadm. kubernetes version: 1.13.2. Kubeadm version: 1.11.x
I0115 08:49:25.414809   23386 kernel_validator.go:81] Validating kernel version
I0115 08:49:25.416120   23386 kernel_validator.go:96] Validating kernel config
        [WARNING SystemVerification]: docker version is greater than the most recently validated version. Docker version: 18.06.1-ce. Max validated version: 17.03
[preflight/images] Pulling images required for setting up a Kubernetes cluster
[preflight/images] This might take a minute or two, depending on the speed of your internet connection
[preflight/images] You can also perform this action in beforehand using 'kubeadm config images pull'
[kubelet] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[preflight] Activating the kubelet service
[certificates] Generated ca certificate and key.
[certificates] Generated apiserver certificate and key.
[certificates] apiserver serving cert is signed for DNS names [et-k8s-master-130 kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 10.0.0.130]
[certificates] Generated apiserver-kubelet-client certificate and key.
[certificates] Generated sa key and public key.
[certificates] Generated front-proxy-ca certificate and key.
[certificates] Generated front-proxy-client certificate and key.
[certificates] Generated etcd/ca certificate and key.
[certificates] Generated etcd/server certificate and key.
[certificates] etcd/server serving cert is signed for DNS names [et-k8s-master-130 localhost] and IPs [127.0.0.1 ::1]
[certificates] Generated etcd/peer certificate and key.
[certificates] etcd/peer serving cert is signed for DNS names [et-k8s-master-130 localhost] and IPs [10.0.0.130 127.0.0.1 ::1]
[certificates] Generated etcd/healthcheck-client certificate and key.
[certificates] Generated apiserver-etcd-client certificate and key.
[certificates] valid certificates and keys now exist in "/etc/kubernetes/pki"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/admin.conf"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/kubelet.conf"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/controller-manager.conf"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/scheduler.conf"
[controlplane] wrote Static Pod manifest for component kube-apiserver to "/etc/kubernetes/manifests/kube-apiserver.yaml"
[controlplane] wrote Static Pod manifest for component kube-controller-manager to "/etc/kubernetes/manifests/kube-controller-manager.yaml"
[controlplane] wrote Static Pod manifest for component kube-scheduler to "/etc/kubernetes/manifests/kube-scheduler.yaml"
[etcd] Wrote Static Pod manifest for a local etcd instance to "/etc/kubernetes/manifests/etcd.yaml"
[init] waiting for the kubelet to boot up the control plane as Static Pods from directory "/etc/kubernetes/manifests" 
[init] this might take a minute or longer if the control plane images have to be pulled
[apiclient] All control plane components are healthy after 56.011462 seconds
[uploadconfig] storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config-1.13" in namespace kube-system with the configuration for the kubelets in the cluster
[markmaster] Marking the node et-k8s-master-130 as master by adding the label "node-role.kubernetes.io/master=''"
[markmaster] Marking the node et-k8s-master-130 as master by adding the taints [node-role.kubernetes.io/master:NoSchedule]
[patchnode] Uploading the CRI Socket information "/var/run/dockershim.sock" to the Node API object "et-k8s-master-130" as an annotation
[bootstraptoken] using token: gyrina.jdnvbj9anyxj8nav
[bootstraptoken] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstraptoken] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstraptoken] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstraptoken] creating the "cluster-info" ConfigMap in the "kube-public" namespace
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes master 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/

You can now join any number of machines by running the following on each node
as root:

  kubeadm join 10.0.0.130:6443 --token gyrina.jdnvbj9anyxj8nav --discovery-token-ca-cert-hash sha256:cdda52d0423d1720436a659a6fc593d5cf789f8c0ee709a35a52b869ac2048ec

master中产生永不过期的token:
kubeadm token generate
kubeadm token create hylutu.kwbu1kstuaz15qo3 –print-join-command –ttl=0
设置–ttl=0代表永不过期

清理重新初始化 sudo kubeadm reset

设置master参与工作负载 (暂时不设置)
4.2 网络部署

flannel网络部署
sudo wget https://github.com/containernetworking/plugins/releases/download/v0.7.4/cni-plugins-amd64-v0.7.4.tgz
sudo tar -zxvf

Slave节点
sudo kubeadm join 10.0.0.130:6443 –token 1zzzb3.qo9lmn0qhikk9zke –discovery-token-ca-cert-hash sha256:c74b2b02a518556b6b8b699154ae830cb3d41712c444eaec6ed759e3a41b9a81

查看节点:kubectl get nodes -o wide
kubectl get pods –all-namespaces
kubectl get nodes
kubectl describe pod

错误:configmaps “kubelet-config-1.13” is forbidden
解决方法:
apt-get remove cri-tools
sudo apt-get install kubelet=1.11.5-00 kubeadm=1.11.5-00

sudo rm /etc/kubernetes/pki/ca.crt
sudo rm /etc/kubernetes/bootstrap-kubelet.conf
sudo kubeadm join 10.0.0.130:6443 –token 1zzzb3.qo9lmn0qhikk9zke –discovery-token-ca-cert-hash sha256:c74b2b02a518556b6b8b699154ae830cb3d41712c444eaec6ed759e3a41b9a81

错误:通过kubectl get pods –all-namespaces 查看node一直处于NotReady状态
解决方法:参照“查找具体的pod错误”笔记 (最终解决方法通过执行image-process.sh)

参考:https://blog.csdn.net/liukuan73/article/details/83116271

(没有操作:load后发现镜像都是calico/为前缀,而yaml文件里配置的镜像前缀是quay.io/calico/,所以需要重新打一下tag或改一下yaml里的前缀。)

检查配置是否正确:kubectl cluster-info

kubectl -n istio-system delete $(kubectl -n istio-system get pod -o name | grep gin-blog1)

k8s、docker、istio微服务架构 – 0序言

公司10多年的积累,网站系统成为了一个庞然大物,一处更改可能带了多处变动,一处出问题导致多处产生问题,各种功能集合在一个导致各个业务高度耦合进而导致了各种问题的产生:1、代码到处拷贝2、复杂性扩散3、SQL 质量得不到保障,业务相互影响4、疯狂的 DB 耦合
微服务基础架构选型:spring cloud、Dubbo、Go kit、Service Mesh由于公司开发是多语言环境(PHP、Python、NodeJS),没有Java、GO语言基础,而且Service Mesh确实也是更加新的、语言无关的、更加优化的方案,所以选择了k8s、docker、istio微服务的架构。通过从无到有的实现,将相关过程进行一些工作中相关的问题及解决进行记录。

certbot通用证书申请及自动更新

看的certbot原文档中说可以自动更新,但是按文档做了以后自动更新一直没有成功过,通过查找资料发现每次更新需要操作需要对域名解析做一些操作才行,所以有一个操作权限的问题,整理如下,成功更新证书。

0、下载certbot-auto
wget https://dl.eff.org/certbot-auto
chmod a+x certbot-auto

1、下载脚本工具
git clone https://github.com/ywdblog/certbot-letencrypt-wildcardcertificates-alydns-au
cd certbot-letencrypt-wildcardcertificates-alydns-au
chmod 0777 au.sh autxy.sh augodaddy.sh python-version/au.sh

2、配置
根据要更新的域名的提供商,选择相应的脚本:

阿里云(根据环境二选一即可):
au.sh (PHP 环境)
python-version/au.py:(Python环境,兼容Python 2/3)
腾讯云:autxy.sh(PHP 环境)
GoDaddy:augodaddy.sh(PHP 环境)

备注:环境-代表要执行脚本的服务器支持的环境

修改DNS API 密钥:
alydns.php,修改 accessKeyId、accessSecrec 变量, API key 和 Secrec 官方申请文档。
txydns.php,修改 txyaccessKeyId、txyaccessSecrec 变量, API 密钥官方申请文档。
python-version/alydns.py,修改 ACCESS_KEY_ID、ACCESS_KEY_SECRET, API key 和 Secrec 官方申请文档。
godaddydns.php,修改 accessKeyId、accessSecrec 变量,GoDaddy API 密钥官方申请文档。

备注:需要通过 API 操作阿里云 DNS 或腾讯云 DNS 的记录,所以需要去域名服务商哪儿获取 API 密钥。

4:申请证书
特别说明: –manual-auth-hook 指定的 hook 文件四个任选其一(au.sh、autxy.sh、augodaddy.sh、python-version/au.sh),其他操作完全相同。

测试是否有错误

$ ./certbot-auto certonly -d *.example.com –manual –preferred-challenges dns –dry-run –manual-auth-hook /脚本目录/au.sh(autxy.sh 或 python-version/au.sh,下面统一以 au.sh 介绍)

实际申请

$ ./certbot-auto certonly -d *.example.com –manual –preferred-challenges dns –manual-auth-hook /脚本目录/au.sh

参数解释:
certonly:表示采用验证模式,只会获取证书,不会为web服务器配置证书
–manual:表示插件
–preferred-challenges dns:表示采用DNS验证申请者合法性(是不是域名的管理者)
–dry-run:在实际申请/更新证书前进行测试,强烈推荐
-d:表示需要为那个域名申请证书,可以有多个。
–manual-auth-hook:在执行命令的时候调用一个 hook 文件
如果你想为多个域名申请通配符证书(合并在一张证书中,也叫做 SAN 通配符证书),直接输入多个 -d 参数即可,比如:

$ ./certbot-auto certonly -d *.liwf1.net -d *.liwf2.net -d liwf.net –manual –preferred-challenges dns –dry-run –manual-auth-hook /脚本目录/au.sh

5、续期证书
1:对机器上所有证书 renew
$ ./certbot-auto renew –manual –preferred-challenges dns –manual-auth-hook /脚本目录/au.sh

2:对某一张证书进行续期
先看看机器上有多少证书:
$ ./certbot-auto certificates

renew 指定域名证书:
$ ./certbot-auto renew –cert-name simplehttps.com –manual-auth-hook /脚本目录/au.sh

6、加入 crontab自动更新证书
编辑文件 /etc/crontab,由于证书有效期是3个月,我们设置一个月进行一次更新
crontab -e
0 19 1 * * /bin/sh /opt/script/autossl.sh
autossl.sh:
/root/certbot-auto renew –cert-name liwf.net -deploy-hook “nginx -s reload” –manual-auth-hook ./dev/certbot-letencrypt-wildcardcertificates-alydns-au/au.sh
其中:renew成功后nginx自动重新加载配置

注意:只有单机建议这样运行,如果要将证书同步到多台web服务器,需要有别的方案

git技巧-git bisect 查找哪一次代码提交引入了错误

原理:就是将代码提交的历史,按照两分法不断缩小定位。所谓”两分法”,就是将代码历史一分为二,确定问题出在前半部分,还是后半部分,不断执行这个过程,直到范围缩小到某一次代码提交。

$ git clone github.com:test/test.git
$ cd test

启动查错:
$ git bisect start [终点] [起点]

“终点”:最近的提交
“起点”: 更久以前的提交。它们之间的这段历史,就是差错的范围。

例如起点提交4d83cf,终点是最近一次的HEAD

$ git bisect start HEAD 6a83cf

执行上面的命令以后,代码库就会切换到这段范围正当中的那一次提交,本例是第51次提交。(假设HEAD 到6a83cf共101次提交)

<>>>>>>>执行程序看目前版本是否正确,如果正确执行:

$ git bisect good

既然第51次提交没有问题,就意味着错误是在代码历史的后半段引入的。执行上面的命令,Git 就自动切换到后半段的中点(第76次提交)。

<>>>>>>>执行程序看目前版本是否正确,如果不正确执行:

$ git bisect bad

执行上面的命令以后,Git 就自动切换到第51次到第76次的中点(第63次提交)。

接下来,不断重复这个过程,直到成功找到出问题的那一次提交为止。这时,Git 会给出如下的提示。

b23794 is the first bad commit

既然找到那个有问题的提交,就可以检查代码,确定具体是什么错误。

<>>>>>>>退出查错,回到最近一次的代码提交

$ git bisect reset

JSON Web Token

一、跨域认证的问题
互联网服务离不开用户认证。一般流程是下面这样。

1、用户向服务器发送用户名和密码。

2、服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。

3、服务器向用户返回一个 session_id,写入用户的 Cookie。

4、用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。

5、服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。

这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。

举例来说,A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?

一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。

另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。

二、JWT 的原理
JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。

{
“姓名”: “张三”,
“角色”: “管理员”,
“到期时间”: “2018年7月1日0点0分”
}
以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。

服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。

三、JWT 的数据结构
实际的 JWT 大概就像下面这样。

它是一个很长的字符串,中间用点(.)分隔成三个部分。注意,JWT 内部是没有换行的,这里只是为了便于展示,将它写成了几行。

JWT 的三个部分依次如下。

Header(头部)
Payload(负载)
Signature(签名)
写成一行,就是下面的样子。

Header.Payload.Signature

下面依次介绍这三个部分。

3.1 Header
Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。

{
“alg”: “HS256”,
“typ”: “JWT”
}
上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。

最后,将上面的 JSON 对象使用 Base64URL 算法(详见后文)转成字符串。

3.2 Payload
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。

iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号
除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。

{
“sub”: “1234567890”,
“name”: “John Doe”,
“admin”: true
}
注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。

这个 JSON 对象也要使用 Base64URL 算法转成字符串。

3.3 Signature
Signature 部分是对前两部分的签名,防止数据篡改。

首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

HMACSHA256(
base64UrlEncode(header) + “.” +
base64UrlEncode(payload),
secret)
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用”点”(.)分隔,就可以返回给用户。

3.4 Base64URL
前面提到,Header 和 Payload 串型化的算法是 Base64URL。这个算法跟 Base64 算法基本类似,但有一些小的不同。

JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_ 。这就是 Base64URL 算法。

四、JWT 的使用方式
客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。

此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。

Authorization: Bearer
另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。

五、JWT 的几个特点
(1)JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。

(2)JWT 不加密的情况下,不能将秘密数据写入 JWT。

(3)JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。

(4)JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。

(5)JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。

(6)为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。

git合并其他分支特定版本

通过Cherry Pick命令
Considering ‘release’ branch is required to cherry-pick from ‘master’ branch, follow the steps as provided below:
Step 1: Checkout ‘release’ branch

Step 2: Click TostoiseGit -> Show log

Step 3: Filter Source Branch Commits to be Cherry-picked
On the Log Messages window, select the source branch from where the commits will be Cherry Picked (i.e. ‘master’ branch in this example). The branch selection can be done from top left of the ‘Log Messages’ window (as shown below)

Step 4: Select the Commits to be Cherry-picked
Finally select the commits and right-click the context menu to cherry-pick them.

go语言基础笔记

1、当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )。

2、
关键字
下面列举了 Go 代码中会使用到的 25 个关键字或保留字:

break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
除了以上介绍的这些关键字,Go 语言还有 36 个预定义标识符:

append bool byte cap close complex complex64 complex128 uint16
copy false float32 float64 imag int int8 int16 uint32
int32 int64 iota len make new nil panic uint64
print println real recover string true uint uint8 uintptr
程序一般由关键字、常量、变量、运算符、类型和函数组成。
程序中可能会使用到这些分隔符:括号 (),中括号 [] 和大括号 {}。
程序中可能会使用到这些标点符号:.、,、;、: 和 …。

3、程序一般结构

// 当前程序的包名
package main

// 导入其他包
import . "fmt"

// 常量定义
const PI = 3.14

// 全局变量的声明和赋值
var name = "gopher"

// 一般类型声明
type newType int

// 结构的声明
type gopher struct{}

// 接口的声明
type golang interface{}

// 由main函数作为程序入口点启动
func main() {
    Println("Hello World!")
}

4、导入
只有 package 名称为 main 的包可以包含 main 函数。
一个可执行程序有且仅有一个 main 包。

import “fmt”
import “io”

或者
import {
“fmt”,
“io”
}

别名:
package 别名:
// 为fmt起别名为fmt2
import fmt2 “fmt”

5、
通过 const 关键字来进行常量的定义。
通过在函数体外部使用 var 关键字来进行全局变量的声明和赋值。
通过 type 关键字来进行结构(struct)和接口(interface)的声明。
通过 func 关键字来进行函数的声明。
可见性规则
Go语言中,使用大小写来决定该常量、变量、类型、接口、结构或函数是否可以被外部包所调用。
函数名首字母小写即为 private

6、数据类型
1)布尔型
var b bool = true
2)数字类型
1 uint8 无符号 8 位整型 (0 到 255)
2 uint16 无符号 16 位整型 (0 到 65535)
3 uint32 无符号 32 位整型 (0 到 4294967295)
4 uint64 无符号 64 位整型 (0 到 18446744073709551615)
5 int8 有符号 8 位整型 (-128 到 127)
6 int16 有符号 16 位整型 (-32768 到 32767)
7 int32 有符号 32 位整型 (-2147483648 到 2147483647)
8 int64 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807)

浮点型:
1 float32 IEEE-754 32位浮点型数
2 float64 IEEE-754 64位浮点型数
3 complex64 32 位实数和虚数
4 complex128 64 位实数和虚数

其他数字类型
1 byte 类似 uint8
2 rune 类似 int32
3 uint 32 或 64 位
4 int 与 uint 一样大小
5 uintptr 无符号整型,用于存放一个指针

go 1.9版本对于数字类型,无需定义int及float32、float64,系统会自动识别
在 Go 中,布尔值的类型为 bool,值是 true 或 false,默认为 false。

7、变量声明
1) 三种声明方式:
var a int = 10
var b = 10 (自行判定变量类型)
c := 10
2) 多变量声明
//类型相同多个变量, 非全局变量
var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3
var vname1, vname2, vname3 = v1, v2, v3 //自行判定变量类型
vname1, vname2, vname3 := v1, v2, v3 //这种不带声明格式的只能在函数体中出现

// 这种因式分解关键字的写法一般用于声明全局变量
var (
vname1 v_type1
vname2 v_type2
)

变量赋值:
_, b = 5, 7 (5被抛弃,_空白标示符)

&i —获取变量i的内存地址
局部变量定义后不使用会报错
并行赋值可以用于函数 val, err = Func1(var1)

8、常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型
显式类型定义: const b string = “abc”
隐式类型定义: const b = “abc”
枚举常量:
const (
Unknown = 0
Female = 1
Male = 2
)
常量可以用len(), cap(), unsafe.Sizeof()函数计算表达式的值。常量表达式中,函数必须是内置函数,否则编译不过:
package main
import “unsafe”
const (
a = “abc”
b = len(a)
c = unsafe.Sizeof(a)
)

特殊常量:iota
在每一个const关键字出现时,被重置为0,然后再下一个const出现之前,每出现一次iota,其所代表的数字会自动增加1。
iota 可以被用作枚举值:
const (
a = iota
b = iota
c = iota
)
第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;所以 a=0, b=1, c=2 可以简写为如下形式:
const (
a = iota
b
c
)
用法示例:
package main
import “fmt”
func main() {
const (
a = iota //0
b //1
c //2
d = “ha” //独立值,iota += 1
e //”ha” iota += 1
f = 100 //iota +=1
g //100 iota +=1
h = iota //7,恢复计数
i //8
)
fmt.Println(a,b,c,d,e,f,g,h,i)
}
结果:0 1 2 ha ha 100 100 7 8

特别示例:

package main
import "fmt"
const (
    i=1<<iota
    j=3<<iota
    k
    l
)

func main() {
    fmt.Println("i=",i)
    fmt.Println("j=",j)
    fmt.Println("k=",k)
    fmt.Println("l=",l)
}

结果:
i= 1
j= 6
k= 12
l= 24
解释:iota 表示从 0 开始自动加 1,所以 i=1<<0, j=3<<1(<< 表示左移的意思),即:i=1, j=6,这没问题,关键在 k 和 l,从输出结果看 k=3<<2,l=3<<3

9、语言运算符
算术运算符: + – * / % ++ —
关系运算符:== != > < >= <=
逻辑运算符: && || !
位运算符: & | ^ << >>
赋值运算符:= += -= *= /= %= <<= >>= &= ^= |=
其他运算符:&-返回变量存储地址 *-指针变量
运算符优先级
7 ^ !
6 * / % << >> & &^
5 + – | ^
4 == != < <= >= >
3 <-
2 &&
1 ||

10、语言条件语句
if…else
switch: 语句用于基于不同条件执行不同动作
select: select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。
11、语言循环语句
for break continue goto
示例无限循环:

package main
import "fmt"
func main() {
    for true  {
        fmt.Printf("这是无限循环。\n");
    }
}

12、函数
func function_name( [parameter list] ) [return_types] {
函数体
}
示例:

package main
import "fmt"
func swap(x, y string) (string, string) {
   return y, x
}
func main() {
   a, b := swap("Mahesh", "Kumar")
   fmt.Println(a, b)
}

函数参数:值传递、引用传递
函数作为值:

package main
import (
   "fmt"
   "math"
)
func main(){
   getSquareRoot := func(x float64) float64 {
      return math.Sqrt(x)
   }
   fmt.Println(getSquareRoot(9))
}

闭包:

package main
import "fmt"
func getSequence() func() int {
    i := 0
    return func() int {
        i += 1
        return i
    }
}
func main() {
    /* nextNumber 为一个函数,函数 i 为 0 */
    nextNumber := getSequence()
    /* 调用 nextNumber 函数,i 变量自增 1 并返回 */
    fmt.Println(nextNumber())
    fmt.Println(nextNumber())
    fmt.Println(nextNumber())
    /* 创建新的函数 nextNumber1,并查看结果 */
    nextNumber1 := getSequence()
    fmt.Println(nextNumber1())
    fmt.Println(nextNumber1())
}

方法
一个方法就是一个包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针,语法格式:
func (variable_name variable_data_type) function_name() [return_type]{
/* 函数体*/
}

示例:

package main
import (
   "fmt"  
)
/* 定义结构体 */
type Circle struct {
  radius float64
}
func main() {
  var c1 Circle
  c1.radius = 10.00
  fmt.Println("Area of Circle(c1) = ", c1.getArea())
}

//该 method 属于 Circle 类型对象中的方法
func (c Circle) getArea() float64 {
//c.radius 即为 Circle 类型对象中的属性
return 3.14 * c.radius * c.radius
}

13、数组
var variable_name [SIZE] variable_type;
1)初始化
var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0} //初始化数组中 {} 中的元素个数不能大于 [] 中的数字。
如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小:
var balance = […]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

2) 向函数传递数组
void myFunction(param [10]int) {}
void myFunction(param []int) {}

14、指针
var var_name *var-type
空指针判断:if(ptr != nil)

15、结构体
定义:
type struct_variable_type struct {
member definition;
member definition;

member definition;
}
使用,声明:
variable_name := structure_variable_type {value1, value2…valuen}
结构体使用: 结构体.成员名
结构体的指针,不像想像的结果:

package main
import "fmt"
type Books struct {
   title string
   author string
   subject string
   book_id int
}

func main() {
   var Book1 Books        /* Declare Book1 of type Book */
   var Book2 Books        /* Declare Book2 of type Book */

   /* book 1 描述 */
   Book1.title = "Go 语言"
   Book1.author = "www.runoob.com"
   Book1.subject = "Go 语言教程"
   Book1.book_id = 6495407

   /* book 2 描述 */
   Book2.title = "Python 教程"
   Book2.author = "www.runoob.com"
   Book2.subject = "Python 语言教程"
   Book2.book_id = 6495700
   /* 打印 Book1 信息 */
   printBook(&Book1)
   /* 打印 Book2 信息 */
   printBook(&Book2)
}
func printBook( book *Books ) {
   fmt.Printf( "Book title : %s\n", book.title);
   fmt.Printf( "Book author : %s\n", book.author);
   fmt.Printf( "Book subject : %s\n", book.subject);
   fmt.Printf( "Book book_id : %d\n", book.book_id);
}

结果:
Book title : Go 语言
Book author : www.runoob.com
Book subject : Go 语言教程
Book book_id : 6495407
Book title : Python 教程
Book author : www.runoob.com
Book subject : Python 语言教程
Book book_id : 6495700

16、语言切片–动态数组
定义:
未指定大小切片: var identifier []type
指定长度:var slice1 []type = make([]type, len) 简写:slice1 := make([]type, len)
这里 len 是数组的长度并且也是切片的初始长度。
初始化:
s :=[] int {1,2,3 }
s := arr[startIndex:endIndex] //初始化切片s,是数组arr的引用
s :=make([]int,len,cap) //通过内置函数make()初始化切片s,[]int 标识为其元素类型为int的切片
切片是可索引的,并且可以由 len() 方法获取长度
cap(): 计算切片容量,最长可以达到多少
一个切片在未初始化之前默认为 nil,长度为 0

append() 和 copy() 函数:
package main
import “fmt”
func main() {
var numbers []int
printSlice(numbers)
/* 允许追加空切片 /
numbers = append(numbers, 0)
printSlice(numbers)
/
向切片添加一个元素 /
numbers = append(numbers, 1)
printSlice(numbers)
/
同时添加多个元素 /
numbers = append(numbers, 2, 3, 4)
printSlice(numbers)
/
创建切片 numbers1 是之前切片的两倍容量/
numbers1 := make([]int, len(numbers), (cap(numbers))
2)
/* 拷贝 numbers 的内容到 numbers1 */
copy(numbers1, numbers)
printSlice(numbers1)
}
func printSlice(x []int) {
fmt.Printf(“len=%d cap=%d slice=%v\n”, len(x), cap(x), x)
}

17、范围(Range)
示例:
package main
import “fmt”
func main() {
//这是我们使用range去求一个slice的和。使用数组跟这个很类似
nums := []int{2, 3, 4}
sum := 0
for _, num := range nums {
sum += num
}
fmt.Println(“sum:”, sum)
//在数组上使用range将传入index和值两个变量。上面那个例子我们不需要使用该元素的序号,所以我们使用空白符”_”省略了。有时侯我们确实需要知道它的索引。
for i, num := range nums {
if num == 3 {
fmt.Println(“index:”, i)
}
}
//range也可以用在map的键值对上。
kvs := map[string]string{“a”: “apple”, “b”: “banana”}
for k, v := range kvs {
fmt.Printf(“%s -> %s\n”, k, v)
}
//range也可以用来枚举Unicode字符串。第一个参数是字符的索引,第二个是字符(Unicode的值)本身。
for i, c := range “go” {
fmt.Println(i, c)
}
}
结果:
sum: 9
index: 1
b -> banana
a -> apple
0 103
1 111

18、Map(集合)
定义:
/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type

/* 使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)
示例:

package main
import "fmt"
func main() {
    var countryCapitalMap map[string]string /*创建集合 */
    countryCapitalMap = make(map[string]string)

    /* map插入key - value对,各个国家对应的首都 */
    countryCapitalMap["France"] = "Paris"
    countryCapitalMap["Italy"] = "罗马"
    countryCapitalMap["Japan"] = "东京"
    countryCapitalMap["India"] = "新德里"

    /*使用键输出地图值*/
    for country := range countryCapitalMap {
        fmt.Println(country, "首都是", countryCapitalMap[country])
    }

    /*查看元素在集合中是否存在 */
    captial, ok := countryCapitalMap["India"] /*如果确定是真实的,则存在,否则不存在 */
    fmt.Println(captial)
    fmt.Println(ok)
    if ok {
        fmt.Println("美国的首都是", captial)
    } else {
        fmt.Println("美国的首都不存在")
    }
}

删除:delete(countryCapitalMap, “France”)

19、递归函数
20、类型转换
type_name(expression)
21、接口
/* 定义接口 */
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]

method_namen [return_type]
}

/* 定义结构体 /
type struct_name struct {
/
variables */
}

/* 实现接口方法 /
func (struct_name_variable struct_name) method_name1() [return_type] {
/
方法实现 /
}

func (struct_name_variable struct_name) method_namen() [return_type] {
/
方法实现*/
}

package main
import (
    "fmt"
)
type Phone interface {
    call()
}
type NokiaPhone struct {
}
func (nokiaPhone NokiaPhone) call() {
    fmt.Println("I am Nokia, I can call you!")
}
type IPhone struct {
}
func (iPhone IPhone) call() {
    fmt.Println("I am iPhone, I can call you!")
}
func main() {
    var phone Phone
    phone = new(NokiaPhone)
    phone.call()
    phone = new(IPhone)
    phone.call()
}

22、错误处理
type error interface {
Error() string
}

package main
import (
    "fmt"
)
// 定义一个 DivideError 结构
type DivideError struct {
    dividee int
    divider int
}
// 实现 `error` 接口
func (de *DivideError) Error() string {
    strFormat := `
    Cannot proceed, the divider is zero.
    dividee: %d
    divider: 0`
    return fmt.Sprintf(strFormat, de.dividee)
}
// 定义 `int` 类型除法运算的函数
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
    if varDivider == 0 {
        dData := DivideError{
            dividee: varDividee,
            divider: varDivider,
        }
        errorMsg = dData.Error()
        return
    } else {
        return varDividee / varDivider, ""
    }
}
func main() {
    // 正常情况
    if result, errorMsg := Divide(100, 10); errorMsg == "" {
        fmt.Println("100/10 = ", result)
    }
    // 当被除数为零的时候会返回错误信息
    if _, errorMsg := Divide(100, 0); errorMsg != "" {
        fmt.Println("errorMsg is: ", errorMsg)
    }
}

mosquitto-auth-plug使用时使用python3生成授权密码

mosquitto-auth-plug自带工具可以生成授权密码:mosquitto-auth-plug根目录下执行 ./np
在mosquitto-auth-plug包含python版本的密码生成工具:mosquitto-auth-plug-0.1.2/contrib/python/np.py 但是该工具是python2的,由于一直在使用python3,特改之:

使用 2to3.py 转换后发现生成的结果还是不同,通过一行一行调试终于可以了

主要修改:

pbkdf2.py
def pbkdf2_bin(data, salt, iterations=1000, keylen=24, hashfunc=None):
“””Returns a binary digest for the PBKDF2 hash algorithm of data
with the given salt. It iterates iterations time and produces a
key of keylen bytes. By default SHA-1 is used as hash function,
a different hashlib hashfunc can be provided.
“””
hashfunc = hashfunc or hashlib.sha1
mac = hmac.new(data, None, hashfunc)

def _pseudorandom(x, mac=mac):
    h = mac.copy()
    h.update(x)
    lll = h.digest()
    # return list(map(ord, h.digest()))
    return list(h.digest())

buf = []
for block in range(1, -(-keylen // mac.digest_size) + 1):
    rv = u = _pseudorandom(salt + _pack_int(block))
    for i in range(iterations - 1):
        # u = _pseudorandom((''.join(map(chr, u))).encode('utf-8'))
        u = _pseudorandom(bytes(u))
        rv = starmap(xor, zip(rv, u))
        gg = 0
    buf.extend(rv)

# result = ''.join(map(chr, buf))[:keylen]
return bytes(buf)[:keylen]

相关代码见云笔记

Raspberry Pi 3 安装Ubuntu-mate-16.04系统

1、硬件
树莓派 3B
闪迪16G class10 microSD高速内存卡(将系统装在内存卡上)
读卡器
显示器(支持HDMI,或者通过VGA转换)
HDMI线

2、软件
1)、去官网下载原生的系统(http://ubuntu-mate.org/download/),下载下来是一个1个多G的.xz压缩文件,解压后得到.img格式的镜像文件;
2)、下载SDFormatter用来格式化内存卡;
3)、Win32DiskImager用来向内存卡中写入镜像文件。

3、首先要用SDFormatter将SD卡格式化,打开SDFormatter,看到下面的界面,系统自动识别SD卡。

点击“选项设置”,将逻辑大小调整设置为“开启(ON)”,点击OK。

格式化

4、打开Win32DiskImager,选择解压后的镜像文件,选择SD卡设备,点击“写入”。耐心等待镜像写入。。。(我的大约等了7分钟)

这个时候SD卡已经搞定了,退出SD卡,将其插入树莓派,并且通过HDMI转VGA转接线连接好电脑显示器。

5、Ubuntu系统配置
6、打开终端,输入“sudo apt-get update”,回车。
更新完后,重启下就可以正常使用了。

chrome浏览器自动恢复功能导致的坑

普通正常浏览器
关闭浏览器->session过期重新打开session变更

chrome浏览器
关闭浏览器->如果使用自动恢复页面功能->session不过期不更新

导致如下问题:
购物车提交时加入购物车时的session与提交订单的session不一致,想重现,使用chrome一直无法重现,其实通过加入购物车关闭浏览器再打开浏览器(这个时候session会变更)就能重现,但是因为chrome自动恢复页面功能导致session关闭浏览器后不过期一直无法重现…..