0%

相关链接

Ukraine/Kharkiv(乌克兰/哈尔科夫)

https://github.com/Mirantis/disk_perf_test_tool

https://github.com/Mirantis/disk_perf_test_tool/wiki

http://koder-ua.github.io/

Overview(概览)

Wally是一种以分布式方式测量不同类型block storages性能的工具,并提供全面的报告。它在分布式和云环境中运行, 但也可以测量单个磁盘。

Wally在控制的方式中投入了大量精力, 从统计的角度正确的处理结果, 提供你可以依靠、争论和理解的数字。

Wally不是负载生成工具。它使用众所周知的负载发生器 - [fio]来测试系统并为其提供包装, 这有助于discovering cluster features and settings,install sensors,为测试preparing system,并行运行多个测试节点的复杂test suites并可视化结果。

主要特点:

  • Cluster and storage preparation,获得尽可能多的可重复结果
  • 与[openstack],[ceph]和[fuel]集成
  • 分布式执行测试
  • 与[fio]紧密结合
  • VM产卵在OS测试
  • Sensors subsystem,用于在测试期间收集群集设备上的负载
  • 简单而灵活的配置文件,允许指定集群结构并选择在加载期间收集信息
  • 综合可视化报告
  • 资源消耗报告,允许查看用于向客户端提供服务的群集资源量
  • 瓶颈分析器,有助于找到影响结果的部件
  • yaml/cvs基于所有结果的存储,因此您可以轻松地将它们插入到结果处理管道中
  • wally可以在故障阶段失败时重新启动测试

wally不能做什么:

  • Deploy/configure storage - 测试系统必须准备好进行测试
  • 在测试执行期间更新报告。沃利是完全 cli 工具, 没有 UI, 报告在测试完成后生成
  • 提供交互式报告。所有图/表都是用 matplotlib 并静态生成的。

Basic architecture overview(基本架构概述)

Wally代码主要由3部分组成 - [agent library],[cephlib] 和 [wally] 本身。Agent library负责提供与群集和测试节点的 [RPC] 连接。Cephlib 包含大部分storage、discovery、sensors、数据处理和可视化代码。Wally本身提供 cli, 负载工具集成, 报告生成和其他部分。

fio是一个主要的负载工具,很好的集成在wally内。 wally有自己的fio版本,为一些Linux发行版构建。wally也可以使用系统fio,但需要安装最新版本之一。fio配置文件位于wally/suits/io目录中,具有cfg扩展名。default_qd.cfg是具有默认设置的文件,它主要包含所有其他配置。ceph.cfg,hdd.cfg,cinder_iscsi.cfg是一个主要的测试suites。cfg文件是一个fio配置文件,由wally提供了一些额外的功能。测试之前,wally在所选的cfg文件中插入提供的设置,展开cycles,将其拆分为jobs并从测试节点逐个同步执行jobs。

虽然fio提供了一些这样的功能,但是wally不会使用它们来更精确地控制结果。

要运行测试需要一个配置文件,其中包含cluster信息,sensors设置,test config和一些其他变量来控制测试执行和处理结果。配置文件的示例位于configs-examples目录中。该目录中的所有配置文件都includes default.yaml,反过来default.yaml中includes logging.yaml。在大多数情况下,您无需更改default.yaml/logging.yaml文件中的任何内容。配置文件详细描述如下。

wally执行由各个阶段组成, 大多数阶段都映射配置文件模块。主要阶段有:

  • 群集发现
  • 通过ssh连接节点
  • 使用rpc服务器检测节点
  • 安装sensors和相应的配置文件
  • 运行测试
  • 生成报告
  • 清理

Wally motivation(Wally动机)

主要测试问题和wally如何为您修复

Howto install wally

Container

1
2
git clone https://github.com/Mirantis/disk_perf_test_tool.git
docker build -t <username>/wally .

Local installation

1
2
3
apt install g++ ....
pip install XXX python -m wally prepare << download fio, compile
可根据Dockerfile查看安装过程

Howto run a test

要运行测试,您需要准备集群和配置文件。
如何运行wally:直接使用容器

Configuration

  • SSHURI - 格式为 [user[:passwd]@]host[:port][:key_file]的字符串,其中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
user - str,用户名,默认为当前用户
passwd - str,ssh密码,如果提供了key_file_rr或使用了默认密钥,则可以省略ssh密码
host - str,唯一必填字段。主机名或IP地址
port - int,要连接的ssh服务器端口,默认为22
key_file - str, ssh私有密钥文件(private)的路径。默认~/.ssh/id_rsa by default. 如果端口被省略, 但提供了key_file - 它必须与host分开两列。

不能同时使用passwd和key_file。
例如:
11.12.23.10:37 - 使用ip 和 ssh 端口, 当前用户和 ~/.ssh/id_rsa key
ceph-1 - 仅使用主机名,默认端口,当前用户和 ~/.ssh/id_rsa key
ceph-12::~/.ssh/keyfile - 使用当前用户和 ~/.ssh/keyfile key
root@master - 以root身份登录并使用 ~/.ssh/id_rsa key
root@127.0.0.1:44 - 以root身份登录,使用44端口 和 ~/.ssh/id_rsa key
user@10.20.20.30::/tmp/keyfile - 以root身份登录 和 /tmp/keyfile key
root:rootpassword@10.20.30.40 - 以root身份登录并使用rootpassword作为ssh密码
  • [XXX] - XXX类型列表

  • {XXX: YYY} - 从类型XXX(键key)映射到类型YYY(值value)

  • SIZE - 带有K/M/G/T/P或不带的整数。请注意,使用1024 base,10M实际上意味着10MiB == 10485760 Bytes,依此类推。

Default settings(默认设置)

许多配置设置在config-examples/default.yaml文件中已经有可用的默认值,在大多数情况下,用户可以重复使用它们。在你自己的配置文件中,include它:

include: default.yaml

您可以在配置文件中覆盖选定的设置。

Plain settings(普通设置)

  • discover: [str]

列表中可能的值:cephopenstackfuelignore_errors。例:discover: openstack,ceph

为wally提供要发现的集群列表。群集发现用于查找群集节点及其角色,以简化设置配置和其他一些步骤。您始终可以在显式部分中定义或重新定义节点角色。每个群集都需要其他配置部分。ignore_errors意味着忽略丢失的集群。

  • run_sensors: bool

设置为true,允许wally在测试期间收集负载信息。 这大大增加了结果大小,但允许wally提供更复杂的报告。

  • results_storage: str

要放置结果的默认目录。 对于每个测试,wally将在此目录中生成唯一名称并创建子目录,所有结果和设置将存储其中。 wally必须有rwx权限来访问此目录。

例如: results_storage: /var/wally

  • sleep: int,默认为零

告诉wally在X秒内什么都不做。 如果您只需要收集sensors,则非常有用。

例如:sleep: 60

Section ceph

提供发现ceph集群节点的设置

  • root_node: str

必须。 根节点的ssh url。 这可以是任何具有ceph client key的节点(任何节点,您可以在其中运行ceph cli命令)。

  • cluster: str

Ceph集群名称。 默认情况下是ceph。

  • conf: str

群集配置文件的路径。默认情况下是/etc/ceph/{cluster_name}.conf。

  • key: str

client.admin密钥文件的路径。默认情况下是/etc/ceph/{cluster_name}.client.admin.keyring。

  • ip_remap: {IP: IP}

如果OSD和Monitor节点在 ceph 中通过内部 ip 地址注册,这在您运行wally的节点是不可见。这允许将non-routable的IP地址映射到可routable的IP地址。例如:

1
2
3
4
ip_remap:
10.8.0.4: 172.16.164.71
10.8.0.3: 172.16.164.72
10.8.0.2: 172.16.164.73

例如:

1
2
3
4
5
6
7
ceph:
root_node: ceph-client
cluster: ceph # << optional
ip_remap: # << optional
10.8.0.4: 172.16.164.71
10.8.0.3: 172.16.164.72
10.8.0.2: 172.16.164.73

Section openstack

提供openstack设置,用于发现OS群集和spawn/find测试vm。

  • skip_preparation: bool

默认值:true,wally需要准备openstack来生成虚拟机。 如果先前已准备好OS群集,则可以将此设置设置为false以节省一些检查时间。

  • openrc: 或 str ir {str: str}

指定源[openstack connection settings]。

openrc: ENV - 从环境变量中获取OS credentials。你需要在wally开始之前导出openrc设置,就像这样

1
2
$ source openrc
$ RUN_WALLY

1
$ env OS_USER=.. OS_PROJECT=..  RUN_WALLY

openrc: str - 使用openrc文件,位于提供的路径以获取OS connection settings。例如:openrc: /tmp/my_cluster_openrc

openrc: {str: str} - 直接在配置文件中提供connection settings。例如:

1
2
3
4
5
openrc:
OS_USERNAME: USER
OS_PASSWORD: PASSWD
OS_TENANT_NAME: KEY_FILE
OS_AUTH_URL: URL
  • insecure: bool - 在openrc section中提供,覆盖OS_INSECURE设置。

  • vms: [SSHURI] vm sshuri的列表,除了使用hostname/ip vm 名称前缀之外。wally将找到所有具有此前缀名称的vm,并将其用作测试节点。例如:

1
2
3
vms:
- wally@wally_vm
- root:rootpasswd@test_vm

这将找到所有名为wally_vm*和test_vm的vm,并尝试使用提供的credentials重用它们进行测试。请注意,默认情况下,vm wally使用openstack ssh key,而不是~/.ssh/id_rsa。有关详细信息,请参阅Openstack vm config部分。

  • VM spawning选项。此选项控制要为测试生成的新vm的数量以及要使用的配置文件。所有衍生的vm将自动获得testnode角色并将用于测试。wally尝试使用anti-affinity组在所有计算节点上均匀地生成vm。

count: str 或 int. 控制生成多少个vm,可能的值:

1
2
3
4
5
=X,其中X是int - 根据需要产生尽可能多的vm,以使总测试节点计数不小于X。例如 - 如果你已经有1个明确的测试节点,通过节点提供,并且在之前的测试运行中找到了2个vm并且你设置了count: =4,那么wally将产生一个额外的vm。

X,其中X是integer整数。 正好生成X新vm。

xX,其中X是integer整数。 每个compute产生X个vm。例如:copunt: x3 - 每个compute产生3个vm。

cfg_name: str,vm config。默认情况下,只有wally_1024配置可用。此配置使用来自https://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-amd64-disk1.img的image作为vm镜像,1GiB的RAM,2个vCPU和100GiB卷。 有关详细信息,请参阅Openstack vm config。

network_zone_name: str。 内部ip v4的Network pool。 通常是net04

flt_ip_pool: str。 用于浮动ip v4的Network pool。 通常是net04_ext

skip_preparation: bool,默认为false。默认情况下,在spawn vm之前,wally检查所有必需的先决条件,如vm flavor,image,aa-groups,ssh rules是否准备好并创建遗漏的东西。这告诉wally跳过这个阶段。如果你确定openstack准备好了,你可以设置它在这个阶段节省一些时间,但最好还是保留它以防止问题。

Section nodes

此section定义要执行的测试suites列表。 每个section都是从suite类型到suite配置的映射。 查看下面不同suites的详细信息。

fio suite config

  • load: str - 必须的选项,负载profile的名称。

默认下一个profiles可用:

ceph - 适用于各种ceph支持的块设备
hdd - 本地hdd驱动器
cinder_iscsi - cinder lvm-over-iscsi卷
check_distribution - 检查IOPS/latency的分布

有关详细信息,请参阅fio task files section。

  • params: {str: Any} - 负载profile的参数列表。子参数:

FILENAME: str,所有profiles都需要。它将用作fio的测试文件。如果测试文件名在不同的测试节点上不同,则需要在开始测试之前在所有测试节点上创建具有相同名称的(sym)链接,并在此处使用链接名称。

FILESIZE: SIZE,文件大小参数。虽然在大多数情况下wally可以正确检测device/file大小,但您不需要测试整个文件。此外,如果文件尚不存在,则需要此参数。

Non-standard负载可能需要一些其他参数,有关详细信息,请参阅fio task files section。

  • use_system_fio: bool,默认为false。告诉wally使用测试节点本地fio二进制文件,而不是wally附带的。如果wally没有为你发行指定版本的fio,你可能需要这个。默认情况下,最好使用wally的fio。有关详细信息,请参阅HOWTO/Supply fio for you distribution。

  • use_sudo: bool,默认为false。Wally将使用sudo在测试节点上运行fio。 通常,如果本地测试节点用户不是root用户,则需要。

  • force_prefill: bool,默认为false。在测试之前,告诉wally无条件地用伪随机数据填充测试file/device。默认情况下,首先检查目标是否已包含随机数据并跳过填充步骤。在这一步中,wally填充整个device,因此可能需要很长时间。

  • skip_prefill: bool,默认为false。强制wally不用伪随机数据填充目标。如果您正在测试本地hdd/ssd/cinder iscsi,请使用此选项。但是,如果测试ceph backed device或任何具有延迟空间分配的系统设备则不要使用。

例如:

1
2
3
4
5
- fio:
load: ceph
params:
FILENAME: /dev/vdb
FILESIZE: 100G

Key test_profile: str

此section允许使用一些预定义的设置集来生成VM和运行测试。config-examples/default.yaml文件中列出了可用的profiles及其设置。下一个profiles默认可用:

  • openstack_ceph - 每个compute产生1个VM,并针对/dev/vdb运行ceph fio profile
  • openstack_cinder - 每个compute产生1个VM,并针对/dev/vdb运行ceph_iscsi_vdb fio profile
  • openstack_nova - 每个compute产生1个VM,并针对/opt/test.bin运行hdd fio profile

例如:

1
2
3
4
5
6
7
8
9
10
11
12
include: default.yaml
discover: openstack,ceph
run_sensors: true
results_storage: /var/wally_results

ceph:
root_node: localhost

openstack:
openrc: ENV # take creds from environment variable

test_profile: openstack_ceph

Howto test

Local block device

使用config-examples/local_block_device.yml作为模板。将{STORAGE_FOLDER}替换为存放结果的文件夹的路径。确保wally具有对此文件夹的read/write访问权限,或者可以创建它。您可以直接测试device,也可以测试已mount device上的文件。将{STORAGE_DEV_OR_FILE_NAME}替换为正确的路径。在大多数情况下,wally可以正确检测file或block device大小,但通常最好直接设置{STORAGE_OR_FILE_SIZE}。使用的文件越大,对结果的影响越小,将导致不同的缓存,但也会填充更长的时间。

测试sdb device的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
include: default.yaml
run_sensors: false
results_storage: /var/wally

nodes:
localhost: testnode

tests:
- fio:
load: hdd
params:
FILENAME: /dev/sdb
FILESIZE: 100G

mount到/opt文件夹的device测试示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
include: default.yaml
run_sensors: false
results_storage: /var/wally

nodes:
localhost: testnode

tests:
- fio:
load: hdd
params:
FILENAME: /opt/some_test_file.bin
FILESIZE: 100G

请注意,测试完成后,wally不会删除文件。

Ceph without openstack, or other NAS/SAN

wally仅支持rbd/cephfs测试,不支持object协议,例如rados和RGW。Cephfs测试不需要任何特殊准备,除了将其mounte在测试节点上,详情请参阅ceph fs quick start。

Ceph线性read/write通常受网络限制。例如,如果您在群集中使用10个SATA drives作为storage drives,则聚合线性读取速度可达到~1Gibps或8Gibps,这接近10Gib网络限制。因此,除非你有一个足够带宽的网络测试节点,否则通常最好在多个测试节点并行测试ceph集群。

Ceph在低QD时通常性能较低,因为在这种模式下,您一次只能使用一个OSD。与此同时,ceph可以扩展到比hdd/ssd drives大得多的QD值,因为在这种情况下,您可以在所有OSD daemons中扩展IO请求。您需要最多(16 * OSD_count) QD用于4k随机读取和大约(12 * OSD_COUNT / REPLICATION_FACTOR)QD用于4k随机写入以摸清群集限制。对于其他blocks块和modes模式,您可能需要不同的设置。如果您使用默认的ceph配置文件,则无需关心此操作。

有三种测试RBD的方法 - direct,通过使用[krbd]和虚拟机将其mount到节点,由rbd driver提供的volume,内置到qemu。对于最后一个,参考Ceph with openstack section或文档。

Using testnode mounted rbd device

首先您需要一个pool作为rbd的target。您可以使用默认rbd pool,也可以创建自己的pool。pool需要有很多PG才能有很好的表现。保守估计是(100 * OSD_COUNT / REPLICATION_FACTOR)。创建后ceph可能会警告“too many PG”,这个消息可以安全地被忽略。ceph文档:PLACEMENT GROUPS。

  • 创建一个pool(有关详细信息,请参阅ceph pools documentation)
1
$ ceph osd pool create {pool-name} {pg-num} 
  • 等到创建完成,所有PG变为active+clean。

  • 在此pool中创建rbd volume,需要选择足够大的volume size以缓解不可避免的OSD节点FS caches。通常(SUM_RAM_SIZE_ON_ALL_OSD * 3)运行良好,并且在读取时仅产生约20%的缓存命中:

1
$ rbd create {vol-name} --size {size} --pool {pool-name}
  • 通过kernel rbd device挂载rbd。这是一个棘手的部分。Kernels通常具有旧版本的rbd driver,并且不支持最新的rbd features。这将导致在mount rbd期间出错。首先尝试挂载rbd device:
1
$ rbd map {vol-name} --pool {pool-name}

如果失败 - 您需要运行rbd info –pool {pool-name} {vol-name},并通过rbd feature disable –pool {pool-name} {vol-name} {feature name}禁用features。然后尝试再次mount。

  • wally需要对rbd device进行read/write访问。

Direct rbd testing

Direct rbd测试通过rbd driver运行,内置在fio中。使用此driver,fio可以直接生成RBD请求,无需外部rbd driver。这是测试RBD的最快和最可靠的方法,但是使用内部rbd driver您可以绕过一些可以在生产环境中使用的代码层。wally附带的fio版本没有rbd support,因为它不能被静态地构建。要使用它,您需要使用rbd support构建fio,请参阅Use you fio binary部分的学习指南。

Ceph with openstack

最简单的方法是使用预定义的openstack_ceph profile。它为每个计算节点生成一个VM,并在所有计算节点上运行ceph测试suite。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
include: default.yaml
discover: openstack,ceph
run_sensors: true
results_storage: /var/wally_results

ceph:
root_node: localhost

openstack:
openrc: ENV # take creds from environment variable

test_profile: openstack_ceph

Cinder lvm volumes

none

Howto

  • Use you fio binary

您需要下载fio源代码,在测试节点上编译它以进行linux distribution,使用bz2进行压缩,命名为fio_{DISTRNAME}_{ARCH}.bz2并放入fio_binaries目录。ARCH是目标系统上arch命令的输出。DISTRNAME应与lsb_release -c -s输出相同。

以下是从master编译最新文件的典型步骤:

1
2
3
4
5
6
$ git clone 
$ cd fio
$ ./configure --build-static
$ make -jXXX # Replace XXX with you CPU core count to decrease compilation time
$ bzip2 fio
$ mv fio.bz2 WALLY_FOLDER/fio_binaries/fio_DISTRO_ARCH.bz2

Storage structure

wally保存所有输入configurations,所有收集的数据和测试结果保存到results_storage设置目录的单个子文件夹下。所有文件都是csv(results/sensor files),yaml/js用于配置non-numeric信息,png/svg用于images和联结原始文本文件,如日志和一些输出。

以下是每个文件包含的内容:

  • cli - txt,wally cli in semi-raw formal

  • config.yaml - yaml,完整的最终config,从原始的wally config构建,处理所有替换和cli参数传递。

  • log - txt,wally执行日志。包含某个测试的所有wally运行log,包括restarts和报告生成。

  • result_code - yaml,此文件夹上带有’test’子命令的最后一次执行的退出代码。

  • run_interval - yaml,此文件夹上带有’test’子命令的最后一次执行的[begin_time, end_time]列表。

  • meta - folder,用于统计计算的cached值。

  • nodes - folder,有关测试集群的信息。

     all.yml - yaml,除节点参数外的所有节点的信息
    
     nodes/parameters.js - js,节点参数。参数是单独存储的,因为它们可能是非常多的ceph节点,并且在python中解析的js文件比yaml快得多。
    
  • report - folder,html/css文件报告和所有图表。可以复制到其他地方。

     index.html - 报告开始页面。
    
     main.css - css文件报告。
    
     XXX/YYY.png or .svg - 图表文件报告。
    
  • results - 所有fio结果的文件夹

     fio_{SUITE_NAME}_{IDX1}.yml - yaml,每个已执行suite的完整配置。
    
     fio_{SUITE_NAME}_{IDX1}.{JOB_SHORT_DESCR}_{IDX2} - 包含suite中每个job所有数据的文件夹
    
            {NODE_IP}:{NODE_SSH_PORT}.fio.{TS}.(csv|json) - fio输出文件。TS是解析时间序列(timeseries)名称 - bw或lat或stdout用于输出。
    

​ info.yml - 社区的原作者没有写完

靠继承和多态能完成的事情,在go语言中是通过接口来完成的。

duck typing

大黄鸭是鸭子吗?

  • 传统类型系统:脊索动物门,脊椎动物门,鸟纲雁形目… …
  • duck typing:是鸭子。

duck typing

  • 像鸭子走路,像鸭子叫(长得像鸭子),那么就是鸭子
  • 描述事物的外部行为而非内部结构
  • 严格说go属于结构化类型系统,类似duck typing

python中的duck typing

1
2
3
4
5
def download(retriever):
return retriever.get("www.imooc.com")

- 运行时才知道传入的retriever有没有get
- 需要注释来说明接口

c++中的duck typing

1
2
3
4
5
6
7
template <class R>
string download(const R& retriever) {
return retriever.get("www.imooc.com")
}

- 编译时才知道传入的retriever有没有get
- 需要注释来说明接口

java中的类似代码

1
2
3
4
5
6
7
<R extends Retriever>
String download(R r) {
return r.get("www.imooc.com")
}

- 传入的参数必须实现Retriever接口
- 不是duck typing

go语言的duck typing

1
2
3
同时需要Readable,Appendable怎么办?(java有个apache polygene)
同时具有python,c++的duck typing的灵活性
又具有java的类型检查

接口的定义

1
2
3
4
 使用者                实现者
download --> retriever

go语言的接口由使用者定义(和传统的面向对象思维不同,传统面向对象是实现者告诉大家我实现了某个接口,然后你们可以通过这个接口来用我)

demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
yujiangdeMacBook-Pro-13:GolangGrammar yujiang$ tree retrieve/
retrieve/
├── main.go
├── mock
│   └── mockretiever.go
└── real
└── retriever.go

2 directories, 3 files

文件main.go
package main
import (
"fmt"
"github.com/lnsyyj/GolangGrammar/retrieve/mock"
"github.com/lnsyyj/GolangGrammar/retrieve/real"
)
type Retriever interface {
Get(url string) string
}
func download(r Retriever) string {
return r.Get("http://www.imooc.com")
}
func main() {
var r Retriever
r = mock.Retriever{"This is a fack imooc.com"}
fmt.Println(download(r))
r = real.Retriever{}
fmt.Println(download(r))
}

文件retriever.go
package real
import (
"time"
"net/http"
"net/http/httputil"
)
type Retriever struct {
UserAgent string
TimeOut time.Duration
}
func (r Retriever) Get(url string) string {
resp, err := http.Get(url)
if err != nil {
panic(err)
}
result, err := httputil.DumpResponse(resp, true)
resp.Body.Close()
if err != nil {
panic(err)
}
return string(result)
}

文件mockretiever.go
package mock
type Retriever struct {
Contents string
}
func (r Retriever) Get(url string) string {
return r.Contents
}

===================================output===================================
This is a fack imooc.com
HTTP/1.1 200 OK
Accept-Ranges: bytes
Age: 9
Connection: keep-alive
Content-Type: text/html; charset=utf-8
Date: Wed, 10 Oct 2018 14:38:23 GMT
Server: nginx
Vary: Accept-Encoding
Via: 1.1 varnish (Varnish/6.0)
X-Cache: HIT from CS42
X-Varnish: 280162519 281222076
...
...body省略
...
===================================output===================================

看看interface里面有什么?
func main() {
var r Retriever
r = mock.Retriever{"This is a fack imooc.com"}
fmt.Printf("%T %v\n", r, r)
//fmt.Println(download(r))
r = real.Retriever{}
fmt.Printf("%T %v\n", r, r)
//fmt.Println(download(r))
}
===================================output===================================
mock.Retriever {This is a fack imooc.com}
real.Retriever { 0s}
===================================output===================================

  • go语言仅支持封装,不支持继承和多态

  • go语言没有class,只有struct

结构的创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main
import "fmt"
type treeNode struct {
value int
left, right *treeNode
}
func main() {
var tree treeNode
fmt.Println(tree)
}
===================================output===================================
{0 <nil> <nil>}
===================================output===================================

package main
import "fmt"
type treeNode struct {
value int
left, right *treeNode
}
func main() {
var root treeNode

root = treeNode{value: 3}
root.left = &treeNode{}
root.right = &treeNode{5, nil, nil}
root.right.left = new(treeNode)

nodes := []treeNode {
{value: 3},
{},
{6, nil, &root},
}

fmt.Println(nodes)
}
===================================output===================================
[{3 <nil> <nil>} {0 <nil> <nil>} {6 <nil> 0xc420098020}]
===================================output===================================
不论地址还是结构本身,一律使用.来访问成员

go语言没有构造函数,但是可以自己创建工厂函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main
import "fmt"
type treeNode struct {
value int
left, right *treeNode
}
func createNode(value int) *treeNode {
// 相当于在函数里建了一个局部变量,返回的地址是局部变量的地址,如果在c++中是典型的错误,在go语言中局部变量地址也是可以给别人用的
// 这个局部变量是创建在堆上还是栈上?go语言不需要知道,有垃圾回收器
return &treeNode{value: value}
}
func main() {
var root treeNode

root = treeNode{value: 3}
root.left = &treeNode{}
root.right = &treeNode{5, nil, nil}
root.right.left = new(treeNode)
root.left.right = createNode(2)

fmt.Println(root)
}
===================================output===================================
{3 0xc42000a080 0xc42000a0a0}
===================================output===================================

