标签 docker 下的文章

Hello, Apollo

要说目前哪个技术领域投资最火热,莫过于人工智能。而人工智能领域中最火的(或者说之一)肯定要算上自动驾驶。自动驾驶的概念不是什么新鲜的玩意了,只是随着近两年这一波人工智能的大热,自动驾驶又被推到了风口浪尖。各大汽车厂商、互联网公司也都跃跃欲试,准备给汽车这一“历经百年的黄金平台”做一次新的“赋能”。

今年7月5日,国内搜索引擎No.1企业百度在其首届百度AI开发者大会上发布了Apollo自动驾驶开放平台,同时百度也对外宣布baidu正式从互联网公司转型为一家人工智能公司。作为“错过了移动互联网时代”的典型公司代表,百度这次押宝人工智能,我觉得也是战略上迫不得已的选择:在现有现金牛“搜索广告业务”还能带来大量利润的时候,为抓住未来那头现金牛而进行的努力。而Apollo自动驾驶平台恰是百度人工智能战略的重要组成部分。

Apollo,阿波罗是古希腊神话中的光明之神,这个名字在西方文化中“自带光环”。提到Apollo,很多人还会想到半个多世纪前美国著名的“登月计划”。百度将其自动驾驶平台命名为Apollo,我猜测是有“借势之意”,即期望Apollo这个项目能在百度众多人工智能业务中拥有美好光明的前景。

作为技术人员,我们不能像一般媒体人员那样根据官方提供的“说辞”做宽泛的介绍,我们要与Apoll亲密接触,看看Apollo究竟是什么,究竟能做什么。这里就和大家一起来Say Hello to Apollo。

一、自动驾驶汽车- “百年黄金平台”的新时代赋能

在正式入门Apollo之前,还要说点“废话”。在接触Apollo之前,我从未认真思考过“汽车”这个平台,这次算是“顿悟”,虽然也算不上深刻。就我看来,汽车 是一个不可多得的“黄金平台”。作为一个平台,汽车已经有了上百年的历史,见证了人类科学技术的发展,是跨学科之集大成者。这百年多时间,任何新的、先进的民用技术都会赋能在汽车工业上。以一个长不足5米,重量不超过2t的一般家用乘用车为例,我们在其上面能看到先进的能源技术、材料技术、化工技术、电子技术、通讯技术以及精密的机械原件和组装技术等,可以说汽车为各个公司的创造力提供了展示的舞台。

就普通老百姓的衣食住行而言,汽车也是史无前例的高频使用典范,且是最直接、最贴近普通百姓生活的,这些都是飞机、火车等无法媲美的(如果非要选一个,那只有智能终端能与汽车媲美了,尤其是在集成度方面)。即便是到了科幻片中的漫天跑飞行器的时候,汽车也可能依旧是短距离交通的首选。当然届时的汽车很可能与我们此时的汽车大不相同了。随着时代的进步,汽车也在演化,日新月异的新技术、新材料、新能源对汽车的进一步赋能,因此汽车依旧是朝阳产业,这也是国际资本依旧积极群雄逐鹿汽车工业发展的根本原因了。比如:通过新能源方式赋能汽车的特斯拉、通过无人驾驶技术赋能的Google的waymo等。当然,不仅是从技术方面,从商业模式方面也有围绕着汽车这一平台创新的经典案例,典型的比如:uber滴滴等的高效出行以及近期日渐升温的共享汽车出行。

可以说,各大公司都在从自身优势出发,考虑如何为汽车这一百年黄金平台赋能。从这一点出发,我们就能大致理解百度Apollo的出现了:它是baidu结合自身的技术优势和数据优势拥抱汽车工业、为汽车做新时代赋能而迈出的重要一步。

二、Apollo的技术架构

Apollo是一套完整的自动驾驶技术方案,官方架构原图的截图较为模糊,这里自己画了一个简单的四层结构,每层内的模块暂未画出,因为不是本次入门的重点:

img{512x368}

按照上图,apollo自动驾驶分成四层技术栈,从下到上分别为:

1、Reference Vehicle Platform(参考车辆平台)

自动驾驶最终都要落地到车上,因此apollo抽象了一个”参考车辆平台”层,通过电子化的方式控制车辆的行驶行为。

Note: 在开发者大会上,百度展示了由美国创业公司AutonomouStuff基于Apollo 1.0开放平台改装而成的循迹自动驾驶车,这辆车是一辆美系的林肯MKZ。也就是说当前发布的Apollo适配林肯MKZ是没有问题的。但这款中型车对于普通开发者来说门槛算是稍高了。如果百度能拿出一款大众系、丰田系或至少也应该是一个本田系这样的车型,那对自动驾驶领域的开发者或者说爱好者来说,才是福利。相比而言,著名黑客George Hotz创立的自动驾驶技术公司comma.ai为其openpilot初始选用的车型则是Honda系的思域和CR-V,滥大街的车型,容易搞到,且低成本搞到,也容易改装。

