分类 技术志 下的文章

Hello, ROS

ROS,全称是Robot Operating System,字面译为“机器人操作系统”。不过ROS并非是一个真正意义上的操作系统,而仅仅是一套用于机器人操作和控制软件开发的开发框架(framework),包括各种库和工具。

ROS在2007年诞生于斯坦福大学人工智能实验室Stanford Artificial Intelligence Laboratory,简称SAIL;2008年至2013年,ROS的开发和推广由Willow Garage公司(该公司2014年已关门大吉)主导。2013年8月,ROS的管理权转移给了Open Source Robotics Foundation。截至目前,ROS已经成为全世界使用和支持最为广泛的机器人开发框架之一。

一、ROS简介

ROS推出的初衷旨在降低机器人类软件开发的门槛,提高复用率,避免机器人软件开发人员的重复劳动,快速搭建机器人原型。因此,它采用了当时流行的面向服务SOA的软件架构,最大程度上降低内部耦合,并且易于被第三方扩展。采用C++作为主要开发语言,提高ROS的可移植性,让ROS可以很方便地移植到其他各种CPU体系和OS上。

ROS最初的是针对单机家用移动智能机器人而设计的,因此ROS1版本在以下几方面尚存不足:

  • 鲁棒性

ROS1版本运行时仅有一个master node,一旦master node发生crash,整个robot将无法正常工作。

  • 安全性

ROS1内部完全不设防,Node间通信完全是信赖的。任何Node都可以轻易得到其他node的各种topic数据、参数以及访问相关关键service。

  • 实时性

在ROS1的设计约束下,ROS内部各个节点间产生的实时数据通过master建立的内部网络在各个node间传递。一旦数据量很大,数据可能因内部网络通信性能问题而导致延迟,致使机器人工作异常。这也是ROS在工业机器人领域并未受到“热烈欢迎”的重要原因之一。

为了解决上述这些问题,ROS启动了ROS2的设计和实现。ROS2的第一个alpha版本发布于2015年,最新一个版本是今年七月份发布的beta2版本,ROS2的1.0版本计划将于今年年末正式发布。不过对于ROS2,笔者了解也不多,感兴趣的童鞋可以移步其wiki观看详情。

二、ROS1安装

在深入ROS1之前,我们先来安装一个ROS1。我们首先需要选择一个ROS1的发布版。ROS的发布模式与Ubuntu极其相似:每逢偶数年份发布一个长期支持版(LTS),support 5年;每逢奇数年份发布一个支持2年的版本。

并且ROS的发布版与Ubuntu发布版有着“神同步”:

2014:     ROS Indigo Igloo  对应  Ubuntu 14.04 LTS
2016:     ROS Kinetic Kame 对应 Ubuntu 16.04 LTS

ROS主要基于Ubuntu这款OS进行开发和测试,所以官方建议ROS尽量与Ubuntu一并使用,当然其他linux distribution也可以安装ROS,但正确性和稳定性ROS不能给予明确的保证。目前ROS1发布版的最新版本为:ROS Lunar Loggerhead,但官方推荐使用Ubuntu 16.04 + ROS Kinetic Kame组合;不过由于KK版本发布也就一年出头,市面上更多组织采用的可能还是Ubuntu 14.04 + ROS Indigo Igloo组合。

这里以Ubuntu 16.04+ ROS Kinetic Kame简单说明一下ROS1的安装过程:

1、获取source list并update源

# sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'
# apt-key adv --keyserver hkp://ha.pool.sks-keyservers.net:80 --recv-key 421C365BD9FF1F717815A3895523BAEEB01FA116
Executing: /tmp/tmp.gJDpQgL6qG/gpg.1.sh --keyserver
hkp://ha.pool.sks-keyservers.net:80
--recv-key
421C365BD9FF1F717815A3895523BAEEB01FA116
gpg: requesting key B01FA116 from hkp server ha.pool.sks-keyservers.net
gpg: key B01FA116: public key "ROS Builder <rosbuild@ros.org>" imported
gpg: Total number processed: 1
gpg:               imported: 1