3
/ \
0 5
\ /
2 0

为结构定义方法

  • 显示定义,并为方法接收者命名
  • 只有使用指针才可以改变结构内容,否则是值传递(是拷贝)
  • nil指针也可以调用方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
package main
import (
"fmt"
)
type treeNode struct {
value int
left, right *treeNode
}
// 给结构定义方法,(node treeNode)就相当于c++的this指针,叫做接收者。就相当于print(函数名)是给node接收的
// node treeNode是传值的还是传引用的?当然是传值的
func (node treeNode) print() {
fmt.Printf("%d ", node.value)
}
// 与print其实是一样的
func print1(node treeNode) {
fmt.Println(node.value)
}
// node treeNode参数实际上是传值的
func (node treeNode) setValue(value int) {
node.value = value
}
// 传指针
func (node *treeNode) setValue1(value int) {
node.value = value
}
// nil
func (node *treeNode) setValue2(value int) {
if node == nil {
fmt.Println("Setting value to nil node. Ignored.")
return
}
node.value = value
}
// 树的中序遍历
func (node *treeNode) traverse() {
if node == nil {
return
}
node.left.traverse()
node.print()
node.right.traverse()
}
func createNode(value int) *treeNode {
// 相当于在函数里建了一个局部变量,返回的地址是局部变量的地址,如果在c++中是典型的错误,在go语言中局部变量地址也是可以给别人用的
return &treeNode{value: value}
}
func main() {
var root treeNode

root = treeNode{value: 3}
root.left = &treeNode{}
root.right = &treeNode{5, nil, nil}
root.right.left = new(treeNode)
root.left.right = createNode(2)

// 使用为结构定义的方法
root.print()
fmt.Println()
print1(root)
fmt.Println()
// 参数实际上是传值的,改不掉
root.right.left.setValue(4)
root.right.left.print()
fmt.Println()
// 传指针
root.right.left.setValue1(4)
root.right.left.print()
fmt.Println()

root.print()
root.setValue1(100)

pRoot := &root
pRoot.print()
pRoot.setValue1(200)
pRoot.print()
fmt.Println()

// nil
var pRoot2 *treeNode
pRoot2.setValue2(200)
pRoot2 = &root
pRoot2.setValue2(300)
pRoot2.print()
fmt.Println()

root.traverse()
}
===================================output===================================
3
3

0
4
3 100 200
Setting value to nil node. Ignored.
300
0 2 300 4 5
===================================output===================================

值接收者VS指针接收者

  • 要改变内容必须使用指针接收者
  • 结构过大也考虑使用指针接收者
  • 一致性:如果指针接收者,最好都是指针接收者
  • 值接受者是go语言特有
  • 值/指针接收者均可以接收 值/指针,并不会改变调用者如何调用

封装

  • 名字一般使用CamelCase
  • 首字母大写:public
  • 首字母小写:private

以上都是针对来说的

  • 每个目录一个包(包名不一定和目录名一样),每个目录只能放一个包
  • main包包含可执行入口(目录中如果有一个main函数,这个目录下只能有一个main包)
  • 为结构定义的方法必须放在同一个包内,可以是不同的文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
yujiangdeMBP-13:~ yujiang$ tree go/src/github.com/lnsyyj/GolangGrammar/tree/
go/src/github.com/lnsyyj/GolangGrammar/tree/
├── entry
│   └── entry.go
├── node.go
└── traversal.go

1 directory, 3 files



yujiangdeMBP-13:~ yujiang$ cat go/src/github.com/lnsyyj/GolangGrammar/tree/node.go
package tree
import (
"fmt"
)
type Node struct {
Value int
Left, Right *Node
}
// 给结构定义方法,(node Node)就相当于c++的this指针,叫做接收者。就相当于print(函数名)是给node接收的
// node Node是传值的还是传引用的?当然是传值的
func (node Node) Print() {
fmt.Printf("%d ", node.Value)
}
// 与print其实是一样的
func Print1(node Node) {
fmt.Println(node.Value)
}
// node Node参数实际上是传值的
func (node Node) SetValue(value int) {
node.Value = value
}
// 传指针
func (node *Node) SetValue1(value int) {
node.Value = value
}
// nil
func (node *Node) SetValue2(value int) {
if node == nil {
fmt.Println("Setting value to nil node. Ignored.")
return
}
node.Value = value
}
func CreateNode(value int) *Node {
// 相当于在函数里建了一个局部变量,返回的地址是局部变量的地址,如果在c++中是典型的错误,在go语言中局部变量地址也是可以给别人用的
return &Node{Value: value}
}



yujiangdeMBP-13:~ yujiang$ cat go/src/github.com/lnsyyj/GolangGrammar/tree/traversal.go
package tree
// 树的中序遍历
func (node *Node) Traverse() {
if node == nil {
return
}
node.Left.Traverse()
node.Print()
node.Right.Traverse()
}



yujiangdeMBP-13:~ yujiang$ cat go/src/github.com/lnsyyj/GolangGrammar/tree/entry/entry.go
package main
import (
"fmt"
"github.com/lnsyyj/GolangGrammar/tree"
)
func main() {
var root tree.Node
root = tree.Node{Value: 3}
root.Left = &tree.Node{}
root.Right = &tree.Node{5, nil, nil}
root.Right.Left = new(tree.Node)
root.Left.Right = tree.CreateNode(2)
// 使用为结构定义的方法
root.Print()
fmt.Println()
tree.Print1(root)
fmt.Println()
// 参数实际上是传值的,改不掉
root.Right.Left.SetValue(4)
root.Right.Left.Print()
fmt.Println()
// 传指针
root.Right.Left.SetValue1(4)
root.Right.Left.Print()
fmt.Println()
root.Print()
root.SetValue1(100)
pRoot := &root
pRoot.Print()
pRoot.SetValue1(200)
pRoot.Print()
fmt.Println()
// nil
var pRoot2 *tree.Node
pRoot2.SetValue2(200)
pRoot2 = &root
pRoot2.SetValue2(300)
pRoot2.Print()
fmt.Println()
root.Traverse()
}
===================================output===================================
3
3

0
4
3 100 200
Setting value to nil node. Ignored.
300
0 2 300 4 5
===================================output===================================

如何扩充系统类型或者别人的类型

  • 使用组合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
yujiangdeMBP-13:~ yujiang$ tree go/src/github.com/lnsyyj/GolangGrammar/tree/
go/src/github.com/lnsyyj/GolangGrammar/tree/
├── entry
│   └── entry.go
├── node.go
└── traversal.go

1 directory, 3 files

yujiangdeMBP-13:~ yujiang$ cat go/src/github.com/lnsyyj/GolangGrammar/tree/node.go
package tree
import (
"fmt"
)
type Node struct {
Value int
Left, Right *Node
}
// 给结构定义方法,(node Node)就相当于c++的this指针,叫做接收者。就相当于print(函数名)是给node接收的
// node Node是传值的还是传引用的?当然是传值的
func (node Node) Print() {
fmt.Printf("%d ", node.Value)
}
// 与print其实是一样的
func Print1(node Node) {
fmt.Println(node.Value)
}
// node Node参数实际上是传值的
func (node Node) SetValue(value int) {
node.Value = value
}
// 传指针
func (node *Node) SetValue1(value int) {
node.Value = value
}
// nil
func (node *Node) SetValue2(value int) {
if node == nil {
fmt.Println("Setting value to nil node. Ignored.")
return
}
node.Value = value
}
func CreateNode(value int) *Node {
// 相当于在函数里建了一个局部变量,返回的地址是局部变量的地址,如果在c++中是典型的错误,在go语言中局部变量地址也是可以给别人用的
return &Node{Value: value}
}



yujiangdeMBP-13:~ yujiang$ cat go/src/github.com/lnsyyj/GolangGrammar/tree/traversal.go
package tree
// 树的中序遍历
func (node *Node) Traverse() {
if node == nil {
return
}
node.Left.Traverse()
node.Print()
node.Right.Traverse()
}



yujiangdeMBP-13:~ yujiang$ cat go/src/github.com/lnsyyj/GolangGrammar/tree/entry/entry.go
package main
import (
"fmt"
"github.com/lnsyyj/GolangGrammar/tree"
)
type myTreeNode struct {
node *tree.Node
}
func (myNode *myTreeNode) postOrder() {
if myNode == nil || myNode.node == nil {
return
}
left := myTreeNode{myNode.node.Left}
right := myTreeNode{myNode.node.Right}
left.postOrder()
right.postOrder()
myNode.node.Print()
}
func main() {
var root tree.Node
root = tree.Node{Value: 3}
root.Left = &tree.Node{}
root.Right = &tree.Node{5, nil, nil}
root.Right.Left = new(tree.Node)
root.Left.Right = tree.CreateNode(2)
root.Right.Left.SetValue1(4)
root.Traverse()
fmt.Println()
myRoot := myTreeNode{&root}
myRoot.postOrder()
fmt.Println()
}
===================================output===================================
0 2 3 4 5
2 0 4 5 3
===================================output===================================

3
/ \
0 5
\ /
2 4
  • 定义别名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
yujiangdeMacBook-Pro-13:~ yujiang$ tree go/src/github.com/lnsyyj/GolangGrammar/queue/
go/src/github.com/lnsyyj/GolangGrammar/queue/
├── entry
│   └── main.go
└── queue.go

1 directory, 2 files

yujiangdeMacBook-Pro-13:~ yujiang$ cat go/src/github.com/lnsyyj/GolangGrammar/queue/queue.go
package queue
type Queue []int
func (q *Queue) Push(v int) {
*q = append(*q, v)
}
func (q *Queue) Pop() int {
head := (*q)[0]
*q = (*q)[1:]
return head
}
func (q *Queue) IsEmpty() bool {
return len(*q) == 0
}



yujiangdeMacBook-Pro-13:~ yujiang$ cat go/src/github.com/lnsyyj/GolangGrammar/queue/entry/main.go
package main
import (
"github.com/lnsyyj/GolangGrammar/queue"
"fmt"
)
func main() {
q := queue.Queue{1}
q.Push(2)
q.Push(3)
fmt.Println(q.Pop())
fmt.Println(q.Pop())
fmt.Println(q.IsEmpty())
fmt.Println(q.Pop())
fmt.Println(q.IsEmpty())
}
===================================output===================================
1
2
false
3
true
===================================output===================================

GOPATH环境变量

  • 默认在~/go(unix, linux),%USERPROFILE%\go(windows)
  • 官方推荐:所有项目和第三方库都放在同一个GOPATH下
  • 也可以将每个项目放在不同的GOPATH下(环境变量可以配置多个GOPATH,编译时会去不同的GOPATH找到依赖的包)
1
2
3
4
5
6
7
yujiangdeMacBook-Pro-13:~ yujiang$ echo $GOPATH
/Users/yujiang/go

yujiangdeMacBook-Pro-13:~ yujiang$ cat ~/.bash_profile
export GOPATH=/Users/yujiang/go
export GOBIN=/Users/yujiang/go/bin
export PATH=$PATH:$GOBIN
  • GoLand自动清理import工具
1
2
3
Preferences | Tools | File Watchers
yujiangdeMBP-13:~ yujiang$ go get golang.org/x/tools/cmd/goimports
被墙了,获取不下来

go get 获取第三方库

  • go get命令演示
  • 使用gopm来获取无法下载的包
1
2
3
4
5
6
7
8
yujiangdeMBP-13:~ yujiang$ go get -v github.com/gpmgo/gopm
yujiangdeMBP-13:~ yujiang$ gopm get -g -v golang.org/x/tools/cmd/goimports
yujiangdeMBP-13:~ yujiang$ ls go/src/golang.org/x/tools/imports/
fix.go fix_test.go imports.go mkindex.go mkstdlib.go sortimports.go zstdlib.go
# 编译出goimports
yujiangdeMBP-13:~ yujiang$ go build golang.org/x/tools/cmd/goimports
# 编译并安装到$GOPATH/bin/目录下
yujiangdeMBP-13:~ yujiang$ go install golang.org/x/tools/cmd/goimports
  • go build编译
  • go install产生pkg文件和可执行文件
  • go run直接编译运行

GOPATH下目录结构

1
2
3
4
5
6
7
8
9
10
src
git repository 1
git repository 2
pkg
git repository 1
git repository 2
bin
执行文件1,2,3

一般每个目录(包)下有一个main文件(package main func main() {})

最近做了一个实验,ceph代码如何build出rpm包?

官方文档:http://docs.ceph.com/docs/master/install/build-ceph/

步骤

1、首先需要ceph源码,官方的例子是把ceph源码压缩成tar.bz2

2、然后rpmbuild工具根据ceph源码中的ceph.spec规则进行build

详细实验步骤

方法一:

1
2
就是按照官方文档的思路去做
tar --strip-components=1 -C ~/rpmbuild/SPECS/ --no-anchored -xvjf ~/rpmbuild/SOURCES/ceph-10.2.11.tar.bz2 "ceph.spec"

方法二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
为了实验方便,我们会下载官方发布出来的.src.rpm包来实验。
.src.rpm会随着rpm同时发布,可以用来移植不同系统平台。

安装依赖
[root@yujiang-dev-20180912135521 ~]# git clone https://github.com/ceph/ceph.git
[root@yujiang-dev-20180912135521 ~]# cd ceph
[root@yujiang-dev-20180912135521 ceph]# ./install-deps.sh
[root@yujiang-dev-20180907154634 ~]# yum install rpm-build rpmdevtools hdparm libatomic_ops-devel fcgi-devel boost-devel cmake gcc-c++ tree selinux-policy-doc -y

下载官方发布出来的.src.rpm
[root@yujiang-dev-20180907154634 ~]# wget http://download.ceph.com/rpm-luminous/el7/SRPMS/ceph-10.2.11-0.el7.src.rpm

生成rpmbuild目录
[root@yujiang-dev-20180907154634 ~]# rpmdev-setuptree

[root@yujiang-dev-20180907154634 ~]# rpm -i ceph-10.2.11-0.el7.src.rpm
[root@yujiang-dev-20180912135521 ~]# tree rpmbuild/SRPMS/
rpmbuild/SRPMS/
└── ceph-10.2.11-0.el7.centos.src.rpm
[root@yujiang-dev-20180912135521 ~]# tree rpmbuild/SPECS/
rpmbuild/SPECS/
└── ceph.spec
开始构建rpm包
[root@yujiang-dev-20180912135521 ~]# rpmbuild -ba ~/rpmbuild/SPECS/ceph.spec

[root@yujiang-dev-20180912135521 ~]# tree rpmbuild/RPMS/
rpmbuild/RPMS/
└── x86_64
├── ceph-10.2.11-0.el7.centos.x86_64.rpm
├── ceph-base-10.2.11-0.el7.centos.x86_64.rpm
├── ceph-common-10.2.11-0.el7.centos.x86_64.rpm
├── ceph-debuginfo-10.2.11-0.el7.centos.x86_64.rpm
├── ceph-devel-compat-10.2.11-0.el7.centos.x86_64.rpm
├── cephfs-java-10.2.11-0.el7.centos.x86_64.rpm
├── ceph-fuse-10.2.11-0.el7.centos.x86_64.rpm
├── ceph-libs-compat-10.2.11-0.el7.centos.x86_64.rpm
├── ceph-mds-10.2.11-0.el7.centos.x86_64.rpm
├── ceph-mon-10.2.11-0.el7.centos.x86_64.rpm
├── ceph-osd-10.2.11-0.el7.centos.x86_64.rpm
├── ceph-radosgw-10.2.11-0.el7.centos.x86_64.rpm
├── ceph-resource-agents-10.2.11-0.el7.centos.x86_64.rpm
├── ceph-selinux-10.2.11-0.el7.centos.x86_64.rpm
├── ceph-test-10.2.11-0.el7.centos.x86_64.rpm
├── libcephfs1-10.2.11-0.el7.centos.x86_64.rpm
├── libcephfs1-devel-10.2.11-0.el7.centos.x86_64.rpm
├── libcephfs_jni1-10.2.11-0.el7.centos.x86_64.rpm
├── libcephfs_jni1-devel-10.2.11-0.el7.centos.x86_64.rpm
├── librados2-10.2.11-0.el7.centos.x86_64.rpm
├── librados2-devel-10.2.11-0.el7.centos.x86_64.rpm
├── libradosstriper1-10.2.11-0.el7.centos.x86_64.rpm
├── libradosstriper1-devel-10.2.11-0.el7.centos.x86_64.rpm
├── librbd1-10.2.11-0.el7.centos.x86_64.rpm
├── librbd1-devel-10.2.11-0.el7.centos.x86_64.rpm
├── librgw2-10.2.11-0.el7.centos.x86_64.rpm
├── librgw2-devel-10.2.11-0.el7.centos.x86_64.rpm
├── python-ceph-compat-10.2.11-0.el7.centos.x86_64.rpm
├── python-cephfs-10.2.11-0.el7.centos.x86_64.rpm
├── python-rados-10.2.11-0.el7.centos.x86_64.rpm
├── python-rbd-10.2.11-0.el7.centos.x86_64.rpm
├── rbd-fuse-10.2.11-0.el7.centos.x86_64.rpm
├── rbd-mirror-10.2.11-0.el7.centos.x86_64.rpm
└── rbd-nbd-10.2.11-0.el7.centos.x86_64.rpm

1 directory, 34 files

解压bz2文件
yum install bzip2 -y
bzip2 -d ceph-12.2.8.tar.bz2
tar xvf ceph-12.2.8.tar
tar zcvf ceph-12.2.8.tar.gz ceph-12.2.8

如果是ceph-14.2.4需要安装
yum -y install centos-release-scl
yum -y install devtoolset-7-gcc devtoolset-7-gcc-c++ devtoolset-7-binutils
scl enable devtoolset-7 bash
yum install ceph-2:14.2.4-0.el7.x86_64
rpmbuild -ba ~/rpmbuild/SPECS/ceph.spec

解包:tar xvf FileName.tar
打包:tar cvf FileName.tar DirName

压缩:bzip2 [原文件名].tar
解压:bunzip2 [原文件名].tar.bz2

课程地址:https://coding.imooc.com/class/chapter/180.html

变量的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package main
import "fmt"
//函数外定义变量不能用:=,作用域只在包package内部
var aa = 3
var ss = "string"
//或者放在括号内
var (
bb = 4
cc = 5
)
//不初始化变量值
func variableZeroValue(){
var i int
var s string
fmt.Printf("%d %q\n", i, s)
}
//初始化变量值
func variableInitialValue(){
var a, b int = 3, 4
var s string = "Hello"
fmt.Println(a, b, s)
}
//初始化多个不同类型变量
func variableTypeDeduction(){
var a, b, c, s = 3, 4, true, "World"
fmt.Println(a, b, c, s)
}
//:=,省略掉var
func variableShorter(){
a, b, c, s := 3, 4, true, "World"
b = 5
fmt.Println(a, b, c, s)
}

func main() {
fmt.Println("Hello World")
variableZeroValue()
variableInitialValue()
variableTypeDeduction()
variableShorter()
fmt.Println(aa, ss, bb, cc)
}

===================================output===================================
Hello World
0 ""
3 4 Hello
3 4 true World
3 5 true World
3 string 4 5

内建变量类型

  • bool, string

  • (u)int, (u)int8, (u)int16, (u)int32, (u)int64, uintptr

  • byte, rune(这是字符型,golang中不叫char,是32位的)

  • float32, float64, complex64, complex128

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main
import (
"fmt"
"math/cmplx"
"math"
)

func euler(){
fmt.Printf("%.3f\n", cmplx.Exp(1i*math.Pi) + 1)
}

func main() {
euler()
}

===================================output===================================
(0.000+0.000i)

强制类型转换

  • 类型转换是强制的(golang只有强制类型转换,没有隐式类型转换)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import (
"fmt"
"math"
)
func triangle(){
var a, b int = 3, 4
var c int
c = int(math.Sqrt(float64(a * a + b * b)))
fmt.Println(c)
}

func main() {
triangle()
}

===================================output===================================
5

常量的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main
import (
"math"
"fmt"
)
const filename = "abc.txt"
func consts(){
//不指定类型就是一个文本
//如果指定类型,某些地方就需要强制类型转换
//const a, b int = 3, 4
//c = int(math.Sqrt(float64(a * a + b * b)))
const a, b = 3, 4
const (
name = "hello world"
d, e= 5,6
)
var c int
c = int(math.Sqrt(a * a + b * b))
fmt.Println(filename, c, name, d, e)
}

func main() {
consts()
}

===================================output===================================
abc.txt 5 hello world 5 6

使用常量定义枚举类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package main
import "fmt"
func enums(){
const (
cpp = 0
java = 1
python = 2
golang = 3
)
fmt.Println(cpp, java, python, golang)
}
func main() {
enums()
}
===================================output===================================
0 1 2 3
===================================output===================================
package main
import "fmt"
func enums(){
const (
cpp = iota
java
python
golang
)
fmt.Println(cpp, java, python, golang)
}
func main() {
enums()
}
===================================output===================================
0 1 2 3
===================================output===================================
package main
import "fmt"
func enums(){
const (
cpp = iota
_
python
golang
javascript
)
fmt.Println(cpp, javascript, python, golang)
}
func main() {
enums()
}
===================================output===================================
0 4 2 3
===================================output===================================

package main
import "fmt"
func enums(){
const (
b = 1 << (10 * iota)
kb
mb
gb
tb
pb
)
fmt.Println(b, kb, mb, gb, tb, pb)
}
func main() {
enums()
}
===================================output===================================
1 1024 1048576 1073741824 1099511627776 1125899906842624
===================================output===================================

条件语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package main
import (
"io/ioutil"
"fmt"
)
func main() {
const filename = "abc.txt"
contents, err := ioutil.ReadFile(filename)
if err != nil {
fmt.Println(err)
}else {
fmt.Printf("%s\n", contents)
}
}
===================================output===================================
open abc.txt: no such file or directory
===================================output===================================
hello world
===================================output===================================
package main
import (
"io/ioutil"
"fmt"
)
func main() {
const filename = "abc.txt"
//条件里赋值的变量作用域就在这个if语句里
if contents, err := ioutil.ReadFile(filename); err != nil {
fmt.Println(err)
}else {
fmt.Printf("%s\n", contents)
}
}
===================================output===================================
hello world
===================================output===================================
package main
import (
"fmt"
)
func grade(score int) string {
g := ""
//不带表达式也可以进行switch
switch {
case score < 0 || score > 100:
panic(fmt.Sprintf("Wrong score: %d", score))
case score < 60:
g = "F"
case score < 80:
g = "C"
case score < 90:
g = "B"
case score <=100:
g = "A"
}
return g
}
func main() {
fmt.Println(
grade(0),
grade(59),
grade(60),
grade(82),
grade(99),
grade(100),
)
}
===================================output===================================
F F C B A A
===================================output===================================

循环

golang中只有for,没有while

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package main
import (
"strconv"
"fmt"
)
func convertToBin(n int) string {
result := ""
for ; n > 0; n /= 2 {
lsb := n % 2
result = strconv.Itoa(lsb) + result
}
return result
}
func main() {
fmt.Println(
convertToBin(5),
convertToBin(13),
convertToBin(789),
convertToBin(0),
)
}
===================================output===================================
101 1101 1100010101
===================================output===================================

package main
import (
"fmt"
"os"
"bufio"
)
func readFile(filename string) {
if file, err := os.Open(filename); err != nil {
panic(err)
}else {
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}
}
func main() {
readFile("/Users/yujiang/go/src/github.com/lnsyyj/GolangGrammar/abc.txt")
}
===================================output===================================
hello world
yes
===================================output===================================
//不写条件,死循环
for {
fmt.Println("abc")
}

函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main
import "fmt"
//函数可以返回多个值
func eval(a, b int ,op string) (int, error) {
switch op {
case "+":
return a + b, nil
case "-":
return a - b, nil
case "*":
return a * b, nil
case "/":
q , _ := div(a, b)
return q, nil
default:
return 0, fmt.Errorf("unsupported operator: %s", op)
}
}
//返回值可以起名
func div(a, b int) (q, r int){
return a / b, a % b
}
func main() {
if result, err := eval(3, 4, ";"); err != nil {
fmt.Println(err)
}else {
fmt.Println(result)
}
q, r := div(13, 3)
fmt.Println(q, r)
}
===================================output===================================
unsupported operator: ;
4 1
===================================output===================================

函数式编程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main
import (
"fmt"
"reflect"
"runtime"
"math"
)
func apply(op func(int, int) int, a, b int) int {
//拿到函数名
p := reflect.ValueOf(op).Pointer()
opName := runtime.FuncForPC(p).Name()
fmt.Printf("Calling function %s with args " + "(%d, %d)\n", opName, a, b)
return op(a, b)
}
func pow(a, b int) int {
return int(math.Pow(float64(a), float64(b)))
}
func main() {
fmt.Println(apply(pow, 3, 4))
fmt.Println(apply(
func(a int, b int) int {
return int(math.Pow(float64(a), float64(b)))
}, 3, 4))
}
===================================output===================================
Calling function main.pow with args (3, 4)
81
Calling function main.main.func1 with args (3, 4)
81
===================================output===================================

可变参数列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main
import (
"fmt"
)
func sum(numbers ...int) int {
s := 0
for i := range numbers {
s += numbers[i]
}
return s
}
func main() {
fmt.Println(sum(1, 2, 3, 4, 5))
}
===================================output===================================
15
===================================output===================================

指针