2、Reference Hardware Platform(参考硬件平台)

这一层为自动驾驶汽车提供计算、感知、交互的硬件能力,包括计算单元(车载处理器设备)、GPS/IMU(惯性测量设备)、摄像头、激光雷达、声波雷达、HMI(人机接口)等。在发布的Apollo 1.0版本中,开放的硬件能力包括:计算单元、GPS/IMU(惯性测量设备)以及HMI。

3、Apollo open software Platform (开放软件平台)

这一层是百度Apollo 1.0开放的核心部分,见下图(蓝色的代表在apollo 1.0.0中已经开放的能力):

img{512x368}

从图中看到,这一层还可以分为三个子层,从下至上分别是:

  • apollo kernel层

这一层是运行于硬件上面的OS,对于自动驾驶这种实时性要求特别强的领域,这里显然只能是RTOS(实时操作系统)。Apollo 1.0开放的源码中包含一个”Apollo Kernel“的项目,在这个项目下汇集着可以满足实时性需求的OS kernel。当然目前还仅有一个选择:realtime linux kernel。这是apollo基于Linux Kernel 4.4.32+realtime patch定制的一款专用linux内核。

  • apollo platform层

在Kernel层的上面就是apollo的runtime framework了,提供platform级的支撑。Apollo 1.0同样也创建了一个专用项目:apollo-platform,用于汇集满足apollo平台级支撑需求的platform。当前该项目下也仅提供了一种选择:Apollo ROS,是基于ROS1的Indigo版二次开发后的定制版ROS。Apollo ROS基于自动驾驶需求出发,对ROS1主要做了三方面改进:

  • 为优化自动驾驶大量使用传感器引发很大的传输带宽需求, Apollo ROS改变基于socket的网络传输模式,大量采用共享内存的node间通信机制,减少传输中的数据拷贝,显著提升传输效率, 尤其是在满足一对多的传输场景下效果明显;

  • 从鲁棒性出发,使用RTPS(Real-Time Publish Subscribe)服务发现协议实现完全的P2P网络拓扑,避免原ROS的以Master作为拓扑网络的中心的单点故障问题;

  • 使用protobuf替代原ROSmessage,提供很好的向后兼容,避免接口升级后,不同版本的模块难以兼容的问题。

其实第二点改进也是ROS2正在做的事情。关于Apollo ROS的详尽变化,可以参考前不久百度工程师的一个分享:《Apollo代码开放框架—ROS 探索与实践》

  • apollo modules层

在这一层是apollo的功能modules,当前似乎依旧是基于ROS的package开发的,在github.com/ApolloAuto/apollo/modules/common/apollo_app.cc你大致能看出来一个ROS Package的开发模板。这一层提供诸如:规划(planning)、洞察(perception)、控制(control)、预测(prediction)、决策(decision)、定位等诸多功能。但Apollo 1.0仅仅开放了Control、Localization和HMI三个module,因为这三块足以构成Apollo 1.0提供的封闭场地循迹驾驶体系了。

4、Cloud Services(云端服务)

Apollo 1.0还开放了云端数据平台,以及唤醒万物的DuerOS能力。DuerOS也是Baidu人工智能战略的重要棋子,似乎也是目前Baidu在AI方面最为成熟的、应用最广的产品。当然这一层还包括仿真、高精度地图等服务,不过目前尚未开放。

三、上手Apollo

买不起林肯MKZ的童鞋也不要担心,Apollo 1.0提供了一个本地仿真工具,给你一个与Apollo亲密接触的途径,让你可以在PC上肆无忌惮地玩耍,毕竟Apollo 1.0仅提供封闭场地的寻迹能力,相对简单。

我们的重点是Apollo open software Platform这一层,而这一层中,我们不关心apollo kernel,只关心Apollo ROS和三个已经开放的apollo modules。

1、下载release版本

截至目前为止,Apollo仅发布了一个版本:apollo-v1.0.0,我们可以从github上将其下载到本地:

# wget -c https://github.com/ApolloAuto/apollo/archive/v1.0.0.tar.gz
# tar zxvf v1.0.0.tar.gz
# cd apollo-1.0.0
# ls -F
apollo_docker.sh*  apollo.doxygen  apollo.sh*  AUTHORS.md  BUILD  CPPLINT.cfg
docker/  docs/  LICENSE  modules/  README.md  scripts/  third_party/  tools/  WORKSPACE

注意:我的实验环境为ubuntu 16.04.1 amd64。

2、本地源码构建

对于基于Apollo这个framework的开发者,Apollo官方强烈建议直接采用官方预定义好的专用docker环境(for dev)。对于爱折腾的我而言,必须要在本地做一次源码构建,即使这个体验是糟糕的,甚至最终是失败的^0^。源码构建的命令很简单,一行即可:

# cd apollo-1.0.0
# bash apollo.sh build

在这个过程中,我遇到了两个错误:

  • bazel不存在

Apollo的构建依赖google出品的bazel构建工具,我个人对bazel并没有什么研究,这里先装上再说:

# echo "deb [arch=amd64] http://storage.googleapis.com/bazel-apt stable jdk1.8" |  tee /etc/apt/sources.list.d/bazel.list
deb [arch=amd64] http://storage.googleapis.com/bazel-apt stable jdk1.8

# curl https://bazel.build/bazel-release.pub.gpg | apt-key add -
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  3157  100  3157    0     0   3202      0 --:--:-- --:--:-- --:--:--  3201
OK

# apt-get update && apt-get install bazel
  • third_party/ros/setup.bash: No such file or directory

apollo的编译要依赖ros,但apollo并没有自带ros。我们需要到apollo platform那个项目中去下载Apollo ROS:

# wget -c https://github.com/ApolloAuto/apollo-platform/releases/download/1.0.0/ros-indigo-apollo-1.0.0.x86_64.tar.gz
# tar zxvf ros-indigo-apollo-1.0.0.x86_64.tar.gz
# cd ros
# ls -F
bin/  BUILD  env.sh*  etc/  include/  lib/  setup.bash  setup.sh  _setup_util.py*  setup.zsh  share/

将下载的ros目录copy到apollo-1.0.0/third_party下,并chmod +x third_party/ros/setup.bash。

我们再次执行bash apollo.sh build,这次执行前面的error和warning基本都消失了,apollo.sh脚本开始下载依赖包并编译:

# bash apollo.sh build
ROS_DISTRO was set to 'kinetic' before. Please make sure that the environment does not mix paths from different distributions.
[WARNING] ESD CAN library supplied by ESD Electronics does not exit.
[WARNING] If you need ESD CAN, please refer to third_party/can_card_library/esd_can/README.md
.
____Loading package: modules/common/util/testing
____Loading package: @com_github_grpc_grpc//
____Loading package: @google_styleguide//
____Loading package: @glog//
____Loading package: @eigen//
____Loading package: @gtest//
____Loading package: @civetweb//
____Loading package: @com_github_google_protobuf//
____Loading package: @websocketpp//
____Loading package: @curlpp//
Building on x86_64, with targets:
//tools/platforms:x86_64
//tools/platforms:aarch64
//modules/prediction:prediction
//modules/prediction:prediction_lib
... ...
//modules/common:log
//modules/canbus/proto:canbus_proto.pb
//:x86_64
//:arm64
WARNING: Running Bazel server needs to be killed, because the startup options are different.
INFO: Downloading https://github.com/google/boringssl/archive/master-with-bazel.zip via codeload.github.com: 2,750,374 bytes
INFO: Cloning https://github.com/madler/zlib: Receiving objects (3309 / 5016)
INFO: Downloading https://github.com/google/boringssl/archive/master-with-bazel.zip via codeload.github.com: 2,773,664 bytes
INFO: Cloning https://github.com/madler/zlib: Receiving objects (3314 / 5016)
INFO: Downloading https://github.com/google/boringssl/archive/master-with-bazel.zip via codeload.github.com: 2,795,584 bytes
INFO: Downloading https://github.com/google/boringssl/archive/master-with-bazel.zip via codeload.github.com: 13,504,198 bytes

INFO: Downloading https://github.com/google/boringssl/archive/master-with-bazel.zip via codeload.github.com: 13,522,008 bytes
INFO: Found 190 targets...
[34 / 41] Compiling external/com_github_google_protobuf/src/google/protobuf/compiler/java/java_message_lite.cc [for host]
[41 / 48] Compiling external/com_github_google_protobuf/src/google/protobuf/compiler/command_line_interface.cc [for host]
[157 / 163] Compiling external/com_github_google_protobuf/src/google/protobuf/compiler/javanano/javanano_enum.cc [for host]
[752 / 756] Compiling external/com_github_grpc_grpc/src/core/ext/client_config/resolver_result.c

ERROR: /root/test/apolloauto/apollo-1.0.0/modules/canbus/BUILD:32:1: Linking of rule '//modules/canbus:canbus' failed: gcc failed: error executing command /usr/bin/gcc -o bazel-out/local-dbg/bin/modules/canbus/canbus '-Wl,-rpath,$ORIGIN/../../_solib_k8/_U_S_Sthird_Uparty_Sros_Cros_Ucommon___Uthird_Uparty_Sros_Slib' ... (remaining 8 argument(s) skipped): com.google.devtools.build.lib.shell.BadExitStatusException: Process exited with status 1.
modules/canbus/main.cc:21: error: undefined reference to 'ros::init(int&, char**, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, unsigned int)'
third_party/ros/include/ros/publisher.h:107: error: undefined reference to 'ros::console::initializeLogLocation(ros::console::LogLocation*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, ros::console::levels::Level)'
... ...
collect2: error: ld returned 1 exit status
INFO: Elapsed time: 578.172s, Critical Path: 26.62s
============================
[ERROR] Build failed!
[INFO] Took 597.189 seconds
============================

