后端开发 (9)


利用context和kubectx快速切换kubernetes集群

在使用kubectl操作kubernetes集群的过程中,可能会遇到需要操作不同集群的问题,例如本地的minikube集群和线上的部署集群,或是线上的测试集群和线上的部署集群,如果集群之间切换不频繁,也可以使用笨办法,比如准备多个config文件在/.kube文件夹下,要切换的时候就改config的名字。不过对于需要频繁切换操作集群的人来说,这个方法显然太麻烦了,当然也不够cool。

当然,k8s是非常完善的集群解决方案,肯定是考虑到了这个问题的,这里就用到了k8s的config文件的context(上下文)了。

如果你是从零开始配置两个集群的cluster, user等信息的,那么可以参照官方的这个文档

Define-clusters-users-and-contexts

这里就不详细说以上这种方法了,我们来说说已经存在了两个集群的config文件时如何简单的生成context,当然,在上述文档的后半部分,也有我要说明的这些,也可以去看英文原文,我这里是因为遇到了一个坑,所以详细解释一下。

首先说方法,我们都知道kubectl如果没有设置config相关位置的环境变量,那么其会默认去一个地方寻找,对于Mac来说,就是

$HOME/.kube/config

如果要自己指定一个config文件所在的位置,那么就要设置KUBECONFIG环境变量,例如:

export KUBECONFIG=$KUBECONFIG:$HOME/.kube/config

而如果有两个集群的配置文件呢,其实就可以非常简单的直接加在环境变量的后面,然后让kubectl自动为我们生成context,例如:

export KUBECONFIG=$KUBECONFIG:$HOME/.kube/config:$HOME/.kube/config-2

注意:可以使用

kubectl config view

来查看目前的context的情况

那么坑在哪里呢,由于两个集群的配置文件可能是管理员只分配给你自己使用的,那么用户名就可能是相同的,在用户名相同的情况下,如果集群的连接启用了TLS验证,那么kubectl自己融合的config文件就会使用相同的client-certificate-data和client-key-data,如果两个集群在同一服务商的同一片网络下还好,可是如果是在不同的网络下,那么就会出现只能连接上其中一个,而另一个集群无法连接的情况,这种时候,我的解决办法是修改用户名,但要注意,在config里的context下,可以看到user和name两个字段,我们要修改的是user字段,这里只是一个名称,不影响集群侧的用户验证,修改完user字段,记得翻到users字段下,那里面有一个name,这个name的值应该和刚刚修改之前的user字段中的值一样,把这里也修改成同样的新值,保存,重新配置环境变量使之生效,这时候就可以使用了。

至于官方提供的切换集群的方法,在上述文档中也能找到,这里也不在赘述,这里主要介绍一个第三方工具来帮我们更快捷的做这个事:
kubectx

这个工具可以给context指定alias并快速的切换他们,简单的介绍一下

kubectx                     : 列出目前所有的context
kubectx <NAME>              : 切换到<NAME>对应的context
kubectx -                   : 切换到上一次的context
kubectx <NEW NAME>=<NAME>   : 给<NAME>context命名一个新名字
kubectx <NAME>=.            : 给当前context命名一个新名字

可以看出,在完成了上述config文件自动融合之后,使用这个工具就可以快速的切换不同的操作集群,同时这个工具还支持bash/zsh的tab自动补全,以及fzf这个快速选择工具,可以说从此以后切换集群就可以如丝般顺滑了,不过缺点就是kubectx这个工具会影响对kubectl的自动补全,全看各位的习惯了
 




Mysql更新排名的取巧实现

最近的项目连续遇到了对用户排名的需求,一个要求不用并列,一个要求同分并列,因为用户体量的考虑,拒绝了实时更新(即每个用户查询排名时获取排名),改为定时更新,在网上查了大量的MySQL排名算法之后,发现大部分的文章是以查询时获取来实现,而非定时更新所有人的排名,所以经过一番研究之后自己写了一个算法,欢迎斧正

首先我们假设有一张user表,其中有四个字段,score,rank,score_rank,join_time分别代表用户的分数,排名,获取排名时的分数,及用户加入时间

首先是不考虑并列的情况

SET @r = 0
UPDATE user
SET `rank` = @r := (@r + 1), `score_rank` = `score`
WHERE `score` != 0
ORDER BY `score` DESC, `join_time` DESC // 因为没有并列,所以增加一个排序

然后是考虑并列的情况

SET @r = 0;
SET @l = 0;
SET @s = 1;
UPDATE `user` SET `rank` = (
CASE
WHEN `score` = @l THEN @r := @r + (@s := @s + 1) - @s
ELSE @r := (@r + @s) + (@s := 1) - 1 END), `score_rank` = @l := (`score`)
WHERE `score` != 0
ORDER BY `score` DESC

这里唯一需要注意的事情就是MySQL中的变量问题,变量只能在两个地方赋值,SET或SELECT时,其余的地方只允许在变量赋值给其他字段时才允许被修改,所以就是在这里我们开始取巧,来解释一下并列时的思想:

r,l,s分别是当前排名,当前排名的分数,和当前同分的人数

当某一行的数据与上一行的分数相同时,即 score = @l,那么这时应该给rank赋值为当前的排名,但同时应该更新同分人数,所以我们这样来实现:@r := @r + (@s := @s + 1) – @s,这样s实现了自增,但对返回结果r没有影响

当某一行的数据与上一行的分数不同时,则应该给rank赋值r+1,但同时我们希望给l赋值当前的分数,并把同分人数s归为1,则选择这样实现:@r := (@r + @s) + (@s := 1) – 1

关于速度

这个算法在我这边的模拟测试中,三万条的数据排序用时0.4秒,所以肯定不能作为实时处理,计划是每天定时更新一回,操作时间也算是可以接受了。不知道其他算法的时间是会是多少,欢迎提供下对比。