如果需要代理,可以用:
apt-key adv --keyserver-options http-proxy=<myProxy>  --keyserver hkp://ha.pool.sks-keyservers.net:80 --recv-key 421C365BD9FF1F717815A3895523BAEEB01FA116

# apt-get update

2、安装kk版本

ROS有几个release配置供你选择安装:ROS-Base、Desktop Install和Desktop-Full Install,Desktop-Full Install是官方推荐的选项,也是安装最全的选项,它包含了ROS, rqt, rviz, robot-generic libraries, 2D/3D simulators, navigation and 2D/3D perception等package:

# apt-get install ros-kinetic-desktop-full

这个过程需要好长一段时间(依你的网络情况而定),因为ROS超级庞大,有大约2G的安装文件要下载安装。

3、初始化ROS依赖

在使用ROS之前,我们还得先初始化ROS的一些依赖,ROS为你提供了“一键式”的初始化命令:

# rosdep init
Wrote /etc/ros/rosdep/sources.list.d/20-default.list
Recommended: please run

    rosdep update

# rosdep update
reading in sources list data from /etc/ros/rosdep/sources.list.d
Hit https://raw.githubusercontent.com/ros/rosdistro/master/rosdep/osx-homebrew.yaml
Hit https://raw.githubusercontent.com/ros/rosdistro/master/rosdep/base.yaml
... ...
Hit https://raw.githubusercontent.com/ros/rosdistro/master/rosdep/python.yaml
Hit https://raw.githubusercontent.com/ros/rosdistro/master/rosdep/ruby.yaml
Hit https://raw.githubusercontent.com/ros/rosdistro/master/releases/fuerte.yaml
Query rosdistro index https://raw.githubusercontent.com/ros/rosdistro/master/index.yaml
Add distro "groovy"
Add distro "hydro"
Add distro "indigo"
Add distro "jade"
Add distro "kinetic"
Add distro "lunar"
updated cache in /home/tonybai/.ros/rosdep/sources.cache
... ...

到这里,我们可以看到ROS被安装到/opt/ros/kinetic下面了:

# tree -L 1  /opt/ros/kinetic
/opt/ros/kinetic
├── bin
├── env.sh
├── etc
├── include
├── lib
├── setup.bash
├── setup.sh
├── _setup_util.py
├── setup.zsh
└── share

5 directories, 5 files

4、设置环境变量

ROS提供了设置环境变量的脚本:/opt/ros/kinetic/setup.bash,我们将其加入到.bashrc中,这样每次用户登录后就可以使用下面这些ROS专属环境变量了:

# echo "source /opt/ros/kinetic/setup.bash" >> ~/.bashrc
# source ~/.bashrc

# env|grep ROS
ROS_ROOT=/opt/ros/kinetic/share/ros
ROS_PACKAGE_PATH=/opt/ros/kinetic/share
ROS_MASTER_URI=http://localhost:11311
ROSLISP_PACKAGE_DIRECTORIES=
ROS_DISTRO=kinetic
ROS_ETC_DIR=/opt/ros/kinetic/etc/ros

5、安装一些用于ROS package构建的工具依赖

ROS的用户会创建自己的ROS package,为了方便构建这些user package,我们需要安装以下一些工具:

# apt-get install python-rosinstall python-rosinstall-generator python-wstool build-essential

6、验证安装结果

完成以上操作后,ROS kk版本就安装OK了,我们来验证一下安装结果是否正确。我们来尝试启动一下ROS的master node:

# roscore
... logging to /root/.ros/log/fc6a002e-75cf-11e7-b053-00163e1001d7/roslaunch-myhost-7609.log
Checking log directory for disk usage. This may take awhile.
Press Ctrl-C to interrupt
Done checking log file disk usage. Usage is <1GB.

started roslaunch server http://myhost:43606/
ros_comm version 1.12.7

SUMMARY
========