经过漫长的等待后,还是以失败告终。并且C++的错误输出分析起来真是好痛苦,于是暂时放弃本地源码编译。

3、pre-specified Docker dev环境

既然apollo已经为我们准备好了pre-specified Docker dev环境,我们不妨用一下,下载和启动该环境可以用下面命令:

# cd apollo-1.0.0
# bash docker/scripts/dev_start.sh

apolloauto/apollo:dev-latest这个image超级庞大,大约有7个G左右,所以你需要耐心等待一会儿了。docker运行起来后,我们在另外一个terminal windows下可以执行下面命令切入到该docker容器内部:

# bash docker/scripts/dev_into.sh
root@myhost: /apollo#

在dev container中,我们可以来编译一下apollo源码:

root@myhost:/apollo# bash apollo.sh build
... ...
Copyright (c) 2017 Various License Holders. All Rights Reserved
Apollo software is built on top of various other open source software packages,
a complete list of licenses are located at https://github.com/ApolloAuto/apollo/blob/master/third_party/ACKNOWLEDGEMENT.txt

You agree to the terms of all the License Agreements.

Type 'y' or 'Y' to agree to the license agreement above, or type any other key to exit
y[WARNING] ESD CAN library supplied by ESD Electronics does not exit.
[WARNING] If you need ESD CAN, please refer to third_party/can_card_library/esd_can/README.md
____Loading package: modules/monitor/common
____Loading package: modules/common/adapters
____Loading package: modules/dreamview/conf
____Loading package: modules/control/integration_tests
____Loading package: @google_styleguide//
____Loading package: @com_github_google_protobuf//
... ...
[502 / 1,099] Compiling external/com_github_grpc_grpc/src/core/ext/transport/chttp2/transport/hpack_encoder.c
[914 / 1,524] Compiling external/com_github_grpc_grpc/src/core/ext/census/tracing.c
[1,304 / 1,527] Linking modules/canbus/vehicle/libmessage_manager_base.a

INFO: Elapsed time: 371.151s, Critical Path: 260.93s
============================
[ OK ] Build passed!
[INFO] Took 401.521 seconds
============================

由于dev环境中相关的依赖已经就绪,因此无需过多干预,在漫长的一段等待后,我们看到编译ok了。

4、运行apollo demo

在dev enviroment中或apollo:release-latest中,我们都可以运行apollo的一个寻迹小车的demo。以apollo:release-latest image环境为例:

// 启动基于apollo:release-latest image的apollo container(image size大约为3G,耐心等待下载):

# cd apollo-1.0.0/
# bash docker/scripts/release_start.sh

//切入到容器中去
# bash docker/scripts/release_into.sh
root@myhost:/apollo#

在容器中启动HMI(human-machine interface):

root@myhost:/apollo# bash scripts/hmi.sh
Start roscore...
HMI ros node service running at localhost:8887
HMI running at http://localhost:8887

root@myhostr:/apollo# rosnode list
/hmi_ros_node_service
/rosout

可以看到,hmi.sh脚本启动了roscore(ros master节点和相关服务)以及hmi的service,我们打开浏览器,输入:http://host_ip:8887即可看到如下场景:

img{512x368}

在容器内继续执行如下命令,回放小车的轨迹数据:

# rosbag play -l ./docs/demo_guide/demo.bag

[ INFO] [1502809442.462789096]: Opening ./docs/demo_guide/demo.bag

Waiting 0.2 seconds after advertising topics... done.

Hit space to toggle paused, or 's' to step.
 [RUNNING]  Bag Time: 1497125289.756657   Duration: 20.614178 / 41.613536
 [RUNNING]  Bag Time: 1497125289.896669   Duration: 20.754189 / 41.613536
... ...

我们打开hmi页面上的Debug开关,点击右上角的”Dreamview”按钮,稍后片刻,你就会在新打开的页面上看到小车仿真寻迹行驶的场景了:

img{512x368}

最初实验时,由于没有在阿里云的防火墙打开8888端口,导致dreamview的websocket建立连接失败,dreamview页面始终无法显示出小车。后经与apollo team的ycool在线联调才发现这个问题。这个问题的解决方法也已更新到Apollo的FAQ中了。

四、小结