Golang生产环境中time包的zonefile.zip问题

前段时间给程序加了个功能,打印时间按照时区打印,具体看起来是这样

    loc, _ := time.LoadLocation("Asia/Chongqing")
    return time.Now().In(loc).Format("2006-01-02 15:04:05")

在本地运行没有丝毫问题,于是就想当然的推到了线上,结果线上的环境在进行到这步的时候直接panic了,嗯,看来是过于的自信导致了问题,明显loc是nil了,那么打印下错误吧,结果看到了这么一行:

open /usr/local/go/lib/time/zoneinfo.zip: no such file or directory

于是瞬间就明白了,我们线上使用的是docker镜像,复制到容器里的go程序也是编译好的二进制文件,所以容器里没有go环境,导致在本地能正常运行的程序在线上因为缺少对应文件而panic。

知道了问题,就好解决了,当然这里可以直接换加了go环境的镜像,但是明显这样违背了docker作为线上生产环境的初衷,即最小可用,加入了go环境会直接把大量的冗余数据加入到容器中,使单个容器的体积直接倍增,所以我们只需要把这个特定依赖的文件复制到容器中即可,在Dockerfile中加入这样一段:

    COPY ./zoneinfo.zip /usr/local/go/lib/time/zoneinfo.zip
    // 这里我把这个文件也在本地复制到了我的项目中,因为我们的打包上线工作由线上的CI服务来完成,如果是在本地打包,那么可以直接复制原文件
    COPY /usr/local/go/lib/time/zoneinfo.zip /usr/local/go/lib/time/zoneinfo.zip

最后说一句,下划线无视error什么的,是真不可取啊,吸取教训把上面的代码也换成了

     loc, err := time.LoadLocation("Asia/Chongqing")

	if err != nil {
		LogError("parse location err:", err)
		loc = time.Local
	}

	return time.Now().In(loc).Format("2006-01-02 15:04:05")



升级MacOS10.13 High Sierra导致PHPMyAdmin无法正常显示的问题

升级了新的版本之后,一切介正常,唯独发现pma打不开了,变成了一堆php代码,猜想是自带的Apache出了问题,参考网上其他教程,打开了httpd.conf,发现果然和php有关的那个loadmodule被注释掉了,打开,重启Apache,无果。继续Google,发现还要加上AddType和AddHandler,加上,重启Apache,仍然无果,但发现php info检测的代码可以正常打开了,遂怀疑是自带的PHP升级了,不再兼容当前版本的pma了,于是按照官方流程升级pma,重新打开,正常工作,解决战斗。

以下是详细三部曲:

  • 修改httpd.conf, 开启PHP loadmodule

打开Terminal,输入:sudo nano /etc/apache2/httpd.conf, 对httpd.conf进行编辑,按ctrl+W,搜索php7_module,找到:#LoadModule php7_module libexec/apache2/libphp7.so,将此行前的#号去掉,ctrl+O保存(此处可能弹出finder,关掉即可),ctrl+X退出

参考文献: PHP精通 mac本地安装php环境 找不到webserver 更改php文件目录 运行

  •  修改httpd.conf,增加php文件支持

同上,搜索IfModule,在任意位置添加如下代码:

<IfModule php7_module>
AddType application/x-httpd-php .php
AddType application/x-httpd-php-source .phps

<IfModule dir_module>
DirectoryIndex index.html index.php
</IfModule>
</IfModule>

参考文献:PHPMyAdmin showing code instead of webpage

  • 升级到最新版本的pma

    1. 从以下网址下载最新版本的PHPmyadmin:https://www.phpmyadmin.net/downloads/
    2. 重命名旧的pma文件夹(例如:phpmyadmin-old)
    3. 解压并复制并重命名刚刚下载好的新版本pma到需要的位置(例如:phpmyadmin)
    4. 从旧的文件夹中拷贝config.inc.php文件到新文件夹中
    5. 测试是否一切正常工作
    6. 删除旧文件夹

翻译自官方文档:phpMyAdmin-Docs-安装-从旧版本更新

PS:Reference websites may require ladders.




Docker-Go 交叉编译运行Go程序

最近在学习docker和kubernetes,无奈本机搭了一个minikube无论如何无法启容器,k8s的日志就只告诉说没起来我在重启,剩下什么原因也不说,于是决定先在本地docker启动一下看看。

原本的后端应用上线是我写好之后由我的boss统一上线的,所以Dockerfile也不是我写的,当然线上能跑,本地就肯定能跑啦,于是直接docker build,docker run,然后就出现问题了

./main: line 1: syntax error: unexpected “(”

随便搜了一下,发现基本上出现这个问题是因为运行程序的内核不同,然后仔细研究了一下Dockerfile,发现使用的是alpine的镜像,这个镜像的特点就是非常小,只有几兆,而且对比了一下公司的Dockerfile和golang官方alpine镜像的Dockerfile,发现我的Dockerfile里并没有集成go语言环境,而是直接将编译好的程序拷贝到镜像中,那么问题其实就可以确定了,我本地编译的基于Mac的go程序,即基于darwin内核的go程序,而alpine是基于Linux的内核的,自然无法运行。

知道了问题解决方法就很好办了,如果使用liteIDE的同学更是省事,直接删掉原本编译好的程序,选择cross-linux64环境编译就好(不用去改现有的darwin环境)

就算不是使用的LiteIDE,那也可以使用如下语句打包:

cd $GOPATH
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 ./make.bash

以上两步是为了生成linux平台所需要的工具和环境,只需要运行一次即可,如果使用liteIDE的话在切换环境的同时就会自动帮你准备好这些的
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build

再次运行docker,成功启动容器!