PARAMETERS
 * /rosdistro: kinetic
 * /rosversion: 1.12.7

NODES

auto-starting new master
process[master]: started with pid [7620]
ROS_MASTER_URI=http://myhost:11311/

setting /run_id to fc6a002e-75cf-11e7-b053-00163e1001d7
process[rosout-1]: started with pid [7633]
started core service [/rosout]

如果你看到上面这些roscore的输出,那么基本就证明你的ROS1安装成功了!

三、ROS架构

ROS安装完毕后,我们来对ROS做进一步的探索!先来看看ROS1的架构。

ROS文档中将ROS架构分为三个级别:Filesystem level、Computation Graph level和Community level。对于一个framework来说,从字面意义上理解这三个level还是有些晦涩的。Community level先不说,我们可以通过对照来理解Filesystem level和Computation Graph level,实质上它们一个对应的是ROS的静态结构,一个对应的则是ROS的运行时结构。

1、ROS Filesystem level

我们这里借用《Effective Robotics Programming with ROS 3rd》中的图来整体看一下ROS Filesystem的概念:

img{512x368}

ROS实质上是由一系列的packages组成的,在packages的基础上,ROS通过metapackage来聚合一组packages以形成一个逻辑package。基于metapackage和package概念,ROS为开发者提供了在package之间跳转、文件拷贝、包查找、执行等功能的”类FileSystem”命令集合,比如:roscd、rosls、roscp、rosrun、roscat、rospack等。下面是一些命令使用的例子:

// 切换到ros安装目录
root@myhost:~# roscd
root@myhost:/opt/ros/kinetic#

// 切换到turtlesim包目录
root@myhost:~# roscd turtlesim
root@myhost:/opt/ros/kinetic/share/turtlesim#

// list turtlesim包内的文件
root@myhost:~# rosls turtlesim
cmake  images  msg  package.xml  srv

// 查找turtlesim包的路径
root@myhost:~# rospack find turtlesim
/opt/ros/kinetic/share/turtlesim

// 执行包turtlesim下的turtlesim_node
root@myhost:~# rosrun turtlesim turtlesim_node

// 查看包turtlesim的package.xml内容
root@myhost:~# roscat turtlesim package.xml

<?xml version="1.0"?>
<package>
  <name>turtlesim</name>
  <version>0.7.1</version>
  <description>
    turtlesim is a tool made for teaching ROS and ROS packages.
  </description>
... ...
</package>

ROS安装后,其所有package均存储在$ROS_PACKAGE_PATH下面,初始情况下即为/opt/ros/kinetic/share:

root@myhost:/opt/ros/kinetic# ls share
actionlib                         eigen_stl_containers          laser_pipeline         rosbag_migration_rule  roswtf                 shape_msgs
actionlib_msgs                    executive_smach               librviz_tutorial       rosbag_storage         rqt_action             simulators
actionlib_tutorials               filters                       map_msgs               ros_base               rqt_bag                smach
... ...

每个package下的结构都类似,以turtlesim包为例:

root@myhost:/opt/ros/kinetic/share# ls -F turtlesim
cmake/  images/  msg/  package.xml  srv/

至此,上面图片中package中的结构似乎与上面看到的turtlesim package中的结构对应上了。每个package下面都至少有一个package.xml作为package的manifests,msg、srv是功能性配置,分别定义了package用到的message和提供的service的结构。这里并没有代码,只是一些配置信息。

而对应的包的可执行文件则在/opt/ros/kinetic/lib下,还是以turtlesim package为例,当我们执行下面命令时:

# rosrun turtlesim turtlesim_node

rosrun首先会到$ROS_PACKAGE_PATH下找是否有package.xml中name为”turtlesim”的package(与目录的名字无关)。如果有,rosrun会到/opt/ros/kinetic/lib/turtlesim下查找是否有turtlesim_node这个二进制可执行文件。存在,则启动之;否则报错。

root@myhost:/opt/ros/kinetic/lib/turtlesim# ls
draw_square  mimic  turtlesim_node  turtle_teleop_key