golang指针不能运算,go语言只有值传递一种方式。没有引用传递。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main
import "fmt"
func swap1(a, b int) {
a, b = b, a
}
//指针
func swap2(a ,b *int) {
*a, *b = *b, *a
}
func swap3(a, b int) (int, int) {
return b, a
}
func main() {
a, b := 3, 4
swap1(a, b)
fmt.Println(a, b)
swap2(&a, &b)
fmt.Println(a, b)
a, b = swap3(a, b)
fmt.Println(a, b)
}
===================================output===================================
3 4
4 3
3 4
===================================output===================================

数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package main
import "fmt"
func main() {
//数组的定义,数量写在类型的前面
var arr1 [5]int
arr2 := [3]int{1,3,5}
//数组必须用...,否则是切片
arr3 := [...]int{2,4,6,8,10}
//二维数组
var grid [4][5]int
fmt.Println(arr1, arr2, arr3)
fmt.Println(grid)

//遍历数组
for i, v := range arr3 {
fmt.Println(i, v)
}
fmt.Println("++++++++++")
//可通过 _ 省略变量。不仅range,任何地方都可以通过 _ 省略变量
for _, v := range arr3 {
fmt.Println(v)
}
}
===================================output===================================
[0 0 0 0 0] [1 3 5] [2 4 6 8 10]
[[0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]]
0 2
1 4
2 6
3 8
4 10
++++++++++
2
4
6
8
10
===================================output===================================

数组是值类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package main
import "fmt"
func printArray(arr [5]int) {
arr[0] = 100
for i, v := range arr {
fmt.Println(i, v)
}
}
func main() {
//数组的定义,数量写在类型的前面
var arr1 [5]int
//数组必须用...,否则是切片
arr3 := [...]int{2,4,6,8,10}
printArray(arr1)
fmt.Println("+++++++++++++")
printArray(arr3)
fmt.Println("+++++++++++++")
fmt.Println(arr1, arr3)
}
===================================output===================================
0 100
1 0
2 0
3 0
4 0
+++++++++++++
0 100
1 4
2 6
3 8
4 10
+++++++++++++
[0 0 0 0 0] [2 4 6 8 10]
===================================output===================================
//[10]int和[20]int是不同类型,调用func f(arr [10]int)会拷贝数组

package main
import "fmt"
func printArray(arr *[5]int) {
arr[0] = 100
for i, v := range arr {
fmt.Println(i, v)
}
}
func main() {
//数组的定义,数量写在类型的前面
var arr1 [5]int
//数组必须用...,否则是切片
arr3 := [...]int{2,4,6,8,10}
printArray(&arr1)
fmt.Println("+++++++++++++")
printArray(&arr3)
fmt.Println("+++++++++++++")
fmt.Println(arr1, arr3)
}
===================================output===================================
0 100
1 0
2 0
3 0
4 0
+++++++++++++
0 100
1 4
2 6
3 8
4 10
+++++++++++++
[100 0 0 0 0] [100 4 6 8 10]
===================================output===================================
//go语言一般不直接使用数组

Slice(切片)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
package main
import "fmt"
func main() {
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
fmt.Println("arr[2:6] = ", arr[2:6])
fmt.Println("arr[:6] = ", arr[:6])
fmt.Println("arr[2:] = ", arr[2:])
fmt.Println("arr[:] = ", arr[:])
}
===================================output===================================
arr[2:6] = [2 3 4 5]
arr[:6] = [0 1 2 3 4 5]
arr[2:] = [2 3 4 5 6 7]
arr[:] = [0 1 2 3 4 5 6 7]
===================================output===================================
//文档中说slice是array的view

package main
import "fmt"
func updateSlice(s []int) {
s[0] = 100
}
func main() {
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
fmt.Println("arr[2:6] = ", arr[2:6])
fmt.Println("arr[:6] = ", arr[:6])
s1 := arr[2:]
fmt.Println("s1 = ", s1)
s2 := arr[:]
fmt.Println("s2 = ", s2)

fmt.Println("After updateSlice(s1)")
updateSlice(s1)
fmt.Println(s1)
fmt.Println(arr)

fmt.Println("After updateSlice(s2)")
updateSlice(s2)
fmt.Println(s2)
fmt.Println(arr)

fmt.Println("Reslice")
fmt.Println(s2)
s2 = s2[:5]
fmt.Println(s2)
s2 = s2[2:]
fmt.Println(s2)
}
===================================output===================================
arr[2:6] = [2 3 4 5]
arr[:6] = [0 1 2 3 4 5]
s1 = [2 3 4 5 6 7]
s2 = [0 1 2 3 4 5 6 7]
After updateSlice(s1)
[100 3 4 5 6 7]
[0 1 100 3 4 5 6 7]
After updateSlice(s2)
[100 1 100 3 4 5 6 7]
[100 1 100 3 4 5 6 7]
Reslice
[100 1 100 3 4 5 6 7]
[100 1 100 3 4]
[100 3 4]
===================================output===================================

//数组版,slice
package main
import "fmt"
func printArray(arr []int) {
arr[0] = 100
for i, v := range arr {
fmt.Println(i, v)
}
}
func main() {
//数组的定义,数量写在类型的前面
var arr1 [5]int
//数组必须用...,否则是切片
arr3 := [...]int{2,4,6,8,10}
//**********使用arr1[:]获得数组的切片**********
printArray(arr1[:])
fmt.Println("+++++++++++++")
printArray(arr3[:])
fmt.Println("+++++++++++++")
fmt.Println(arr1, arr3)
}
===================================output===================================
0 100
1 0
2 0
3 0
4 0
+++++++++++++
0 100
1 4
2 6
3 8
4 10
+++++++++++++
[100 0 0 0 0] [100 4 6 8 10]
===================================output===================================
//slice本身没有数据,是对底层array的一个view
//arr的值变为[0 1 10 3 4 5 6 7]

Slice的扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package main
import "fmt"
func main() {
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}

fmt.Println("Extending slice")
arr[0], arr[2] = 0, 2
s1 := arr[2:6]
s2 := s1[3:5]
fmt.Println("s1=", s1)
fmt.Println("s2=", s2)
}
===================================output===================================
Extending slice
s1= [2 3 4 5]
s2= [5 6]
===================================output===================================
//6不在s1中,slice是怎么取到的?slice是数组的view
//s2:=arr[3:5] [0 1 2] s2下标,下标2在s2中是看不到的
//s1:=arr[2:6] [0 1 2 3 4 5] s1下标,下标4、5在s1中是看不到的,s1[4]会报错
//arr [0 1 2 3 4 5 6 7] 数组中数值
//slice中有ptr、len、cap,只能取值到len的值,越界会报错
//slice可以向后扩展,不可以向前扩展
//s[i]不可以超越len(s),向后扩展不可以超越底层数组cap(s)

package main
import "fmt"
func main() {
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
fmt.Println("Extending slice")
arr[0], arr[2] = 0, 2
fmt.Println("arr =", arr)
s1 := arr[2:6]
s2 := s1[3:5]
fmt.Printf("s1=%v, len(s1)=%d, cap(s1)=%d\n", s1, len(s1), cap(s1))
fmt.Printf("s2=%v, len(s2)=%d, cap(s2)=%d\n", s2, len(s2), cap(s2))
//我的理解是下标虽然取不到,但是可以扩展
fmt.Println(s1[3:6])
}
===================================output===================================
Extending slice
arr = [0 1 2 3 4 5 6 7]
s1=[2 3 4 5], len(s1)=4, cap(s1)=6
s2=[5 6], len(s2)=2, cap(s2)=3
[5 6 7]
===================================output===================================

向Slice添加元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main
import "fmt"
func main() {
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
fmt.Println("Extending slice")
arr[0], arr[2] = 0, 2
fmt.Println("arr =", arr)
s1 := arr[2:6]
s2 := s1[3:5]
fmt.Printf("s1=%v, len(s1)=%d, cap(s1)=%d\n", s1, len(s1), cap(s1))
fmt.Printf("s2=%v, len(s2)=%d, cap(s2)=%d\n", s2, len(s2), cap(s2))
fmt.Println(s1[3:6])

s3 := append(s2, 10)
s4 := append(s3, 11) //append超出arr的下标范围就不是原来的arr了,是一个新的array,新的array会设置的长一些
s5 := append(s4, 12) //go语言会开辟一个新的array,把arr中的元素拷贝过去
fmt.Println("s3, s4, s5 =", s3, s4, s5)
// s4 and s5 no longer view arr.
fmt.Println("arr =", arr)
}
===================================output===================================
Extending slice
arr = [0 1 2 3 4 5 6 7]
s1=[2 3 4 5], len(s1)=4, cap(s1)=6
s2=[5 6], len(s2)=2, cap(s2)=3
[5 6 7]
s3, s4, s5 = [5 6 10] [5 6 10 11] [5 6 10 11 12]
arr = [0 1 2 3 4 5 6 10]
===================================output===================================
//添加元素时如果超越cap,系统会重新分配更大的底层数组
//由于值传递的关系,必须接收append的返回值
//s = append(s, val)

Slice的创建,拷贝,删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package main
import "fmt"
func printSlice(s []int) {
fmt.Printf("%v, len=%d, cap=%d\n", s, len(s), cap(s))
}
func main() {
fmt.Println("Creating slice")
var s []int //Zero value for slice is nil
for i := 0; i < 10 ; i++ {
printSlice(s)
s = append(s, 2 * i + 1)
}
fmt.Println(s)
s1 := []int{2, 4, 6, 8}
printSlice(s1)
s2 := make([]int, 16)
printSlice(s2)
s3 := make([]int, 10, 32)
printSlice(s3)

fmt.Println("Copying slice")
copy(s2, s1)
printSlice(s2)

fmt.Println("Deleting elements from slice")
s2 = append(s2[:3], s2[4:]...)
printSlice(s2)

fmt.Println("Popping from front")
front := s2[0]
s2 = s2[1:]
fmt.Println(front)
printSlice(s2)

fmt.Println("Popping from back")
tail := s2[len(s2) - 1]
s2 = s2[:len(s2) - 1]
fmt.Println(tail)
printSlice(s2)
}
===================================output===================================
Creating slice
[], len=0, cap=0
[1], len=1, cap=1
[1 3], len=2, cap=2
[1 3 5], len=3, cap=4
[1 3 5 7], len=4, cap=4
[1 3 5 7 9], len=5, cap=8
[1 3 5 7 9 11], len=6, cap=8
[1 3 5 7 9 11 13], len=7, cap=8
[1 3 5 7 9 11 13 15], len=8, cap=8
[1 3 5 7 9 11 13 15 17], len=9, cap=16
[1 3 5 7 9 11 13 15 17 19]
[2 4 6 8], len=4, cap=4
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0], len=16, cap=16
[0 0 0 0 0 0 0 0 0 0], len=10, cap=32
Copying slice
[2 4 6 8 0 0 0 0 0 0 0 0 0 0 0 0], len=16, cap=16
Deleting elements from slice
[2 4 6 0 0 0 0 0 0 0 0 0 0 0 0], len=15, cap=16
Popping from front
2
[4 6 0 0 0 0 0 0 0 0 0 0 0 0], len=14, cap=15
Popping from back
0
[4 6 0 0 0 0 0 0 0 0 0 0 0], len=13, cap=15
===================================output===================================

Map

定义:map[key]value map[key_1]map[key_2]value

  • 创建:make(map[string]int)
  • 获取元素:m[key]
  • key不存在时,获得value类型的初始值
  • 用value, ok := m[key]来判断是否存在key
  • 用delete删除一个key
  • 使用range遍历key,或者遍历key, value对
  • 不保证遍历顺序,如需顺序,需手动对key排序
  • 使用len获取元素个数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package main
import "fmt"
func main() {
// map的定义
m := map[string]string {
"name": "ccmouse",
"course": "golang",
"site": "imooc",
"quality": "notbad",
}
m2 := make(map[string]int) // m2 == empty map
var m3 map[string]int // m3 == nil
fmt.Println(m, m2, m3)
// map的遍历
fmt.Println("Traversing map")
for k, v := range m {
fmt.Println(k, v)
}
// 获取value
fmt.Println("Getting values")
courseName, ok := m["course"]
fmt.Println(courseName, ok)
// key拼错了怎么办?会打印空串
causeName, ok := m["cause"]
fmt.Println(causeName, ok)
if causeName, ok := m["cause"]; ok {
fmt.Println(causeName)
}else {
fmt.Println("key does not exist")
}
// 删除
fmt.Println("Deleting values")
name, ok := m["name"]
fmt.Println(name, ok)
delete(m, "name")
name, ok = m["name"]
fmt.Println(name, ok)
}
===================================output===================================
map[name:ccmouse course:golang site:imooc quality:notbad] map[] map[]
Traversing map
site imooc
quality notbad
name ccmouse
course golang
Getting values
golang true
false
key does not exist
Deleting values
ccmouse true
false
===================================output===================================

map的key

  • map使用哈希表,必须可以比较相等
  • 除了slice,map,function的内建类型都可以作为key
  • struct类型不包含上述字段,也可作为key
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
例:寻找最长不含有重复字符的子串
abcabcbb -> abc
bbbbb -> b
pwwkew -> wke
思路:
对于每一个字母x
lastOccurred[x]不存在,或者 < start -----> 无需操作
lastOccurred[x] >= start -----> 更新start
更新lastOccurred[x],更新maxLength