Baidu为apollo项目做了一个4年的规划(见下面的roadmap),并计划在2020年实现全路网自动驾驶,这个说法似乎有意避开了自动驾驶的级别,这个2020目标到底是L4呢还是L5呢?不过无论是L4还是L5,这个目标都十分有挑战啊。

img{512x368}

个人觉得:未来的L4、L5级别的自动驾驶一定不光光是依靠车辆自身的设备与算法,还要与道路基础设施相配合去实现。甚至是依赖车与车之间的通信才能做到全天候、全路况的自动驾驶。apollo虽然迈出了第一步,但任重道远,让我们拭目以待吧!


微博:@tonybai_cn
微信公众号:iamtonybai
github.com: https://github.com/bigwhite

解决Kubernetes 1.6.4 Dashboard无法访问的问题

前一段时间将之前采用kubeadm安装的Kubernetes 1.5.1环境升级到了1.6.4版本,升级过程较为顺利。由于该k8s cluster是一个测试环境,当时并没有过于关注,就忙别的事情了。最近项目组打算在这个环境下做一些事情,而当我们重新“捡起”这个环境时,发现Kubernetes Dashboard无法访问了。

Kubernetes的dashboard可以有很多种访问方式,比如:可以通过暴露nodeport的方式(无身份验证,不安全)、可以通过访问apiserver的api服务的方式等。我们的Dashboard通过APIServer进行访问:

https://apiserver_ip:secure_port/ui

正常情况下通过浏览器访问:https://apiserver_ip:secure_port/ui,浏览器会弹出身份验证对话框,待输入正确的用户名和密码后,便可成功进入Dashboard了。但当前,我们得到的结果却是:

User "system:anonymous" cannot proxy services in the namespace "kube-system".