root@myhost:/opt/ros/kinetic/lib/turtlesim# rosrun turtlesim turtlesim_node
[ INFO] [1501549501.410816841]: Starting turtlesim with node name /turtlesim
[ INFO] [1501549501.428589492]: Spawning turtle [turtle1] at x=[5.544445], y=[5.544445], theta=[0.000000]

还有一种package:metapackage。metapackage在目录结构上与普通package无异,但package.xml尾部多了metapackage标签,我们以ros_core/package.xml为例:

<package>
  <name>ros_core</name>
  <version>1.3.1</version>

  <buildtool_depend>catkin</buildtool_depend>

  <run_depend>catkin</run_depend>
  <run_depend>cmake_modules</run_depend>
  <run_depend>common_msgs</run_depend>
  <run_depend>gencpp</run_depend>
  <run_depend>geneus</run_depend>
  <run_depend>genlisp</run_depend>
  <run_depend>genmsg</run_depend>
  <run_depend>gennodejs</run_depend>
  <run_depend>genpy</run_depend>
  <run_depend>message_generation</run_depend>
  <run_depend>message_runtime</run_depend>
  <run_depend>ros</run_depend>
  <run_depend>ros_comm</run_depend>
  <run_depend>rosbag_migration_rule</run_depend>
  <run_depend>rosconsole_bridge</run_depend>
  <run_depend>roscpp_core</run_depend>
  <run_depend>rosgraph_msgs</run_depend>
  <run_depend>roslisp</run_depend>
  <run_depend>rospack</run_depend>
  <run_depend>std_msgs</run_depend>
  <run_depend>std_srvs</run_depend>

  <export>
    <metapackage/>
  </export>
</package>

这种包称为metapackage,它的实质是一组package的集合。

2、ROS Computation Graph level

说完了ROS的静态结构,我们再来看看ROS整体的运行时结构,即ROS Computation Graph level:

img{512x368}

ROS在运行时层面是由一个master和一组node组成的,master的作用就是名字注册和查找,建立node与topic间联系以及服务发现之用。node间的通信方式可以是:

  • 服务srv调用
  • topic的发布和订阅

我们通过rosnode命令可以操作node,比如查看当前ROS中node信息:

# rosnode list
/rosout
/turtlesim

/rosout node是一个由roscore命令启动的特殊node,它相当于整个ROS运行环境的stdout/stderr,将所有node发往/rosout topic的消息汇聚在一起。

每个ROS运行时环境有且仅有一个ros master,ros master通过执行roscore命令启动,这也是一个ROS运行环境启动最先应该执行的命令:

# roscore
... logging to /home/tonybai/.ros/log/ee13b88e-7666-11e7-af90-4ccc6a7061a6/roslaunch-tonybai-myhost-26158.log
Checking log directory for disk usage. This may take awhile.
Press Ctrl-C to interrupt
Done checking log file disk usage. Usage is <1GB.

started roslaunch server http://tonybai-myhost:36180/
ros_comm version 1.12.7

SUMMARY
========

PARAMETERS
 * /rosdistro: kinetic
 * /rosversion: 1.12.7

NODES

auto-starting new master
process[master]: started with pid [26169]
ROS_MASTER_URI=http://tonybai-myhost:11311/

setting /run_id to ee13b88e-7666-11e7-af90-4ccc6a7061a6
process[rosout-1]: started with pid [26182]
started core service [/rosout]

roscore位于/opt/ros/kinetic/bin下,它实际上是一个python脚本,它调用位于/opt/ros/kinetic/lib/python2.7/dist-packages/roslaunch下的roslaunch lib,并依据launch配置文件/opt/ros/kinetic/etc/ros/roscore.xml启动对应的核心node:

// /opt/ros/kinetic/etc/ros/roscore.xml
<!--
  ROS Core Stack definition

  Before making any modifications to this file, please read:

http://ros.org/wiki/roscore

  -->