package main
import "fmt"
func lengthOfNonRepeatingSubStr(s string) int {
lastOccurred := make(map[byte]int)
start := 0
maxLength := 0
for i, ch := range []byte(s) {
if lastI, ok := lastOccurred[ch]; ok && lastI >= start {
//if lastOccurred[ch] >= start {
start = lastI + i
}
if i - start + 1 > maxLength {
maxLength = i - start + 1
}
lastOccurred[ch] = i
}
return maxLength
}
func main() {
fmt.Println(lengthOfNonRepeatingSubStr("abcabcbb"))
fmt.Println(lengthOfNonRepeatingSubStr("bbbbb"))
fmt.Println(lengthOfNonRepeatingSubStr("pwwkew"))
fmt.Println(lengthOfNonRepeatingSubStr(""))
fmt.Println(lengthOfNonRepeatingSubStr("b"))
fmt.Println(lengthOfNonRepeatingSubStr("abcdef"))
}
===================================output===================================
3
1
3
0
1
6
===================================output===================================

rune相当于go的char

  • 使用range遍历pos,rune对
  • 使用utf8.RuneCountInString获得字符数量
  • 使用len获得字节长度
  • 使用[]byte获得字节
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
s := "Yes我爱慕课网!" //UTF-8
fmt.Println(s)
for _, b := range []byte(s) {
fmt.Printf("%X ", b)
}
fmt.Println()
for i, ch := range s { // ch is a rune
fmt.Printf("(%d %X) ", i ,ch)
}
fmt.Println()
fmt.Println("Rune count:", utf8.RuneCountInString(s))
bytes := []byte(s)
for len(bytes) > 0 {
ch, size := utf8.DecodeRune(bytes)
bytes = bytes[size:]
fmt.Printf("%c ", ch)
}
fmt.Println()
for i, ch := range []rune(s) {
fmt.Printf("(%d %c) ", i, ch)
}
fmt.Println()
}
===================================output===================================
Yes我爱慕课网!
59 65 73 E6 88 91 E7 88 B1 E6 85 95 E8 AF BE E7 BD 91 21
(0 59) (1 65) (2 73) (3 6211) (6 7231) (9 6155) (12 8BFE) (15 7F51) (18 21)
Rune count: 9
Y e s 我 爱 慕 课 网 !
(0 Y) (1 e) (2 s) (3 我) (4 爱) (5 慕) (6 课) (7 网) (8 !)
===================================output===================================

package main
import (
"fmt"
)
func lengthOfNonRepeatingSubStr(s string) int {
lastOccurred := make(map[rune]int)
for k, v := range lastOccurred {
fmt.Printf("%k=%v, v=%v ", k, v)
}
start := 0
maxLength := 0
for i, ch := range []rune(s) {
lastI, ok := lastOccurred[ch]
if ok && lastI >= start {
//if lastOccurred[ch] >= start {
start = lastI + i
}
if i - start + 1 > maxLength {
maxLength = i - start + 1
}
lastOccurred[ch] = i
}
return maxLength
}
func main() {
fmt.Println(lengthOfNonRepeatingSubStr("abcabcbb"))
fmt.Println(lengthOfNonRepeatingSubStr("bbbbb"))
fmt.Println(lengthOfNonRepeatingSubStr("pwwkew"))
fmt.Println(lengthOfNonRepeatingSubStr(""))
fmt.Println(lengthOfNonRepeatingSubStr("b"))
fmt.Println(lengthOfNonRepeatingSubStr("abcdef"))
fmt.Println(lengthOfNonRepeatingSubStr("一二三二一"))
fmt.Println(lengthOfNonRepeatingSubStr("一二三四五六"))
}
===================================output===================================
3
1
3
0
1
6
3
6
===================================output===================================

其他字符串操作

  • Fields,Split,Join
  • Contains,Index
  • ToLower,ToUpper
  • Trim,TrimRight,TrimLeft

近期工作是调研teuthology,目标是把teuthology enable起来做ceph的测试使用,但没想到teuthology只是冰山一角,其关联了ceph社区的整套CI/CD,这套CI/CD关联项目多的力不从心,其关联的项目大致如下:

1
2
3
4
5
6
7
8
https://github.com/ceph/shaman.git
https://github.com/ceph/teuthology.git
https://github.com/ceph/ceph-cm-ansible.git
https://github.com/ceph/pulpito.git
https://github.com/ceph/ceph-qa-suite.git
https://github.com/ceph/paddles.git
https://github.com/ceph/ceph.git
还有很多未知的,shaman社区明确说是内部使用的,不提供文档,所以只能撸代码,从代码中找数据库的表结构,到目前还没时间找出来。https://github.com/ceph/shaman/issues/110

既然社区使用ansible,并且Sébastien Han一直维护,毕竟有他的道理,ansible在自动化运维领域也是很常用的(尤其在大规模部署的情况下),倒不如借着这个机会学习一下。

ansible基础语法看以下两个文章差不多够用,再有问题可以查ansible官网。

1
2
https://www.w3cschool.cn/automate_with_ansible/
https://www.cnblogs.com/f-ck-need-u/p/7567417.html

接着来看一下ceph-ansible项目,README中给出了文档,一个是master一个是stable-3.0。我没有那么傻,当然是看stable-3.0了,哈哈。

1
https://github.com/ceph/ceph-ansible

首先是把ceph-ansible的playbooks clone下来。

1
2
3
4
5
[root@k8s-master ~]# yum install git -y
[root@k8s-master ~]# git clone https://github.com/ceph/ceph-ansible.git
[root@k8s-master ~]# cd ceph-ansible/
# 当然branch也要对应文档,这样后续困难应该少一点
[root@k8s-master ceph-ansible]# git checkout -b mystable-3.0 origin/stable-3.0

接着需要安装ansible,官方stable-3.0文档中说明了ceph-ansible项目各个branch,支持ansible版本和ceph版本。我们只看现在的branch,其他branch有需要再说。

1
stable-3.0支持ceph版本jewel和luminous。该branch支持ansible版本2.3.1,2.3.2和2.4.2。

安装ansible 2.4.2

使用yum安装指定版本的ansible

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[root@cephJ ~]# yum -h | grep show
--showduplicates 在 list/search 命令下,显示源里重复的条目

[root@cephJ ~]# yum --showduplicates list ansible
已加载插件:fastestmirror, priorities
Loading mirror speeds from cached hostfile
* base: mirrors.cn99.com
* epel: www.ftp.ne.jp
* extras: mirrors.nwsuaf.edu.cn
* nux-dextop: li.nux.ro
* updates: mirrors.nwsuaf.edu.cn
12 packages excluded due to repository priority protections
可安装的软件包
ansible.noarch 2.4.2.0-2.el7 extras
ansible.noarch 2.7.5-1.el7 epel

### sudo yum install <package name>-<version info>
[root@cephJ ~]# sudo yum install -y ansible-2.4.2.0

[root@cephJ ~]# ansible --version
ansible 2.4.2.0
config file = /etc/ansible/ansible.cfg
configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python2.7/site-packages/ansible
executable location = /usr/bin/ansible
python version = 2.7.5 (default, Apr 11 2018, 07:36:10) [GCC 4.8.5 20150623 (Red Hat 4.8.5-28)]

配置ceph-ansible

准备四台机器

1
2
3
4
5
6
7
ansible节点
ansible-master

ceph节点
ansible-ceph-1
ansible-ceph-2
ansible-ceph-3

配置ansible

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
ansible节点执行

1、首先clone代码
[root@ansible-master ~]# git clone https://github.com/ceph/ceph-ansible.git && cd ceph-ansible/

2、创建本地分支并切换分支
[root@ansible-master ceph-ansible]# git checkout -b myv3.2.0 v3.2.0

3、copy模板文件
[root@ansible-master ceph-ansible]# cp site.yml.sample site.yml

4、修改ansible机器清单(inventory)
[root@ansible-master ~]# vim /etc/ansible/hosts
[mons]
ansible-ceph-[1:3] ansible_ssh_pass=yujiang2
[osds]
ansible-ceph-[1:3] ansible_ssh_pass=yujiang2
[rgws]
ansible-ceph-[1:3] ansible_ssh_pass=yujiang2
[mgrs]
ansible-ceph-[1:3] ansible_ssh_pass=yujiang2

5、批量推送sshkey
[root@ansible-master ~]# ssh-keygen -t rsa
[root@ansible-master ~]# cat push-ssh.yaml
- hosts: all
user: root
tasks:
- name: ssh-key-copy
authorized_key: user=root key="{{ lookup('file','/root/.ssh/id_rsa.pub')}}"
tags:
- sshkey

[root@ansible-master ~]# ansible-playbook push-ssh.yaml
ansible-ceph-1 : ok=2 changed=1 unreachable=0 failed=0
ansible-ceph-2 : ok=2 changed=1 unreachable=0 failed=0
ansible-ceph-3 : ok=2 changed=1 unreachable=0 failed=0

6、安装pip并安装ceph-ansible依赖
[root@ansible-master ~]# curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py" && python get-pip.py && pip install --upgrade setuptools
[root@ansible-master ceph-ansible]# pip install -r requirements.txt

7、修改ansible变量
[root@ansible-master group_vars]# pwd
/root/ceph-ansible/group_vars

[root@ansible-master group_vars]# cp all.yml.sample all.yml && cp osds.yml.sample osds.yml && cp mgrs.yml.sample mgrs.yml

all.yml修改如下:
mon_group_name: mons
osd_group_name: osds
ntp_daemon_type: chronyd
ceph_origin: repository
ceph_repository: community
ceph_mirror: http://mirrors.163.com/ceph
ceph_stable_key: http://mirrors.163.com/ceph/keys/release.asc
ceph_stable_release: luminous
ceph_stable_repo: "{{ ceph_mirror }}/rpm-{{ ceph_stable_release }}/el7/x86_64"
monitor_interface: eth0
public_network: 192.168.0.0/24
cluster_network: 192.168.0.0/24
radosgw_interface: eth0
radosgw_address: 0.0.0.0

osds.yml修改如下:
copy_admin_key: true
devices:
- /dev/vdb
osd_scenario: collocated

mgrs.yml修改如下:
ceph_mgr_modules: [status,dashboard]

8、执行ansible开始部署ceph,部署哪些模块会在/etc/ansible/hosts中定义(下面是我们之前定义的),如果在该文件中没有对应模块定义,ansible会忽略该模块的部署。
[mons]
ansible-ceph-[1:3] ansible_ssh_pass=yujiang2
[osds]
ansible-ceph-[1:3] ansible_ssh_pass=yujiang2
[rgws]
ansible-ceph-[1:3] ansible_ssh_pass=yujiang2
[mgrs]
ansible-ceph-[1:3] ansible_ssh_pass=yujiang2

9、修改ansible host_key_checking
[root@ansible-master ~]# vim /etc/ansible/ansible.cfg
[defaults]
host_key_checking = False

[root@ansible-master ceph-ansible]# pwd
/root/ceph-ansible
[root@ansible-master ceph-ansible]# ansible-playbook site.yml
PLAY RECAP *******************************************************************************************************************************************************************************************************************************************************************
ansible-ceph-1 : ok=310 changed=17 unreachable=0 failed=0
ansible-ceph-2 : ok=287 changed=17 unreachable=0 failed=0
ansible-ceph-3 : ok=289 changed=20 unreachable=0 failed=0

INSTALLER STATUS *************************************************************************************************************************************************************************************************************************************************************
Install Ceph Monitor : Complete (0:00:37)
Install Ceph Manager : Complete (0:01:05)
Install Ceph OSD : Complete (0:00:38)
Install Ceph RGW : Complete (0:00:30)

参考:https://cloud.tencent.com/developer/article/1122336

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[root@k8s-master ~]# yum groupinstall "Development Tools" -y && yum install -y m2crypto python-setuptools && easy_install pip && python -m pip install --upgrade pip && python -m pip install --upgrade setuptools && pip install shadowsocks

[root@k8s-master ~]# vi /etc/shadowsocks.json
{
"server": "38.*.*.*",
"server_port": 7777,
"password": "pwd",
"method": "aes-256-cfb",
"local_address":"127.0.0.1",
"local_port":1080
}

[root@k8s-master ~]# sslocal -c /etc/shadowsocks.json

[root@k8s-master ~]# yum install epel-release -y && yum install -y privoxy
[root@k8s-master ~]# vi /etc/privoxy/config
forward-socks5t / 127.0.0.1:1080
listen-address 127.0.0.1:8118

[root@k8s-master ~]# vi ~/.bashrc
export http_proxy=http://127.0.0.1:8118
export https_proxy=http://127.0.0.1:8118
export ftp_proxy=http://127.0.0.1:8118
[root@k8s-master ~]# source ~/.bashrc

[root@k8s-master ~]# systemctl restart privoxy.service && systemctl status privoxy.service

参考文章:针对以下文章的实际操作

【1】https://juejin.im/post/5b45d4185188251ac062f27c

【2】https://blog.qikqiak.com/post/use-kubeadm-install-kubernetes-1.10/

虚拟机master节点和slave节点——Docker版本

1
2
3
4
5
6
# 所有master节点和slave节点
[root@k8s-master ~]# docker --version
Docker version 1.13.1, build dded712/1.13.1

[root@k8s-master ~]# yum install -y docker && systemctl enable docker && systemctl restart docker
[root@k8s-slave ~]# yum install -y docker && systemctl enable docker && systemctl restart docker

虚拟机master节点——安装etcd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
[root@k8s-master ~]# hostnamectl
Static hostname: rhel7
Icon name: computer-vm
Chassis: vm
Machine ID: 5b097de8abcc4690b4cdd7ec9deefbc4
Boot ID: 89bd04baef274f4f9e8eceb107547e5a
Virtualization: kvm
Operating System: CentOS Linux 7 (Core)
CPE OS Name: cpe:/o:centos:centos:7
Kernel: Linux 3.10.0-862.el7.x86_64
Architecture: x86-64

[root@k8s-master ~]# yum install wget -y && wget https://github.com/coreos/etcd/releases/download/v3.3.9/etcd-v3.3.9-linux-amd64.tar.gz && tar zxvf etcd-v3.3.9-linux-amd64.tar.gz && cd etcd-v3.3.9-linux-amd64

[root@k8s-master etcd-v3.3.9-linux-amd64]# cp etcd /usr/bin/ && cp etcdctl /usr/bin/

[root@k8s-master ~]# vi /usr/lib/systemd/system/etcd.service
[Unit]
Description=Etcd Server
After=network.target
After=network-online.target
Wants=network-online.target
[Service]
Type=notify
WorkingDirectory=/var/lib/etcd/
EnvironmentFile=-/etc/etcd/etcd.conf
User=root
# set GOMAXPROCS to number of processors
ExecStart=/bin/bash -c "GOMAXPROCS=$(nproc) /usr/bin/etcd --name=\"${ETCD_NAME}\" --data-dir=\"${ETCD_DATA_DIR}\" --listen-client-urls=\"${ETCD_LISTEN_CLIENT_URLS}\""
Restart=on-failure
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target

[root@k8s-master ~]# systemctl daemon-reload && systemctl start etcd && systemctl enable etcd && systemctl status etcd

使用kubeadm安装要关闭etcd,

虚拟机master节点——安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# 所有master和slave节点,配置kubernetes源
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
[root@k8s-master ~]# yum install -y kubelet kubeadm kubectl ipvsadm

# 所有master和slave节点
[root@k8s-master ~]# iptables -P FORWARD ACCEPT

# 所有master和slave节点,配置转发相关参数
cat <<EOF > /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
vm.swappiness=0
EOF
[root@k8s-master ~]# sysctl --system

# 所有master和slave节点,加载ipvs相关内核模块
[root@k8s-master ~]# modprobe ip_vs
[root@k8s-master ~]# modprobe ip_vs_rr
[root@k8s-master ~]# modprobe ip_vs_wrr
[root@k8s-master ~]# modprobe ip_vs_sh
[root@k8s-master ~]# modprobe nf_conntrack_ipv4
[root@k8s-master ~]# lsmod | grep ip_vs
ip_vs_sh 12688 0
ip_vs_wrr 12697 0
ip_vs_rr 12600 0
ip_vs 141432 6 ip_vs_rr,ip_vs_sh,ip_vs_wrr
nf_conntrack 133053 6 ip_vs,nf_nat,nf_nat_ipv4,xt_conntrack,nf_nat_masquerade_ipv4,nf_conntrack_ipv4
libcrc32c 12644 4 xfs,ip_vs,nf_nat,nf_conntrack
# 或持久化
[root@k8s-master modules]# cat /etc/sysconfig/modules/k8s.modules
#!/bin/sh
/sbin/modinfo -F filename ip_vs > /dev/null 2>&1
if [ $? -eq 0 ]; then
/sbin/modprobe ip_vs
fi
/sbin/modinfo -F filename ip_vs_rr > /dev/null 2>&1
if [ $? -eq 0 ]; then
/sbin/modprobe ip_vs_rr
fi
/sbin/modinfo -F filename ip_vs_wrr > /dev/null 2>&1
if [ $? -eq 0 ]; then
/sbin/modprobe ip_vs_wrr
fi
/sbin/modinfo -F filename ip_vs_sh > /dev/null 2>&1
if [ $? -eq 0 ]; then
/sbin/modprobe ip_vs_sh
fi
/sbin/modinfo -F filename nf_conntrack_ipv4 > /dev/null 2>&1
if [ $? -eq 0 ]; then
/sbin/modprobe nf_conntrack_ipv4
fi
[root@k8s-master modules]# chmod +x k8s.modules
[root@k8s-master ~]# reboot
[root@k8s-master ~]# lsmod | grep ip_vs

# 所有master和slave节点,修改hosts文件
[root@k8s-master ~]# cat /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.56.200 k8s-master k8s-master.localdomain
192.168.56.201 k8s-slave k8s-slave.localdomain

# 所有master和slave节点
[root@k8s-master ~]# DOCKER_CGROUPS=$(docker info | grep 'Cgroup' | cut -d' ' -f3)
[root@k8s-master ~]# echo $DOCKER_CGROUPS
# 或持久化
[root@k8s-master ~]# vi /etc/profile
export DOCKER_CGROUPS=$(docker info | grep 'Cgroup' | cut -d' ' -f3)
[root@k8s-master ~]# source /etc/profile

# 所有master和slave节点
cat >/etc/sysconfig/kubelet<<EOF
KUBELET_EXTRA_ARGS="--cgroup-driver=$DOCKER_CGROUPS --pod-infra-container-image=registry.cn-hangzhou.aliyuncs.com/google_containers/pause-amd64:3.1"
EOF
[root@k8s-master ~]# systemctl daemon-reload && systemctl enable kubelet && systemctl start kubelet && systemctl status kubelet

# 永久关闭 注释/etc/fstab文件里swap相关的行
[root@k8s-master ~]# swapoff -a

#
cat >kubeadm-master.config<<EOF
apiVersion: kubeadm.k8s.io/v1alpha2
kind: MasterConfiguration
kubernetesVersion: v1.11.2
imageRepository: registry.cn-hangzhou.aliyuncs.com/google_containers
api:
advertiseAddress: 192.168.56.200

controllerManagerExtraArgs:
node-monitor-grace-period: 10s
pod-eviction-timeout: 10s

networking:
podSubnet: 10.244.0.0/16

kubeProxy:
config:
# mode: ipvs
mode: iptables
EOF

[root@k8s-master ~]# kubeadm config images pull --config kubeadm-master.config
[config/images] Pulled registry.cn-hangzhou.aliyuncs.com/google_containers/kube-apiserver-amd64:v1.11.2
[config/images] Pulled registry.cn-hangzhou.aliyuncs.com/google_containers/kube-controller-manager-amd64:v1.11.2
[config/images] Pulled registry.cn-hangzhou.aliyuncs.com/google_containers/kube-scheduler-amd64:v1.11.2
[config/images] Pulled registry.cn-hangzhou.aliyuncs.com/google_containers/kube-proxy-amd64:v1.11.2
[config/images] Pulled registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.1
[config/images] Pulled registry.cn-hangzhou.aliyuncs.com/google_containers/etcd-amd64:3.2.18
[config/images] Pulled registry.cn-hangzhou.aliyuncs.com/google_containers/coredns:1.1.3

[root@k8s-master ~]# kubeadm init --config kubeadm-master.config
[init] using Kubernetes version: v1.11.2
[preflight] running pre-flight checks
I0822 05:07:42.655181 8212 kernel_validator.go:81] Validating kernel version
I0822 05:07:42.655280 8212 kernel_validator.go:96] Validating kernel config
[preflight] Some fatal errors occurred:
[ERROR DirAvailable--var-lib-etcd]: /var/lib/etcd is not empty
[preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...`
[root@k8s-master ~]# rm -rf /var/lib/etcd/*
[root@k8s-master ~]# kubeadm init --config kubeadm-master.config
[init] using Kubernetes version: v1.11.2
[preflight] running pre-flight checks
I0822 05:08:00.364246 8239 kernel_validator.go:81] Validating kernel version
I0822 05:08:00.364505 8239 kernel_validator.go:96] Validating kernel config
[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 [k8s-master.localdomain kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 192.168.56.200]
[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 [k8s-master.localdomain 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 [k8s-master.localdomain localhost] and IPs [192.168.56.200 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 39.501843 seconds
[uploadconfig] storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config-1.11" in namespace kube-system with the configuration for the kubelets in the cluster
[markmaster] Marking the node k8s-master.localdomain as master by adding the label "node-role.kubernetes.io/master=''"
[markmaster] Marking the node k8s-master.localdomain 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 "k8s-master.localdomain" as an annotation
[bootstraptoken] using token: vyabge.9wi2cqgukrf3c2qh
[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 192.168.56.200:6443 --token vyabge.9wi2cqgukrf3c2qh --discovery-token-ca-cert-hash sha256:2224c75b200730114e0153aa7aea9014affd90ffc47cfe08b7f931ef8e58b6ab

[root@k8s-master ~]# rm -rf $HOME/.kube
[root@k8s-master ~]# mkdir -p $HOME/.kube
[root@k8s-master ~]# sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
[root@k8s-master ~]# sudo chown $(id -u):$(id -g) $HOME/.kube/config
[root@k8s-master ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master.localdomain NotReady master 2d v1.11.2

虚拟机master节点——安装flannel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 下载配置文件
[root@k8s-master ~]# mkdir flannel && cd flannel
[root@k8s-master flannel]# wget https://raw.githubusercontent.com/coreos/flannel/v0.10.0/Documentation/kube-flannel.yml

# 修改配置
# 此处的ip配置要与上面kubeadm的pod-network一致
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "vxlan"
}
}
# 修改镜像
image: registry.cn-shanghai.aliyuncs.com/gcr-k8s/flannel:v0.10.0-amd64

# 如果Node有多个网卡的话,参考flannel issues 39701,
# https://github.com/kubernetes/kubernetes/issues/39701
# 目前需要在kube-flannel.yml中使用--iface参数指定集群主机内网网卡的名称,
# 否则可能会出现dns无法解析。容器无法通信的情况,需要将kube-flannel.yml下载到本地,
# flanneld启动参数加上--iface=<iface-name>
containers:
- name: kube-flannel
image: registry.cn-shanghai.aliyuncs.com/gcr-k8s/flannel:v0.10.0-amd64
command:
- /opt/bin/flanneld
args:
- --ip-masq
- --kube-subnet-mgr
- --iface=enp0s8

# 启动
[root@k8s-master flannel]# kubectl apply -f kube-flannel.yml
clusterrole.rbac.authorization.k8s.io/flannel created
clusterrolebinding.rbac.authorization.k8s.io/flannel created
serviceaccount/flannel created
configmap/kube-flannel-cfg created
daemonset.extensions/kube-flannel-ds created

# 查看
[root@k8s-master flannel]# kubectl get pods --namespace kube-system
NAME READY STATUS RESTARTS AGE
coredns-777d78ff6f-2z75r 0/1 ContainerCreating 0 2d
coredns-777d78ff6f-rghp9 0/1 ContainerCreating 0 2d
etcd-k8s-master.localdomain 1/1 Running 1 2d
kube-apiserver-k8s-master.localdomain 1/1 Running 1 2d
kube-controller-manager-k8s-master.localdomain 1/1 Running 1 2d
kube-flannel-ds-jftcs 1/1 Running 0 19s
kube-proxy-wvsxb 1/1 Running 1 2d
kube-scheduler-k8s-master.localdomain 1/1 Running 1 2d
[root@k8s-master flannel]# kubectl get svc --namespace kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP 2d

配置slave节点加入集群

1
2
[root@k8s-master ~]# kubeadm join 192.168.56.201:6443 --token vliohs.l5lz1treou1srvt1 --discovery-token-ca-cert-hash sha256:0c08eeb897ef635c188770ef42e82580326f5ff534be49cc1a80fd8785173495

极客时间部署方式

master节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 添加kubernetes源
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=0
EOF

# 安装kubelet、kubeadm、kubectl、kubernetes-cni
sudo yum -y install epel-release && yum clean all && yum makecache
sudo yum -y install kubelet-1.11.1 kubeadm-1.11.1 kubectl-1.11.1 kubernetes-cni

# 安装docker
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install docker-ce -y && sudo systemctl start docker && sudo systemctl enable docker


参考文章:https://www.jianshu.com/p/fc36368b5c44

mac virtualbox 制作两台虚拟机进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
机器名:k8s-master
[root@k8s-master ~]# cat /etc/hostname
k8s-master.localdomain

[root@k8s-master ~]# systemctl stop firewalld.service
[root@k8s-master ~]# systemctl disable firewalld.service

[root@k8s-master ~]# cat /etc/sysconfig/selinux
# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
# enforcing - SELinux security policy is enforced.
# permissive - SELinux prints warnings instead of enforcing.
# disabled - No SELinux policy is loaded.
SELINUX=disabled
# SELINUXTYPE= can take one of three two values:
# targeted - Targeted processes are protected,
# minimum - Modification of targeted policy. Only selected processes are protected.
# mls - Multi Level Security protection.
SELINUXTYPE=targeted

[root@k8s-master ~]# cat /etc/sysconfig/network-scripts/ifcfg-enp0s8
TYPE=Ethernet
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=static
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
IPV6_ADDR_GEN_MODE=stable-privacy
NAME=enp0s8
DEVICE=enp0s8
ONBOOT=yes
IPADDR=192.168.56.200 #静态IP
GATEWAY=192.168.56.1 #默认网关
NETMASK=255.255.255.0 #子网掩码
DNS1=192.168.56.1 #DNS 配置

机器名:k8s-slave
[root@k8s-slave ~]# cat /etc/hostname
k8s-slave.localdomain

[root@k8s-slave ~]# systemctl stop firewalld.service
[root@k8s-slave ~]# systemctl disable firewalld.service

[root@k8s-slave ~]# cat /etc/sysconfig/selinux
# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
# enforcing - SELinux security policy is enforced.
# permissive - SELinux prints warnings instead of enforcing.
# disabled - No SELinux policy is loaded.
SELINUX=disabled
# SELINUXTYPE= can take one of three two values:
# targeted - Targeted processes are protected,
# minimum - Modification of targeted policy. Only selected processes are protected.
# mls - Multi Level Security protection.
SELINUXTYPE=targeted

[root@k8s-slave ~]# cat /etc/sysconfig/network-scripts/ifcfg-enp0s8
TYPE=Ethernet
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=static
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
IPV6_ADDR_GEN_MODE=stable-privacy
NAME=enp0s8
DEVICE=enp0s8
ONBOOT=yes
IPADDR=192.168.56.201 #静态IP
GATEWAY=192.168.56.1 #默认网关
NETMASK=255.255.255.0 #子网掩码
DNS1=192.168.56.1 #DNS 配置

两台虚拟机安装docker并启动服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
[root@k8s-master ~]# yum -y install docker
[root@k8s-master ~]# systemctl start docker.service
[root@k8s-master ~]# systemctl status docker.service
● docker.service - Docker Application Container Engine
Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: disabled)
Active: active (running) since 六 2018-08-18 11:10:58 EDT; 6s ago
Docs: http://docs.docker.com
Main PID: 1522 (dockerd-current)
CGroup: /system.slice/docker.service
├─1522 /usr/bin/dockerd-current --add-runtime docker-runc=/usr/libexec/docker/docker-runc-current --default-runtime=docker-runc --exec-opt native.cgroupdriver=systemd ...
└─1526 /usr/bin/docker-containerd-current -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --metrics-interval=0 --start-timeout 2m --state-dir /var/run/d...

8月 18 11:10:58 k8s-master.localdomain dockerd-current[1522]: time="2018-08-18T11:10:58.149875557-04:00" level=warning msg="Docker could not enable SELinux on the host system"
8月 18 11:10:58 k8s-master.localdomain dockerd-current[1522]: time="2018-08-18T11:10:58.178512067-04:00" level=info msg="Graph migration to content-addressability took ... seconds"
8月 18 11:10:58 k8s-master.localdomain dockerd-current[1522]: time="2018-08-18T11:10:58.179123980-04:00" level=info msg="Loading containers: start."
8月 18 11:10:58 k8s-master.localdomain dockerd-current[1522]: time="2018-08-18T11:10:58.229107163-04:00" level=info msg="Firewalld running: false"
8月 18 11:10:58 k8s-master.localdomain dockerd-current[1522]: time="2018-08-18T11:10:58.312308773-04:00" level=info msg="Default bridge (docker0) is assigned with an IP... address"
8月 18 11:10:58 k8s-master.localdomain dockerd-current[1522]: time="2018-08-18T11:10:58.386055892-04:00" level=info msg="Loading containers: done."
8月 18 11:10:58 k8s-master.localdomain dockerd-current[1522]: time="2018-08-18T11:10:58.403922614-04:00" level=info msg="Daemon has completed initialization"
8月 18 11:10:58 k8s-master.localdomain dockerd-current[1522]: time="2018-08-18T11:10:58.403960859-04:00" level=info msg="Docker daemon" commit="dded712/1.13.1" graphdri...on=1.13.1
8月 18 11:10:58 k8s-master.localdomain systemd[1]: Started Docker Application Container Engine.
8月 18 11:10:58 k8s-master.localdomain dockerd-current[1522]: time="2018-08-18T11:10:58.427377761-04:00" level=info msg="API listen on /var/run/docker.sock"
Hint: Some lines were ellipsized, use -l to show in full.

[root@k8s-slave ~]# yum -y install docker
[root@k8s-slave ~]# systemctl start docker.service
[root@k8s-slave ~]# systemctl status docker.service
● docker.service - Docker Application Container Engine
Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: disabled)
Active: active (running) since 六 2018-08-18 11:11:14 EDT; 3s ago
Docs: http://docs.docker.com
Main PID: 1517 (dockerd-current)
CGroup: /system.slice/docker.service
├─1517 /usr/bin/dockerd-current --add-runtime docker-runc=/usr/libexec/docker/docker-runc-current --default-runtime=docker-runc --exec-opt native.cgroupdriver=systemd ...
└─1521 /usr/bin/docker-containerd-current -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --metrics-interval=0 --start-timeout 2m --state-dir /var/run/d...

8月 18 11:11:14 k8s-slave.localdomain dockerd-current[1517]: time="2018-08-18T11:11:14.647943371-04:00" level=warning msg="Docker could not enable SELinux on the host system"
8月 18 11:11:14 k8s-slave.localdomain dockerd-current[1517]: time="2018-08-18T11:11:14.678117451-04:00" level=info msg="Graph migration to content-addressability took 0.00 seconds"
8月 18 11:11:14 k8s-slave.localdomain dockerd-current[1517]: time="2018-08-18T11:11:14.678885165-04:00" level=info msg="Loading containers: start."
8月 18 11:11:14 k8s-slave.localdomain dockerd-current[1517]: time="2018-08-18T11:11:14.732225305-04:00" level=info msg="Firewalld running: false"
8月 18 11:11:14 k8s-slave.localdomain dockerd-current[1517]: time="2018-08-18T11:11:14.822182514-04:00" level=info msg="Default bridge (docker0) is assigned with an IP ... address"
8月 18 11:11:14 k8s-slave.localdomain dockerd-current[1517]: time="2018-08-18T11:11:14.903524638-04:00" level=info msg="Loading containers: done."
8月 18 11:11:14 k8s-slave.localdomain dockerd-current[1517]: time="2018-08-18T11:11:14.927271723-04:00" level=info msg="Daemon has completed initialization"
8月 18 11:11:14 k8s-slave.localdomain dockerd-current[1517]: time="2018-08-18T11:11:14.927306463-04:00" level=info msg="Docker daemon" commit="dded712/1.13.1" graphdriv...on=1.13.1
8月 18 11:11:14 k8s-slave.localdomain systemd[1]: Started Docker Application Container Engine.
8月 18 11:11:14 k8s-slave.localdomain dockerd-current[1517]: time="2018-08-18T11:11:14.952806378-04:00" level=info msg="API listen on /var/run/docker.sock"
Hint: Some lines were ellipsized, use -l to show in full.

下载registry镜像并启动registry容器

1
2
3
4
5
[root@k8s-master ~]# docker pull registry
[root@k8s-master ~]# docker run -d --name registry-container --restart always -p 5000:5000 -v /data/docker/registry:/tmp/registry docker.io/registry
98dfeb9dc851f54c725ee4a27f058f71ef926b3832f61a4baf8514d354f63531
[root@k8s-master ~]# curl -X GET 127.0.0.1:5000/v2/_catalog
{"repositories":[]}

测试镜像仓库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@k8s-master ~]# docker pull centos:7.5.1804
[root@k8s-master ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/centos 7.5.1804 fdf13fa91c6e 11 days ago 200 MB
docker.io/registry latest b2b03e9146e1 6 weeks ago 33.3 MB

[root@k8s-master ~]# docker tag fdf13fa91c6e 192.168.56.200:5000/centos

[root@k8s-master ~]# vi /etc/sysconfig/docker
OPTIONS='--selinux-enabled --log-driver=journald --signature-verification=false --insecure-registry 192.168.56.200:5000'
[root@k8s-master ~]# systemctl restart docker

[root@k8s-master ~]# docker push 192.168.56.200:5000/centos
The push refers to a repository [192.168.56.200:5000/centos]
bcc97fbfc9e1: Pushed
latest: digest: sha256:7c14180942615fef85cb5c8b1388e028be1a8f79694a5fa30a4025173e42ad61 size: 529

[root@k8s-master ~]# curl -X GET http://192.168.56.200:5000/v2/_catalog
{"repositories":["centos"]}
[root@k8s-master ~]# curl -X GET http://192.168.56.200:5000/v2/centos/tags/list
{"name":"centos","tags":["latest"]}

客户端测试

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@k8s-slave ~]# vi /etc/sysconfig/docker
OPTIONS='--selinux-enabled --log-driver=journald --signature-verification=false --insecure-registry 192.168.56.200:5000'
[root@k8s-slave ~]# systemctl restart docker

[root@k8s-slave ~]# docker pull 192.168.56.200:5000/centos
Using default tag: latest
Trying to pull repository 192.168.56.200:5000/centos ...
latest: Pulling from 192.168.56.200:5000/centos
7dc0dca2b151: Extracting [================================================> ] 71.86 MB/74.69 MB

[root@k8s-slave ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
192.168.56.200:5000/centos latest fdf13fa91c6e 11 days ago 200 MB

制作nginx镜像并上传到私有仓库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@k8s-master ~]# docker pull ubuntu:18.04
[root@k8s-master ubuntu-nginx-dockerfile]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
192.168.56.200:5000/centos latest fdf13fa91c6e 11 days ago 200 MB
docker.io/centos 7.5.1804 fdf13fa91c6e 11 days ago 200 MB
docker.io/ubuntu 18.04 735f80812f90 3 weeks ago 83.5 MB
docker.io/registry latest b2b03e9146e1 6 weeks ago 33.3 MB

[root@k8s-master ubuntu-nginx-dockerfile]# vi Dockerfile
# 指定基于的基础镜像
FROM ubuntu:18.04
# 维护者信息
MAINTAINER yujiang
# 更新软件
RUN sed -i 's/http:\/\/archive\.ubuntu\.com\/ubuntu\//http:\/\/mirrors\.aliyun\.com\/ubuntu\//g' /etc/apt/sources.list
RUN apt-get update
# 安装软件
RUN apt-get install nginx net-tools curl vim -y
# 允许指定的端口
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

[root@k8s-master ubuntu-nginx-dockerfile]# docker build -t 192.168.56.200/nginx:v1.0 .

课程地址:https://www.imooc.com/learn/1009

安装

1
yum install cronie crontabs -y

验证CROND服务

验证crond服务和crontab工具(centos7)

检查crond服务是否安装及启动:

1
yum list cronie && systemctl status crond

检查crontab工具是否安装:

1
yum list crontabs && which crontab && crontab -l

CRONTAB架构

1
2
3
4
5
   文件                              解析                              守护进程
crontab工具
* * * * * ==========> CROND
my command
按照格式编写定时任务 定时检查是否有任务需要执行

例如:

1
2
3
4
5
6
7
(1)编辑任务列表
crontab -e
(2)查看任务列表
crontab -l
(3)重启crond服务,查看crond服务状态
systemctl restart crond
systemctl status crond

CRONTAB文件格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
*  *  *  *  *     my command
分 时 日 月 周 要运行的命令

分:范围0-59
时:范围0-23
日:范围1-31
月:范围1-12
周:范围0-6
my command:范围命令或脚本

*:取值范围内的数字,通常代表对应时间区间内所涵盖的所有数字
/:代表每,通常与*组合。例如2/*在"分"这个占位符中代表每两分钟,5/*在"时"这个占位符代表每5个小时
-:代表某个数字到某个数字之间的区间,2-10在"分"占位符中代表第2分钟到第10分钟
,:分开几个离散的数字。2,10 代表第2分钟和第10分钟

例子:
crontab -e
* * * * * echo -e "Hello" > /root/crontabtest.output

CRONTAB配置文件

系统配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
############### /etc/crontab ###############
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root

# For details see man 4 crontabs

# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed

系统用户crontab配置文件保存目录(crontab -e)

1
2
3
4
############### /var/spool/cron/ ###############
文件以linux用户区分
root:/var/spool/cron/root
user01:/var/spool/cron/user01

CRONTAB环境变量

添加PATH到/etc/crontab

1
2
3
4
############### /etc/crontab ###############
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/jdk1.8.0_111/bin

* * * * * root java -version 2> /root/temp.out

在执行具体任务前引入系统/用户环境变量(推荐)

1
2
3
30 2 * * * source /etc/profile;sh /root/test.sh
30 2 * * * source ~/.bash_profile;sh /root/test.sh
systemctl restart crond

CRONTAB日志

Cron日志保存在系统目录/var/log/cron

1
tail -n 2 /var/log/cron

实战

CRONTAB清理系统日志

1
2
3
查看当前目录所有文件大小
# du -sh *
* 1 * * * cat /dev/null > /var/log/messages