而访问apiserver(https://apiserver_ip:secure_port/)得到的结果如下:

User "system:anonymous" cannot get  at the cluster scope.

一、问题原因分析

k8s 1.6.x版本与1.5.x版本的一个很大不同在于1.6.x版本启用了RBACAuthorization mode(授权模型),这点在K8s master init的日志中可以得到证实:

# kubeadm init --apiserver-advertise-address xx.xx.xx
... ...
[init] Using Kubernetes version: v1.6.4
[init] Using Authorization mode: RBAC
[preflight] Running pre-flight checks
[preflight] Starting the kubelet service
[certificates] Generated CA certificate and key.
[certificates] Generated API server certificate and key
.... ...
[apiconfig] Created RBAC rules
[addons] Created essential addon: kube-proxy
[addons] Created essential addon: kube-dns

Your Kubernetes master has initialized successfully!
... ...

《Kubernetes集群的安全配置》一文中我们提到过Kubernetes API server的访问方法:

Authentication(身份验证) -> Authorization(授权)-> Admission Control(入口条件控制)

只不过在Kubernetes 1.5.x及以前的版本中,Authorization的环节都采用了默认的配置,即”AlwaysAllow”,对访问APIServer并不产生什么影响:

# kube-apiserver -h
... ...
--authorization-mode="AlwaysAllow": Ordered list of plug-ins to do authorization on secure port. Comma-delimited list of: AlwaysAllow,AlwaysDeny,ABAC,Webhook,RBAC
... ...

但K8s 1.6.x版本中,–authorization-mode的值发生了变化:

# cat /etc/kubernetes/manifests/kube-apiserver.yaml

spec:
  containers:
  - command:
    - kube-apiserver
    - --allow-privileged=true
    ... ...
    - --basic-auth-file=/etc/kubernetes/basic_auth_file
    - --authorization-mode=RBAC
    ... ...

注:这里我们依旧通过basic auth方式进行apiserver的Authentication,而不是用客户端数字证书校验等其他方式。

显然问题的原因就在于这里RBAC授权方式的使用,让我们无法正常访问Dashboard了。

二、Kubernetes RBAC Authorization简介

RBAC Authorization的基本概念是Role和RoleBinding。Role是一些permission的集合;而RoleBinding则是将Role授权给某些User、某些Group或某些ServiceAccount。K8s官方博客《RBAC Support in Kubernetes》一文的中的配图对此做了很生动的诠释:

img{512x368}

从上图中我们可以看到:

Role: pod-reader 拥有Pod的get和list permissions;
RoleBinding: pod-reader 将Role: pod-reader授权给右边的User、Group和ServiceAccount。

和Role和RoleBinding对应的是,K8s还有ClusterRole和ClusterRoleBinding的概念,它们不同之处在于:ClusterRole和ClusterRoleBinding是针对整个Cluster范围内有效的,无论用户或资源所在的namespace是什么;而Role和RoleBinding的作用范围是局限在某个k8s namespace中的。

Kubernetes 1.6.4安装时内建了许多Role/ClusterRole和RoleBinds/ClusterRoleBindings:

# kubectl get role -n kube-system
NAME                                        AGE
extension-apiserver-authentication-reader   50d
system:controller:bootstrap-signer          50d
system:controller:token-cleaner             50d

# kubectl get rolebinding -n kube-system
NAME                                 AGE
system:controller:bootstrap-signer   50d
system:controller:token-cleaner      50d

# kubectl get clusterrole
NAME                                           AGE
admin                                          50d
cluster-admin                                  50d
edit                                           50d
system:auth-delegator                          50d
system:basic-user                              50d
system:controller:attachdetach-controller      50d
... ...
system:discovery                               50d
system:heapster                                50d
system:kube-aggregator                         50d
system:kube-controller-manager                 50d
system:kube-dns                                50d
system:kube-scheduler                          50d
system:node                                    50d
system:node-bootstrapper                       50d
system:node-problem-detector                   50d
system:node-proxier                            50d
system:persistent-volume-provisioner           50d
view                                           50d
weave-net                                      50d

# kubectl get clusterrolebinding
NAME                                           AGE
cluster-admin                                  50d
kubeadm:kubelet-bootstrap                      50d
kubeadm:node-proxier                           50d
kubernetes-dashboard                           50d
system:basic-user                              50d
system:controller:attachdetach-controller      50d
... ...
system:controller:statefulset-controller       50d
system:controller:ttl-controller               50d
system:discovery                               50d
system:kube-controller-manager                 50d
system:kube-dns                                50d
system:kube-scheduler                          50d
system:node                                    50d
system:node-proxier                            50d
weave-net                                      50d

三、Dashboard的role和rolebinding

Kubernetes 1.6.x启用RBAC后,诸多周边插件也都推出了适合K8s 1.6.x的manifest描述文件,比如:weave-net等。Dashboard的manifest文件中也增加了关于rolebinding的描述,我当初用的是1.6.1版本,文件内容摘录如下:

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

我们看到在kubernetes-dashboard.yaml中,描述文件新建了一个ClusterRoleBinding:kubernetes-dashboard。该binding将ClusterRole: cluster-admin授权给了一个ServiceAccount: kubernetes-dashboard。我们看看ClusterRole: cluster-admin都包含了哪些permission:

# kubectl get clusterrole/cluster-admin -o yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
  creationTimestamp: 2017-05-30T14:06:39Z
  labels:
    kubernetes.io/bootstrapping: rbac-defaults
  name: cluster-admin
  resourceVersion: "11"
  selfLink: /apis/rbac.authorization.k8s.io/v1beta1/clusterrolescluster-admin
  uid: 331c79dc-4541-11e7-bc9a-12584ec3a8c9
rules:
- apiGroups:
  - '*'
  resources:
  - '*'
  verbs:
  - '*'
- nonResourceURLs:
  - '*'
  verbs:
  - '*'

可以看到,在rules设定中,cluster-admin似乎拥有了“无限”权限。不过注意:这里仅仅授权给了一个service account,并没有授权给user或group。并且这里的kubernetes-dashboard是dashboard访问apiserver时使用的(下图右侧流程),并不是user访问APIServer时使用的。

img{512x368}

我们需要给登录dashboard或者说apiserver的user(图左侧)进行授权。

四、为user: admin进行授权

我们的kube-apiserver的启动参数中包含:

    - --basic-auth-file=/etc/kubernetes/basic_auth_file

也就是说我们访问apiserver使用的是basic auth的身份验证方式,而user恰为admin。而从本文开头的错误现象来看,admin这个user并未得到足够的授权。这里我们要做的就是给admin选择一个合适的clusterrole。但kubectl并不支持查看user的信息,初始的clusterrolebinding又那么多,一一查看十分麻烦。我们知道cluster-admin这个clusterrole是全权限的,我们就来将admin这个user与clusterrole: cluster-admin bind到一起:

# kubectl create clusterrolebinding login-on-dashboard-with-cluster-admin --clusterrole=cluster-admin --user=admin
clusterrolebinding "login-on-dashboard-with-cluster-admin" created

# kubectl get clusterrolebinding/login-on-dashboard-with-cluster-admin -o yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  creationTimestamp: 2017-07-20T08:57:07Z
  name: login-on-dashboard-with-cluster-admin
  resourceVersion: "5363564"
  selfLink: /apis/rbac.authorization.k8s.io/v1beta1/clusterrolebindingslogin-on-dashboard-with-cluster-admin
  uid: 686a3f36-6d29-11e7-8f69-00163e1001d7
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: admin

binding后,我们再来访问一下dashboard UI,不出意外的话,熟悉的dashboard界面就会出现在你的眼前。

注:Kubernetes API Server新增了–anonymous-auth选项,允许匿名请求访问secure port。没有被其他authentication方法拒绝的请求即Anonymous requests, 这样的匿名请求的username为”system:anonymous”, 归属的组为”system:unauthenticated”。并且该选线是默认的。这样一来,当采用chrome浏览器访问dashboard UI时很可能无法弹出用户名、密码输入对话框,导致后续authorization失败。为了保证用户名、密码输入对话框的弹出,需要将–anonymous-auth设置为false:

// /etc/kubernetes/manifests/kube-apiserver.yaml
    - --anonymous-auth=false

用curl测试结果如下:

$curl -u admin:YOUR_PASSWORD -k https://apiserver_ip:secure_port/
{
  "paths": [
    "/api",
    "/api/v1",
    "/apis",
    "/apis/apps",
    "/apis/apps/v1beta1",
    "/apis/authentication.k8s.io",
    "/apis/authentication.k8s.io/v1",
    "/apis/authentication.k8s.io/v1beta1",
    "/apis/authorization.k8s.io",
    "/apis/authorization.k8s.io/v1",
    "/apis/authorization.k8s.io/v1beta1",
    "/apis/autoscaling",
    "/apis/autoscaling/v1",
    "/apis/autoscaling/v2alpha1",
    "/apis/batch",
    "/apis/batch/v1",
    "/apis/batch/v2alpha1",
    "/apis/certificates.k8s.io",
    "/apis/certificates.k8s.io/v1beta1",
    "/apis/extensions",
    "/apis/extensions/v1beta1",
    "/apis/policy",
    "/apis/policy/v1beta1",
    "/apis/rbac.authorization.k8s.io",
    "/apis/rbac.authorization.k8s.io/v1alpha1",
    "/apis/rbac.authorization.k8s.io/v1beta1",
    "/apis/settings.k8s.io",
    "/apis/settings.k8s.io/v1alpha1",
    "/apis/storage.k8s.io",
    "/apis/storage.k8s.io/v1",
    "/apis/storage.k8s.io/v1beta1",
    "/healthz",
    "/healthz/ping",
    "/healthz/poststarthook/bootstrap-controller",
    "/healthz/poststarthook/ca-registration",
    "/healthz/poststarthook/extensions/third-party-resources",
    "/healthz/poststarthook/rbac/bootstrap-roles",
    "/logs",
    "/metrics",
    "/swaggerapi/",
    "/ui/",
    "/version"
  ]
}


微博:@tonybai_cn
微信公众号:iamtonybai
github.com: https://github.com/bigwhite

解决登录Harbor Registry时鉴权失败的问题

今天在测试之前搭建好的高可用Harbor时,发现了一个问题:使用docker login harbor时,有时成功,有时失败:

# docker login -u user -p passwd http://hub.my-domain.com:36666
Login Succeeded

# docker login -u user -p passwd http://hub.my-domain.com:36666
Error response from daemon: login attempt to http://hub.my-domain.com:36666/v2/ failed with status: 401 Unauthorized

我们在DNS中将hub.my-domain.com这个域名解析成两个IP,分别是两个Harbor节点的public IP,这可能是问题的诱发原因,但我还不知道问题根源在哪里。以下是问题的查找过程记录。

1、保证每个Harbor node都是可以login ok的

我在client端通过修改/etc/hosts将hub.my-domain.com分别解析成上述说到的两个node IP并测试。测试结果表明:无论单独解析成哪个IP,docker login http://hub.my-domain.com:36666都会100%的成功。

2、查看两个Harbor node上的registry log,弄清问题现象

将/etc/hosts中hub.my-domain.com的硬解析删除,恢复DNS解析。打开两个terminal tab分别监视连个Harbor node上的registry的日志。经过几次测试,发现一个现象:当docker login成功时,都是一个node上的日志出现更新;而当docker login fail时,我们会看到两个Node上的registry日志都有变化,似乎请求发给了两个node

node1:
Jun 15 14:40:01 172.19.0.1 registry[30242]: time="2017-06-15T06:40:01.245822446Z" level=debug msg="authorizing request" go.version=go1.7.3 http.request.host="hub.my-domain.com:36666" http.request.id=62add46e-e176-4eb8-b36a-84a9fbe7ac9c http.request.method=GET http.request.remoteaddr=xx.xx.xx.xx http.request.uri="/v2/" http.request.useragent="docker/1.12.5 go/go1.6.4 git-commit/7392c3b kernel/4.4.0-58-generic os/linux arch/amd64 UpstreamClient(Docker-Client/1.12.5 \\(linux\\))" instance.id=43380207-7b61-4d45-b06a-a017c9a075af service=registry version="v2.4.1+unknown"

Jun 15 14:40:01 172.19.0.1 registry[30242]: time="2017-06-15T06:40:01.246002519Z" level=error msg="token signed by untrusted key with ID: \"BASH:RNPJ:PEBU:7THG:2NAR:OSFV:CG6U:ANV4:CCNB:ODZR:4BL6:TMD6\""

node2:

Jun 15 14:40:01 172.18.0.1 registry[28674]: time="2017-06-15T06:40:01.213604228Z" level=debug msg="authorizing request" go.version=go1.7.3 http.request.host="hub.my-domain.com:36666" http.request.id=bb6eeb8f-99f1-47a0-8cae-dae9b402b758 http.request.method=GET http.request.remoteaddr=xx.xx.xx.xx http.request.uri="/v2/" http.request.useragent="docker/1.12.5 go/go1.6.4 git-commit/7392c3b kernel/4.4.0-58-generic os/linux arch/amd64 UpstreamClient(Docker-Client/1.12.5 \\(linux\\))" instance.id=2a364e0c-425f-47a9-b144-887d439243ba service=registry version="v2.4.1+unknown"

Jun 15 14:40:01 172.18.0.1 registry[28674]: time="2017-06-15T06:40:01.21374491Z" level=warning msg="error authorizing context: authorization token required" go.version=go1.7.3 http.request.host="hub.my-domain.com:36666" http.request.id=bb6eeb8f-99f1-47a0-8cae-dae9b402b758 http.request.method=GET http.request.remoteaddr=xx.xx.xx.xx http.request.uri="/v2/" http.request.useragent="docker/1.12.5 go/go1.6.4 git-commit/7392c3b kernel/4.4.0-58-generic os/linux arch/amd64 UpstreamClient(Docker-Client/1.12.5 \\(linux\\))" instance.id=2a364e0c-425f-47a9-b144-887d439243ba service=registry version="v2.4.1+unknown"

Jun 15 14:40:01 172.18.0.1 registry[28674]: 172.18.0.3 - - [15/Jun/2017:06:40:01 +0000] "GET /v2/ HTTP/1.1" 401 87 "" "docker/1.12.5 go/go1.6.4 git-commit/7392c3b kernel/4.4.0-58-generic os/linux arch/amd64 UpstreamClient(Docker-Client/1.12.5 \\(linux\\))"

3、探寻Harbor原理,弄清问题根源

打开harbor在github.com的wiki页,在”Architecture Overview of Harbor“中我找到了docker login的流程:

img{512x368}

从图片上,我一眼就看到了从docker client发出的*”两个请求: a和c流程”,看来docker client的确不止一次向Harbor发起了请求。wiki上对docker login流程给了简明扼要的解释。大致的流程是:

  • docker向registry发起请求,由于registry是基于token auth的,因此registry回复应答,告诉docker client去哪个URL去获取token;
  • docker client根据应答中的URL向token service(ui)发起请求,通过user和passwd获取token;如果user和passwd在db中通过了验证,那么token service将用自己的私钥(harbor/common/config/ui/private_key.pem)生成一个token,返回给docker client端;
  • docker client获得token后再向registry发起login请求,registry用自己的证书(harbor/common/config/registry/root.crt)对token进行校验。通过则返回成功,否则返回失败。

从这个原理,我们可以知道问题就出在docker client多次向Harbor发起请求这个环节:对于每次请求,DNS会将域名可能解析为不同IP,因此不同请求可能落到不同的node上。这样当docker client拿着node1上token service分配的token去到node2的registry上鉴权时,就会出现鉴权失败的情况。

4、统一私钥和证书,问题得以解决

token service的私钥(harbor/common/config/ui/private_key.pem)和registry的证书(harbor/common/config/registry/root.crt)都是在prepare时生成的,两个节点都独立prepare过,因此两个node上的private_key.pem和root.crt是不同的,这就是问题根源。

解决这个问题很简单,就是统一私钥和证书。比如:将node1上的private_key.pem和root.crt复制到node2上,并重新创建node2上的container:

// node2上

将node1上的harbor/common/config/ui/private_key.pem复制到node2上的harbor/common/config/ui/private_key.pem;
将node1上的harbor/common/config/registry/root.crt复制到harbor/common/config/registry/root.crt;

$ docker-compose down -v
$ docker-compose up -d

更换了private_key.pem和root.crt的node2上的Harbor启动后,再进行login测试,就会100%成功了!

# docker login -u admin -p passwd http://hub.my-domain.com:36666
Login Succeeded

微博:@tonybai_cn
微信公众号:iamtonybai
github.com: https://github.com/bigwhite




这里是Tony Bai的个人Blog,欢迎访问、订阅和留言!订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:


以太币:


如果您喜欢通过微信App浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:



本站Powered by Digital Ocean VPS。

选择Digital Ocean VPS主机,即可获得10美元现金充值,可免费使用两个月哟!

著名主机提供商Linode 10$优惠码:linode10,在这里注册即可免费获得。

阿里云推荐码:1WFZ0V立享9折!

View Tony Bai's profile on LinkedIn


文章

评论

  • 正在加载...

分类

标签

归档











更多