<launch>
  <group ns="/">
    <param name="rosversion" command="rosversion roslaunch" />
    <param name="rosdistro" command="rosversion -d" />
    <node pkg="rosout" type="rosout" name="rosout" respawn="true"/>
  </group>
</launch>

roscore会自动启动master,master对应的是一个metapackage: ros。ros package的package.xml如下:

<package>
  <name>ros</name>
  <version>1.13.5</version>
  <description>ROS packaging system</description>
  <maintainer email="dthomas@osrfoundation.org">Dirk Thomas</maintainer>
  ... ...

  <buildtool_depend>catkin</buildtool_depend>

  <run_depend>catkin</run_depend> <!-- only for backward compatibility with rosbuild -->
  <run_depend>mk</run_depend>
  <run_depend>rosbuild</run_depend>
  <run_depend>roslang</run_depend>
  <run_depend>roslib</run_depend>
  <run_depend>rosbash</run_depend>
  <run_depend>rosboost_cfg</run_depend>
  <run_depend>rosclean</run_depend>
  <run_depend>roscreate</run_depend>
  <run_depend>rosmake</run_depend>
  <run_depend>rosunit</run_depend>

  <export>
    <metapackage/>
  </export>
</package>

ROS的运行时当前目录为~/.ros,在这个目录下你会看到ros的一些运行时输出信息:

$ tree  -L 1 ~/.ros
/home/tonybai/.ros
├── log/
├── roscore-11311.pid
├── rosdep/
├── rospack_cache_00988404638878154258
├── rospack_cache_04359245844500407984
├── rospack_cache_05251971726343818934
├── rospack_cache_11134725904490598093
├── rosstack_cache_00988404638878154258
├── rosstack_cache_04359245844500407984
├── rosstack_cache_05251971726343818934
└── rosstack_cache_11134725904490598093

2 directories, 9 files

roscore还会启动一个Parameter Server,用于各个节点保存或读取parameters,通过rosparam可以查看相关param信息,比如当前param的list:

$ rosparam list
/background_b
/background_g
/background_r
/rosdistro
/roslaunch/uris/host_tonybai_myhost__36180
/rosversion
/run_id

我们可以通过ros提供的rqt_graph命令查看node之间以及node与topic之间的订阅和发布关系,如下图:

img{512x368}

3、ROS的“分布式”源码结构

安装过程中ROS的“庞大”,与ROS在github上源码库的“渺小”形成鲜明对比。其实我们安装的ROS与这份源码库并非一一对应的:ROS的源码结构也是“分布式”的,ROS源码实质上是一系列package源码的组合。当前版本的ROS发布版采用bloom工具进行release的。以kk版本为例,bloom读取一份rosdistro库的kk版本distribution.yaml文件(这份文件比较庞大),即ROS发布文件,并根据文件中的描述信息,下载对应的package源码并编译构建的:

// kinetic/distribution.yaml)
%YAML 1.1
# ROS distribution file
# see REP 143: http://ros.org/reps/rep-0143.html
---
release_platforms:
  debian:
  - jessie
  fedora:
  - '23'
  - '24'
  ubuntu:
  - xenial
repositories:
  abb:
    doc:
      type: git
      url: https://github.com/ros-industrial/abb.git
      version: kinetic-devel
    release:
      packages:
      - abb
      - abb_driver
      - abb_resources
      ... ...
      tags:
        release: release/kinetic/{package}/{version}
      url: https://github.com/ros-industrial-release/abb-release.git
      version: 1.3.0-1
    source:
      type: git
      url: https://github.com/ros-industrial/abb.git
      version: kinetic
    status: developed
  abb_experimental:
    doc:
      type: git
      url: https://github.com/ros-industrial/abb_experimental.git
      version: kinetic-devel
    status: developed
... ...
type: distribution
version: 2

鉴于ROS这种分布式的相对松散的源码组织结构,对ROS的裁剪则相对简单,只需挑选你自己需要的第三方包即可。

四、启动你的第一个ROS“机器人”

ROS虽然号称机器人开发框架,但拥有一个实体版机器人并不是进行ROS开发的必要条件。ROS的一大优势就是可以利用各种仿真工具进行机器人操作和控制逻辑的仿真和调试。常见的仿真器主要有三个:TurtlesimRviz+arbotixGazebo。Turtlesim是一个QT开发的2D轨迹显示界面,只能显示运动轨迹;arbotix是含有一个差速驱动机器人的控制器,结合rviz使用,用于机器人运动及topic数据的3D显示,但不包含物理学引擎;Gazebo是全功能的3D物理模拟器,要用这个模拟器,需要掂量掂量你的主机的内存和显卡是否够用。

本文是入门文章,我们就从turtlesim开始。假设此时roscore已经启动了。

我们来启动一下turtlesim_node:

# rosrun turtlesim turtlesim_node
[ INFO] [1501549501.410816841]: Starting turtlesim with node name /turtlesim
[ INFO] [1501549501.428589492]: Spawning turtle [turtle1] at x=[5.544445], y=[5.544445], theta=[0.000000]

这时你的desktop会出现一个新的窗口,如下图:

img{512x368}

不过此时小海龟一动不动!如果要让它移动,我们需要告诉他如何移动!

我们启动另外一个node – turtle_teleop_key:

# rosrun turtlesim turtle_teleop_key
Reading from keyboard
---------------------------
Use arrow keys to move the turtle.

通过turtle_teleop_key,我们可以使用方向键控制小海龟的移动了:

img{512x368}

其原理在于:turtle_teleop_key将方向键产生的数据转换为位置信息后,发布到topic: /turtle1/cmd_vel上;turtlesim_node由于subscribe了该topic,因此将接收到新的位置数据,这样小海龟就会移动到新的位置上去:

img{512x368}

turtlesim node启动后还启动了一个service: spawn,调用该服务我们可以在窗口上创建出一个新的小海龟:

# rosservice call /spawn 2 2 0.2 ""
name: turtle2

img{512x368}

可以看到,通过service调用或向topic发布数据,我们可以自由控制小海龟。下面的是一个稍微复杂的控制指令,其结果就是让小海龟1进行持续的转圈动作:

rostopic pub /turtle1/cmd_vel geometry_msgs/Twist -r 1 -- '[2.0, 0.0, 0.0]' '[0.0, 0.0, -1.8]'

img{512x368}

五、创建你的第一个ROS package

现在我们来创建第一个ROS package!

1、初始化ros workspace

我们要添加自己的ROS package,一般不会直接在ROS的安装目录下创建,因此我们需要创建自己的workspace,并在后续将其加入到ROS_PACKAGE_PATH中,以使得ros的文件系统命令也能适用于我们自己的workspace路径。

# mkdir -p ~/myros_ws/src
# cd ~/myros_ws/src
# catkin_init_workspace
Creating symlink "/home/tonybai/myros_ws/src/CMakeLists.txt" pointing to "/opt/ros/kinetic/share/catkin/cmake/toplevel.cmake"

$ tree ~/myros_ws/
/home/tonybai/myros_ws/
└── src
    └── CMakeLists.txt -> /opt/ros/kinetic/share/catkin/cmake/toplevel.cmake

1 directory, 1 file

2、创建Package

我们来创建一个我们自己的package – chattingsim:

# cd ~/myros_ws/src
# catkin_create_pkg chattingsim std_msgs rospy roscpp
Created file chattingsim/package.xml
Created file chattingsim/CMakeLists.txt
Created folder chattingsim/include/chattingsim
Created folder chattingsim/src
Successfully created files in /home/tonybai/myros_ws/src/chattingsim. Please adjust the values in package.xml.

# tree chattingsim/
chattingsim/
├── CMakeLists.txt
├── include
│   └── chattingsim
├── package.xml
└── src

3 directories, 2 files

虽然目前我们的chattingsim package并没有任何有意义的代码,但不妨碍我们先来编译一下myros_ws这个workspace:

# cd ~/myros_ws/
# catkin_make

# catkin_make
Base path: /home/tonybai/myros_ws
Source space: /home/tonybai/myros_ws/src
Build space: /home/tonybai/myros_ws/build
Devel space: /home/tonybai/myros_ws/devel
Install space: /home/tonybai/myros_ws/install
####
#### Running command: "cmake /home/tonybai/myros_ws/src -DCATKIN_DEVEL_PREFIX=/home/tonybai/myros_ws/devel -DCMAKE_INSTALL_PREFIX=/home/tonybai/myros_ws/install -G Unix Makefiles" in "/home/tonybai/myros_ws/build"
####
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
... ...
-- Looking for pthread_create in pthread
-- Looking for pthread_create in pthread - found
-- Found Threads: TRUE
-- Found gtest sources under '/usr/src/gtest': gtests will be built
-- Using Python nosetests: /usr/bin/nosetests-2.7
-- catkin 0.7.6
-- BUILD_SHARED_LIBS is on
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- ~~  traversing 1 packages in topological order:
-- ~~  - chattingsim
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- +++ processing catkin package: 'chattingsim'
-- ==> add_subdirectory(chattingsim)
-- Configuring done
-- Generating done
-- Build files have been written to: /home/tonybai/myros_ws/build
####
#### Running command: "make -j4 -l4" in "/home/tonybai/myros_ws/build"
####

catkin_make后,myros_ws下面又增加了不少目录和文件:

~/myros_ws$ tree -L 2
.
├── build
│   ├── catkin
│   ├── catkin_generated
│   ├── CATKIN_IGNORE
│   ├── catkin_make.cache
│   ├── chattingsim
│   ├── CMakeCache.txt
│   ├── CMakeFiles
│   ├── cmake_install.cmake
│   ├── CTestTestfile.cmake
│   ├── gtest
│   ├── Makefile
│   └── test_results
├── devel
│   ├── env.sh
│   ├── lib
│   ├── setup.bash
│   ├── setup.sh
│   ├── _setup_util.py
│   ├── setup.zsh
│   └── share
└── src
    ├── chattingsim
    └── CMakeLists.txt -> /opt/ros/kinetic/share/catkin/cmake/toplevel.cmake

12 directories, 12 files

我们看到~/myros_ws/devel目录下的结构与/opt/ros/kinetic下的非常相似,我们将其加入到ROS_PACKAGE_PATH:

# cd ~/myros_ws/devel
# source ./setup.bash
# echo $ROS_PACKAGE_PATH
/home/tonybai/myros_ws/src:/opt/ros/kinetic/share

3、添加talker和listener

chattingsim package的架子已经搭好,接下来我们开始“填肉”。这里我们直接使用ros tutorials中写好的两个源文件talker.cpplistener.cpp,我们把这两个文件放在~/myros_ws/src/chattingsim/src下面。

在build之前,我们需要修改一下chattingsim的CMakeLists.txt:

cmake_minimum_required(VERSION 2.8.3)
project(chattingsim)

find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  genmsg
)

generate_messages(DEPENDENCIES std_msgs)

include_directories(
  include ${catkin_INCLUDE_DIRS}
)

add_executable(talker src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
add_dependencies(talker chattingsim_generate_messages_cpp)

add_executable(listener src/listener.cpp)
target_link_libraries(listener ${catkin_LIBRARIES})
add_dependencies(listener chattingsim_generate_messages_cpp)

构建chattingsim package:

~/myros_ws# catkin_make
Base path: /home/tonybai/myros_ws
Source space: /home/tonybai/myros_ws/src
Build space: /home/tonybai/myros_ws/build
Devel space: /home/tonybai/myros_ws/devel
Install space: /home/tonybai/myros_ws/install
####
#### Running command: "make cmake_check_build_system" in "/home/tonybai/myros_ws/build"
####
####
#### Running command: "make -j4 -l4" in "/home/tonybai/myros_ws/build"
####
[  0%] Built target std_msgs_generate_messages_eus
[  0%] Built target std_msgs_generate_messages_cpp
[  0%] Built target std_msgs_generate_messages_lisp
[  0%] Built target std_msgs_generate_messages_py
[  0%] Built target std_msgs_generate_messages_nodejs
[  0%] Built target chattingsim_generate_messages_cpp
[  0%] Built target chattingsim_generate_messages_lisp
[ 14%] Generating EusLisp manifest code for chattingsim
[ 28%] Building CXX object chattingsim/CMakeFiles/talker.dir/src/talker.cpp.o
[ 28%] Built target chattingsim_generate_messages_nodejs
[ 42%] Generating Python msg __init__.py for chattingsim
[ 57%] Generating Python srv __init__.py for chattingsim
[ 71%] Building CXX object chattingsim/CMakeFiles/listener.dir/src/listener.cpp.o
[ 71%] Built target chattingsim_generate_messages_py
[ 71%] Built target chattingsim_generate_messages_eus
[ 71%] Built target chattingsim_generate_messages
[ 85%] Linking CXX executable /home/tonybai/myros_ws/devel/lib/chattingsim/talker
[ 85%] Built target talker
[100%] Linking CXX executable /home/tonybai/myros_ws/devel/lib/chattingsim/listener
[100%] Built target listener

4、启动chattingsim的talker node和listener node

在两个terminal窗口分别启动listener node和talker node:

# rosrun chattingsim listener
[ INFO] [1501577165.148477238]: I heard: [hello world 3]
[ INFO] [1501577165.248349227]: I heard: [hello world 4]
[ INFO] [1501577165.348301478]: I heard: [hello world 5]
[ INFO] [1501577165.448340592]: I heard: [hello world 6]
[ INFO] [1501577165.548433696]: I heard: [hello world 7]
[ INFO] [1501577165.648466054]: I heard: [hello world 8]
[ INFO] [1501577165.748424131]: I heard: [hello world 9]
[ INFO] [1501577165.848457076]: I heard: [hello world 10]
[ INFO] [1501577165.948449431]: I heard: [hello world 11]
[ INFO] [1501577166.048470110]: I heard: [hello world 12]
[ INFO] [1501577166.148340964]: I heard: [hello world 13]

# rosrun chattingsim talker
[ INFO] [1501577164.847745179]: hello world 0
[ INFO] [1501577164.947898377]: hello world 1
[ INFO] [1501577165.047889213]: hello world 2
[ INFO] [1501577165.147882701]: hello world 3
[ INFO] [1501577165.247923700]: hello world 4
[ INFO] [1501577165.347918242]: hello world 5
[ INFO] [1501577165.447917169]: hello world 6
[ INFO] [1501577165.547916593]: hello world 7
[ INFO] [1501577165.647920474]: hello world 8
[ INFO] [1501577165.747930882]: hello world 9
[ INFO] [1501577165.847917356]: hello world 10
[ INFO] [1501577165.947918365]: hello world 11
[ INFO] [1501577166.047918187]: hello world 12
[ INFO] [1501577166.147919712]: hello world 13
^C[ INFO] [1501577166.247984284]: hello world 14

至此,基于我们第一个package: chattingsim而创建的node工作正常!

六、小结

如果说人工智能的算法是大脑,实体的机械部件构成四肢,那么ROS则提供了大脑与各肢体间提供了神经传递的机制。之前ROS在国内发展的不瘟不火,随着Baidu Apollo项目将ros作为Apollo-platform支持的一部分,更多人会去了解ROS,ROS在国内的发展势也许会走得更顺畅一些。ROSCon 2017也即将于下月在加拿大温哥华召开,ROS2对ROS1的安全性和实时性的加强也势必会让ROS有更多用武之地,值得期待。

注:ros wiki 资料非常详尽,ros tutorial是学习ros的起点,几乎不用任何其他书籍。


微博:@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

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

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

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

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

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

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats