编程建议
Effective java第三版笔记
Spring Boot
是对Spring Framework
的二次封装, 重要是为了解决Spring Framework
中配置, 环境的部署等琐碎问题, 而只需专注与业务开发
Spring Framework
是一个开源的java开发框架, 它的核心是IOC
和AOP
, 可以更容易地构建出企业级java应用
Spring Boot
如何做到帮助开发者省略/简化配置
Spring Boot
对日常开发中比较常见的场景提供了默认配置Spring Boot
对常用场景进行了整合, 将这些场景中所需的依赖都收集整理到一个依赖的web
容器: Spring Boot
在运行时可以不依赖外部的Web容器, 而是使用内嵌嵌入式Web容器来支撑应用运行Web
容器包括Tomcat
, Jetty
等Servlet
容器以及Netty
等非堵塞Web
容器在内的所有能部署Web
项目的应用服务器
Spring FrameWork
配置第三方组件有两种方式, 1. 通过配置文件; 2. 编写注解配置类. 这个过程被称为组件装配
自动装配: 框架自动根据项目中整合的场景依赖, 做出判断并装配合适的Bean到IOC容器中
itoa
是个什么const
中. 我觉得没啥用, 一点通用性都没有, 作用也非常有限. 忘记它都可以, java
中有这样的东西吗? 好像是有的, 叫什么生成器., 但这个有些用, 也具有通用性. int
和uint
有什么用, 已经有int8
, int16
, int32
, int64
和uint
, uint8
, uint16
, uint32
, uint64
int
和uint
应该是在程序内部自己使用的, 如果要调用外部的接口, 也就是别人包里面的东西, 肯定只能用确定位数的数字, 否则大小有可能不匹配, 会吗. go
编译的时候应该也会把别人包里面的东西一起编译吧, 会吗? 那go
没哟编译好的库吗? 所有的东西都要自己编译吗? java
是怎么引入jar
包的呢? go
语言的包管理是真的乱, 下载的包的位置, 找包的路径, go get
这个东西又一直在变, go install
又是什么东西, 下载了包为什么还要自己编译, 真是吐了. java
如何使用jar
包呢. java
和go
对于编译成包, 和使用包都有些复杂, 复杂吗? 学习这些语言时, 好像都不关心这些东西? 真的不重要吗?go
语言中有声明和定义和区别吗? java
有吗? go
区分函数的声明和定义吗? 现在go
函数的定义不需要出现使用之前go
语言不存在未初始化的值, 无论是包级别的变量, 还是函数内部的, 都会被赋零值. 那怎么区分不存在和零值呢? 例如输入空字符串和不输入字符串go
语言的人写出bug
. java
没有这种赋值和定义同时进行的操作, 只能同时定义, int a = 10, b = 20;
go
的可运行程序的第一个非注释行必须是package main
? go
语言中package
与文件路径无关, 那它有什么用呢?go
语言允许返回局部变量的地址, 且这个地址是有效的, 不会被覆盖, 这个跟c
语言不同, c
语言地址里面的值是会被修改, 导致不可用, 这个更像java
中的返回函数中new
的对象. 看来go
跟c
的实现存在明显区别java
没有自带的命令行参数解析库, 需要自己去下载jar
包, 这里可以借助maven
struct{}
和[0]int
, 可能会有相同的地址, 同时go
语言对于大小为0的结构的处理还存在问题, 那为什么不修呢new
只是一个预定义的函数, 不是一个关键字, 我们因此可以将new
名字重新定义为别的类型. 这个该说是go
语言的有点呢, 还是确定呢? 我认为还是缺点更大一些, 反正都有别的关键字了, 多这一个也不多啊. 也可以认为new
并不重要, 毕竟可以通过返回内部变量的地址来达到同样的效果go
语言的每一行不是以分号分隔的, 有一种复杂的规则来认定一行, 值得吗, 为了省一个分号go
自己判断变量是在栈上分配空间还是在堆上分配空间, 不是靠new
来决定的, 真是增加了理解程序的难度, 和c
语言完全不同go
语言中++
和--
是语句, 不可以在++
和--
的同时再进行赋值, 而在java
中是允许的, go
中这种强制行为, 感觉不灵活, 应该还是缺点更多go
的元组赋值(多变量同时赋值), 比起java
多了一种选择go
中返回多个值的函数, 还能用说与返回值个数的变量接收, 导致和全部接收不同的结果. 简直离谱. 例如map
的取值操作go
语言只用常量的赋值才会发生隐式类型转换吗? java
只有数值类型的赋值和计算才会发生隐式转换, 什么是隐式转换呢?go
语言中type
的用法是type 类型名字 底层类型
, 与c
语言的顺序相反, type
就是为了给类型一个别名, 就是为了增加代码可读性吗, 还可以防止类型混用%v
, %s
, %g
有什么区别, 为什么会有这么多种打印一个类型值的方式float64(1.0)
和1.0
虽然底层类型时相同的, 但是一个是命名了, 一个是未命名. 未命名的可以用于赋值给 相同底层数据结构的变量(运算符都是支持的)go
语言中一个目录下只能有一个包, 也就是说package xxx
, xxx
必须相同, xxx
还会作为别的包使用这个包时带的前缀, 同一个包中可以不带前缀. 感觉跟java
的包还是有些不同的go moudle
语言对于包导入, 不再依赖$GOPATH
, 那它从那里找包呢? go mod
文件定义了一个模块名, 导入包时, 以这个模块名开头的路径, 会把不带模块头的路径作为相对路径, 从go mod
文件所在文件夹, 开始找包. 感觉比java
指定类路径的方式复杂了go
包的初始化, 外部变量循环依赖怎么办, 内部包级变量循环依赖怎么办? 感觉比java
的复杂c
语言是一样的go
语言中if
语句的判断条件中可以定义变量, 而且这个变量还可以在整个if
和else
语句体中使用, 是一个全新的写法, 之前没有任何一种编程语言使用过这种方式, 有增加了复杂度go
语言中包级别的变量按照依赖顺序进行解析和初始化, 局部变量只会按照执行顺序初始化go
语言的main
函数的参数是空, 更java
和c
语言不一样, 如果要使用命令行参数, 就得使用go
自带的一个库go
语句只有局部变量会检查是否被使用吗, 包级变量不会吗?go
语言把数据类型分成了四类, 基础数据类型, 复合类型, 引用类型和接口类型. 字符串为什么被归为基础数据类型呢, 它不是一个数组吗. 数组为什么是复合类型, 不是引用类型?, 指针不是一个值吗, 为什么不是基础类型呢, 接口类型不是引用类型吗? go
的数据分类的根据是什么呢, 真是看不懂go
语言中int
和uint
的大小, 不仅和编译器, 还和平台有关系, 我们根本不知道它的大小, 我们该怎么使用它呢. 这是不是说明go
编译的程序, 无法在32位和64位上同时运行, 需要同时编译, uintptr
的大小也跟平台有关, 是不是用到了, 就得在特定的机器上编译呢? 那有点麻烦, 但是还好, 不用修改源码, 重新编译就行rune
用int32
作为底层, byte
用int8
作为底层, 一个用有符号数, 一个用符号数, 明明只是一个编码, 不关注数据的正负go
语言有<<
和>>
, 用的是逻辑位移还是算术位移呢, c
的呢, 反正java
有<<<
和>>>
来做区分. go
语言中, 在x<<n
中, n
必须是无符号数, 为什么呢, 正的有符号数不行吗? go
语言的>>
是算术左移, 但可以通过类型转换来实现首位的填充值, java
只有有符号数所以用了两种位移来实现go
语言的&^
是啥运算符, 叫做AND NOT
, 位清空, 有什么用呢, 别的语言都没有? 通过与, 或, 非, 应该也可以实现吧go
语言中%
只能用于整数运算, c
语言和java
语言也是一样吗? 其实也不用管这些细枝末节的运算细节, 编程中几乎用不到, 通常只会将%
用于正整数, 这时这个行为在任何编程语言中都是一致的, 不会有什么差别, 其他的细节, 记还容易记错go
语言多次import
一个包, 这个包会被多次初始化吗? 我认为只初始化一次比较合理, 不然没办法共享变量了go
语言中len
函数返回的是一个有符号类型, 有符号类型使逆序遍历时, 不会转换为最大的正整数而发生panic
. 其实也对无符号数, 太容易变成最大的正整数了, 非常容易出错, 这有可能也是java
只用有符号数的原因吧, 所以只在数值的大小没用的时候才使用无符号数, 例如位运算go
语言中, rune
变量存的是符号的码表值, 还是经过UTF-8
编码后的值呢? 应该是码表值. java
中也是一样go
语言除以float
变量的0
值, 居然不报错, 真是离谱, 但是直接除以0.0
又会报错, 就离谱go
语言短变量声明的默认类型为int
和float64
go
语言true
不会隐式转换为1, false
也不会隐式转换为0, 反之亦然, java
也是这样go
语言中字符串是不可变的, 跟java
是一样的, 为什么它们都是不可变的呢? 我只能想到一个原因共享提升性能go
中字符串的len
返回的是字节的个数, java
中返回的字符的个数go
中字符串不能简单的看成字符的数组, 字符串是不可变的, 而数组是会相互影响的go
的字符串支持[]
来取值, java
不支持[]
, 但有charAt
函数go
支持`扩起来的原生字符串, java
支持吗UTF-8
大小为1
到4
字节, 那对于4位的unicode
码点, 是不是有可能放不下呢?go
语言字符串不是rune
的数组, 直接就是UTF-8
编码好的字节流,, 跟java
完全不一样. 那怎么取第几个字节呢? 使用utf8.DecodeRuneInString
来解码, 或使用range
, range
自动就是按字符读取的, 那我就想取第i
个字符呢? 可以将string
转为rune
数组, 这丫就可以取了, 感觉go
语言使用string
还是好没必要, 几乎每什么用. 那要向java
那样使用字符串, 还要利用string
来赋值, 再转乘[]rune
来使用, rune
可以用字符字面量, unicode
码点, 对于小于256的字符, 还可以使用\x
, 好像每啥用, string
可以字符串字面量, UTF-8
字节序列, 和unicode
码点序列go
语言和c
语言都有scanf
, 但java
没有, 但java
应该有从字符串提取出值的办法go
中每种常量的潜在类型都是基础类型, 那是说只要潜在类型为基础类型的, 就能是常量吗?go
中const
和type
都可以使用括号来同时定义多个go
中常量间的所有算术运算, 逻辑运算和比较运输的结果也是常量, 对于常量的类型转换操作或部分函数也是常数, 常数就是提升了一点可读性和性能, 好像也没有什么大作用go
中通过%T
来打印类型, java
可以通过打印类名go
语言常量也一些简化的赋值方式, 还有一个作用相当有限的itoa
关键字来赋值, 感觉没有什么必要go
中字面量称为无类型常量, 无类型常量可以自己转为声明的类型, 如果没有声明类型, 就是go
定义的规则指定的类型, 算术运算也是同理, 这跟c
和java
中字面量有默认的类型不同go
中var a [3]int
和var b [4]int
, a
和b
是不同类型, 不能相互赋值, 在c
语言中也是一样. 但在java
中长度不会导致数组类型不一样, 而且定义数组变量时, 也没有指定长度. java
中数组怎么初始化的呢? 会有默认值吗go
语言中数组的相等判断非常奇怪, 只有数据类型, 长度, 且数据类型能比较的情况下, 数组才能比较, 否则会报错. 当数据长度, 且数据值相等时, 才相等. 能比较的数据结构, 不是只有基础数据结构, 结构体支持吗go
语言传递数组时, 是会挨个复制数组值的, 这个跟c
和java
都是不一样的. 可以说是多了一种防止修改数组的方式go
对指向数组的指针是可以直接便利的, ptr[i]
和(*ptr)[i]
的效果是一样的, 在range
中ptr
和(*ptr)
也是一样的, 这很离谱go
语言数组和slice
的语法很难区分, 只有定义的时候, slice
的[]
不写值, 而数组是需要指定大小或者...
. len
和cap
既可以运用于数组, 也可以用于slice
. s[i:j]
切片操作中j
可以超过len
, 但不能超过cap
, 真的无语, 切片操作可以用在数组和slice
上, 切出来是slice
go
语言的slice
不支持比较, java
的是支持的, go
语言面向对象的特性太差了. 是怕slice
自己引用自己吗, java
不也有这种问题吗, 那为什么java
可以比较. map
的key
, 无论是go
还是java
, key
如果变了, hash没变
会导致位置不变, 如果引用了同一个key
, 会导致判断不出key
已经存在, 或者相等. 放入map
后, key
的hash
值应该是不会在变了, 不然还要watch
, key
的值变没有, 太消耗性能了, 这只是一个推测还没有自我验证过.go
的slice
为啥不能比较, 吐了slice
只能和nil
比较, 可以用[]int(nil)
或nil
来赋值给以slice
变量, nil
可以赋值给指针和引用类型, 接口类型算引用类型吗, 结构体可以用nil
赋值吗go
中len
和cap
为0的slice
, 就可以为nil
, 也可以不为nil
. 为nil
的sclie
底层没有引用数组, go
中nil
有什么用呢???go
的printf
怎么用的, 数组怎么打印, 类型怎么打印go
语言中copy
函数就只能用于数组的复制吗? 能使用的范围太小了吧,append
也只能用于slice
吗go
语言中var x, y []int
可以声明多个slice
, 那声明多个指针呢? 指针能var x, y *int
这么写吗go
语言支持可变参数, java
支持吗? 可变参数可以用slice
来实现, 那直接用slice
来传参数不行吗, 就为了调用函数时写起来好看吗delete
的用法, 无语, 增删改查, 不应该是最重要的吗, go
中数组删除是自己用分片来实现吗? delete
是用来删除map
中的元素的go
语言中cap
有必要让go
语言使用者感知到吗? 真不知道有什么用, java
是没有让使用者感知到的go
的map
中key
必须是支持==
的, 可是go
中结构体支持==
吗, 我认为应该必须得支持, 不然map
也太鸡肋了, 确认了, 结构体是支持==
go
中在map
中取值时, 如果key
不存在, 返回的是零值, 其实也只能这样, 总不能报错吧go
中不能对map
中的value
取地址, value
是会移动, 地址可能会随时失效. 地址的抽象程度太低了, 很容易受别的因素变化go
语言中map
的零值是nil
, 这时map
没有引用任何哈希表, nil
的slice
和空的slice
几乎没有差别, nil
的map
无法添加值, 毕竟append
函数是有能力自己创建新slice
的go
语言中map
是无法比较的, 除了和nil
, 这个是和slice
是一致的. java
中List
和Map
是能比较的吗go
语言中len
函数可以运用于map
go
中0个值的聚合体有什么用呢? go
语言中点操作符可以用于指向结构体的指针, 这是什么骚操作, 直接用->
不好吗, 这样很容易把结构体和结构体指针弄混, go
语言中指针是不能用->
, 好像都没有这个操作符.go
中调用函数返回的是值, 并不是一个可取地址的变量. 头一次在编程语言中听说这种说法. c
语言会有这种问题吗? 应该会有吧, 没有验证过go
中定义结构体时, 成员的顺序不同, 会导致定义的结构体不同, 那这种不同体现在哪里呢? 不是有类型名称来做区分了吗? 简直无法理解go
语言中结构体的导出, 也是根据首字母的大小写来判断的, 导出能在包外访问, 没有导出的, 只能在包内访问go
语言中一个命名为S的结构体类型不能再包含S类型的成员. 但是S类型的结构体可以包含*S
指针类型的成员, c
和java
是怎样的呢go
中为什么结构体字面量可以取地址, 而整数不行? 看不懂看不懂go
语言中结构体的比较, 限制好大, 而且非常不灵活. go
要求结构体的所有成员都必须可以比较, 而且必须全部相等, 这比java
差太多了go
的匿名成员机制, 也没有啥用呀, 就是为了使用成员时不用写出中间的成员. 如果有多个相同类型的成员呢? 而且结构体的命名还会影响导出, 又是一个鸡肋的东西. 唯一的用处可以获得匿名成员的方法, 其实可以把匿名成员当成继承来用go
中函数是有类型的, c
也是, java
中函数只能放在class
中, 不能被赋值啥的go
中函数值的零值是nil
, 当调用nil
时会发生panic
, 同时函数值是不能比较的, 只能和nil
比较. 那是不是go
中每种类型都要记一下能不能比较呢, 感觉容易出现混乱. 现在不能比较的是slice
, map
, 函数java
的匿名类, 当访问外部变量这种用法, 还是有些高级, 不知到该用在什么地方, 不用这种高级用法, 用传参数可以吗go
中函数值也被称为闭包, 闭包是一种很底层的概念了吧go
程序设计语言举的例子都不简单, 真的垃圾go
中函数值使用外部的变量的时, 非常容易出现bug
, 使用所有值都是地址, 又是一个坑呀go
中Printf
是可变参数吗, 为什么它可以任意类型呢? 自己可变参数只能是同一类型的, Printf
用的是interface{}
类型, 这个类型有点像java
中的object
slice
来实现, 那多一种语法其实也没有什么必要, 可以看作是一种语法糖go
语言中defer
的作用跟finally
差不多, 但是使用的位置是不同的, 同时defer
调用的顺序是相反的. defer
有一个很独特的操作func ()()
, 它只会在函数结束后调用最后一个函数, 前面的函数会在使用defer
的地方被调用. 这就实现了, 使用前调用和使用后调用的结果, 很像aop
, defer
能实现的东西finally
也能实现, 但是defer
的写法更好看一些err
, 调用者遵守了规则, 但是实现者发现因为某种原因, 无法给出正确的结果. 说到底, 就是谁负责. go
中结构体的成员和方法共用一个命名空间, 就是成员和方法的名称不能相同. java
中成员方法应该是可以同名的吧go
中方法可以被声明到任意类型, 只要不是一个指针或者一个interface
, interface
不能有方法我能理解, 毕竟自己定义时已经写了, 为什么指针也不能有方法呢? 不明白, 也许设计如此吧go
中调用方法是不是有两种方式, 一种是对象.
语法, 另一种是函数类似的调用, 用第一参数作为对象, 应该是有两种的. 为什么有两种呢? 第一种是第二种的语法糖, 这是我认为的, 其实也是非常合理的go
语言中指针可以调用类型的方法, 类型也可以调用指针的方法前提是它是变量, 不是字面量, 那为什么地址字面量可以调用类型的方法呢? 到底什么是字面量啊), 这当中go
会存在隐式转换, 语法糖太多了, 让初学者很困惑, 垃圾. 如果指针和类型, 都声明了同名称方法, 这样会有什么后果, 或者这样是在同一个命名中的. 应该是在同一个命名空间中的吧. 验证出来, 是在同一个命名空间中的, 这样挺合理, 不然就太混乱了 go
语言允许类型的nil
来调用方法, 跟java
不一样, java
会报空指针异常的go
中结构体除了能内嵌类型, 还能内嵌指针, 内嵌指针有什么用呢? 其实没有什么区别, 除了利用指针可以共享同一个对象.go
中除了匿名函数, 还有匿名结构, 但是匿名结构没有地方也方法, 不像java
匿名类可以在块中写方法. 但是go
匿名结构中匿名成员能带来一些方法, 算是一种有效的弥补手段吧. 匿名函数和匿名结构, 一般什么时候会用呢? 一个函数或结构只会被用一次时, 可以懒得取方法, 还是非常有用的, 取名字可太麻烦了go
中接口是隐式实现的, java
中需要自己写明, 这两个有什么区别呢? 隐式可以让旧的类实现新实现的方, 但是java
可以用适配器模式来实现, 说到底, 其实也没有啥区别是吗? 就是稍微方便点LSP
是面向接口编程的实现的要求和规则go
中for
循环对于string
有特殊的操作, slice
和string
也有特殊操作, []byte("霄剑")
可以直接变成UTF8
编码的字节slice
go
中结构体和接口都有内嵌语法, 还有别的类型有这种语法吗? go
值接收器和指针接收器有很多不同的, 太坑了. 既然值接收器会生成一个指针接收器的方法, 那为啥用指针类型调用方法时, 还要编译器来解引用呢? 不是已经生成了指针接收器的方法了吗? 搞不懂go
语言中匿名结构体可以有相同名称的方法, 例如String
方法, 如果有两个匿名结构体都有String
方法, 编译会报错, 这个有点像多继承带来的问题go
语言结构体字面量可以取地址, c
语言不行, 我认为这应该又是go
语言的语法糖go
中接口是可以比较的, 但是接口的动态类型不能比较的话, 会报panic
, 还有一个问题, 接口比较有什么用呢? 这又是什么骚操作go
中实现排序需要实现三个方法, java
中只需要实现一个方法, c
语言中是怎么实现的呢? go
中类型断言和转型的语法很像. x.(T)
和T(x)
, 真的像. 类型断言中x
只能是接口, T
可以是接口或者类型. 当x
为nil
时, 会报panic
. 类型断言是反多态的, 在java
中是非常不值得推荐的. go
却非常推荐, 我觉得应该时不值得推荐的, 毕竟违反了多态,当然特例情况也是存在的, 具体类型有更好解决方案时, 可以使用断言java
中是不是把object
接收值再转成各种类型, 只能说很像, 这种做法面向对象不是不提倡吗? 也不是面向对象编程的使用接口的目的, 真是各种用法都有, 只要有用就行. 这种用法在go
语言中有一种语法来支持, 叫类型开关, x.(type)
, java
中有类似的语法吗? case
内部, 才会进行类型转换, 在其他情况下都是没有转换之前的值. 行为挺奇怪的KISS
原则和YANGI
原则pod
中使用宿主节点的Linux
命名空间部分pod
(特别是系统pod
)需要在宿主节点的默认命名空间中运行, 以允许它们看到和操作节点级别的资源和设备.
这可以通过配置pod
的spec.containers.ports
字段中某个容器某一个端口的hostPort
属性来实现.
如果一个pod
绑定了宿主节点上的一个特定端口, 每个宿主节点只能调度一个这样的pod
实例, 因为两个进程不能绑定宿主机上的同一个端口. 调度器在调度pod
时会考虑这一点, 所以它不会把这样的两个pod
调度到同一个节点上.
1 | spec: |
hostPort
功能最初是用于暴露通过DeamonSet
部署在每个节点上的系统服务的.
安全上下文中可配置的内容:
ID
)root
用于运行(容器的默认运行用户通常在其镜像中指定, 所以可能需要阻止容器以root
用户运行)SELinux
选项, 加强对容器的限制1 | spec: |
root
用户运行1 | spec: |
pod
1 | spec: |
相比于让容器运行在特权模式下以给予其无限的权限, 一个更加安全地做法是只给予它使用真正需要的内核功能的权限. Kubernetes
允许为特定的容器添加内核功能, 或禁止部分内核功能, 以允许对容器进行更加细致的权限控制, 限制攻击者潜在侵入的影响.
1 | spec: |
1 | spec: |
1 | spec: |
1 | spec: |
pod
使用安全相关的特性PodSecurityPolicy
资源介绍PodSecuriyPolicy
是一种集群级别(无命名空间)的资源, 它定义了用户能否在pod
中使用各种安全相关的特性. 维护PodSecurityPolicy
资源中配置策略的工作由集成在API
服务器中的PodSercurityPolicy
准入控制插件完成.
PodSecurityPolicy
可以做的事pod
使用宿主节点的PID
, IPC
, 网络命名空间pod
允许绑定的宿主节点的端口ID
pod
SELinux
选项pod
使用哪些类型的存储卷1 | apiVersion: extensions/v1beta1 |
runAsUser
, fsGroup
和supplementGroup
策略如果需要限制容器可以使用的用户和用户组ID
, 可以将规则改为MustRunAs
, 并指定允许使用的ID
范围.
1 | runAsUser: |
如果pod spec
试图将其中的任一字段设置为该范围之外的值, 这个pod
将不会被API
服务器接收.
ID
在指定范围之外的pod
PodSecurityPolicy
会将硬编码覆盖到镜像中的用户ID
.
runAsUser
字段中使用mustRunAsNonRoot
规则runAsUser
字段中还可以使用另一种规则: mustRunAsNonRoot
. 它将阻止用户部署以root
用户运行的容器. 在这种情况下, spec
容器中必须指定runAsUser
字段, 并且不能为0, 或者容器的镜像本身指定了一个用非0的用户ID
运行.
1 | apiVersion: extensions/v1beta1 |
PodSecurityPolicy
PodSecurityPolicy
是集群级别的资源, 这意味着它不能存储和应用在某一特定的命名空间上. 对不同用户分配不同PodSecurityPolicy
是通过RBAC
机制实现的. 这个方式是, 创建你需要的PodSecurityPolicy
资源, 然后创建ClusterRole
资源并通过名称将它们指向不同的策略, 以此使PodSecurityPolicy
资源中的策略对不同的用户或组生效. 通过ClusterRoleBinding
资源将特定的用户组绑定到ClusterRole
上, 当PodSecurityPolicy
访问控制插件需要决定是否接纳一个pod
时, 它只会考虑创建pod
的用户可以访问到的PodSecurityPolicy
中的策略.
pod
的网络一个NetworkPolicy
会应用在匹配它的标签选择器的pod
上, 指明这些允许访问这些pod
的源地址, 或这些pod
可以访问的目标地址. 这些分别由入向ingress
和出向egress
规则指定. 这两种规则都可以匹配由标签选择器选出的pod
, 或者一个namespace
中的所有pod
, 或者通过无类别域间路由(CIDR
)指定的IP
地址段.
1 | apiVersion: networking.k8s.io/v1 |
空的标签选择器匹配命名空间中的所有pod
.
pod
访问一个服务端pod
1 | apiVersion: networking.k8s.io/v1 |
Kubernetes
命名空间之间进行网络隔离1 | apiVersion: networking.k8s.io/v1 |
CIDR
隔离网络1 | ingress: |
pod
的对外访问流量1 | spec: |
ConfigMap
和Secret
Docker
中定义命令与参数容器中运行的完整指令由两部分组成, 命令与参数.
ENTRYPOINT
与CMD
ENTRYPOINT
定义容器启动时被调用的可执行程序.
CMD
指定传递给ENTRYPOINT
的参数.
shell
与exec
形式的区别shell
形式–如ENTRYPOINT node app.js
.
exec
形式– 如ENTRYPONT ["node", "app.js"]
.
两者的区别在于指定的命令是否在shell
中被调用, 确定PID
为1的进程是谁.
Kubernetes
中覆盖命令和参数在Kubernetes
中定义容器时, 镜像的ENTRYPOINT
和CMD
均可以被覆盖, 仅需要在容器定义中设置属性command
和args
的值.
1 | kind: Pod |
绝大多数情况下, 只需要设置自定义参数. 命令一般很少被覆盖, 除非针对一些未定义ENTRYPINT
的通用镜像, 例如busybox
.
command
和args
字段在pod
创建后无法被修改.
Kubernetes
允许为pod
中每一个容器都指定自定义的环境变量集合.
与容器的命令和参数设置相同, 环境变量列表无法在pod
创建后被修改.
1 | kind: Pod |
在每个容器中, Kubernetes
会自动暴露相同命名空间下每个service
对应的黄金变量. 这些环境变量基本上可以被看作自动注入的配置.
pod
定义硬编码意味着需要有效区分生产环境与开发过程中的pod
定义. 为了能在多个环境下复用pod
的定义, 需要将配置从pod
定义描述中解耦出来.
ConfigMap
解耦配置应用配置的关键在于能够在多个环境中区分配置选项, 将配置从应用程序源码中分离, 可频繁变更配置值.
ConfigMap
介绍Kubernetes
允许将配置选项分离到单独的资源对象ConfigMap
中, 本质上就是一个键/
值对映射, 值可以是短字面量, 也可以是完整的配置文件.
应用无须直接读取ConfigMap
, 甚至根本不需要知道其是否存在. 映射的内容通过环境变量或者卷文件的形式传递给容器, 而非直接传递给容器. 命令行参数的定义中可以通过$(ENV_VAR)
语法引用环境变量, 因而可以达到将ConfigMap
条目当作命令行参数传递给进程的效果.
当然, 应用程序同样可以通过Kubernetes Rest API
按需直接读取ConfigMap
的内容. 不过除非是需求如此, 应尽可能使你的应用保持对Kubernetes
的无感知.
ConfigMap
kubectl
创建ConfigMap
1 | kubectl create configmap <configMap的名字> --from-literal=<键>=<值> |
键只能包含数字, 字母, 破折号, 下画线以及圆点. --from-literal
参数用于指定多个条目.
1 | apiVersion: v1 |
ConfigMap
条目ConfigMap
同样可以存储粗粒度的配置数据, 比如完整的配置文件.
1 | kubectl create configmap <configmap的名字> --from-file=<配置文件名> |
运行上述命令时, kubectl
会在当前目录下查找配置文件, 并将文件内容存储在ConfigMap
中以配置文件名为键名的条目下. 也可以手动指定键名
1 | kubectl create configmap <configmap的名字> --from-file=<键>=<配置文件名> |
ConfigMap
1 | kubectl create configmap <configmap的名字> --from-file=/path/to/dir |
这种情况下, kubectl
会为文件夹中的每个文件单独创建条目, 仅限于那些文件名可作为合法ConfigMap
键名的文件.
ConfigMap
条目作为环境变量1 | apiVersion: v1 |
引用不存在ConfigMap
的容器会启动失败. 当之后创建了这个缺失的ConfigMap
, 失败的容器会自动启动, 无须重新创建pod
.
可以标记对ConfigMap
的引用时可选的(设置configMapKeyRef.optional: true
). 这样, 即便ConfigMap
不存在, 容器也能正常启动.
ConfigMap
的所有条目作为环境变量1 | spec: |
前缀设置是可选的, 若不设置前缀值, 环境变量的名称与ConfigMap
中的键名相同.
Configmap
条目作为命令行参数在字段pod.spec.containers.args
中无法直接引用ConfigMap
的条目, 但是可以利用ConfigMap
条目初始化某个环境变量, 然后再在参数字段中引用该环境变量.
ConfigMap
卷将条目暴露为文件环境变量或者命令行参数值作为配置值通常适用于变量值较短的场景.
configMap
卷会将ConfigMap
中每个条目均暴露成一个文件. 运行在容器中的进程可通过读取文件内容获得对应的条目值.
1 | volumes: |
ConfigMap
条目1 | volumes: |
假设拥有一个包含文件myconfig.conf
的configMap
卷, 希望能将奇添加为/etc
文件夹下的文件someconfig.conf
. 通过属性subPath
可以将该文件挂载的同时又不影响文件夹中的其他文件.
1 | spec: |
挂载任意一种卷时均可以使用subPath
属性. 可以选择挂载部分卷而不是挂载完整的卷. 捕获这种独立文件的挂载方式会带来文件更新上的缺陷.
configMap
卷中的文件设置权限configMap
卷中所有文件的权限默认被设置为644. 可以通过卷规格定义中的defaultMode
属性改变默认权限.
1 | volumes: |
使用环境变量或命令行参数作为配置源的弊端在于无法在进程运行时更新配置. 将ConfigMap
暴露为卷可以达到配置热更新的效果, 无须重新创建pod
或者重启容器.
ConfigMap
被更新之后, 卷中引用它的所有文件也会相应更新, 进程发现文件被改变之后进行重载(这要进程自己实现).
如果挂载的是容器中的单个文件而不是完整的卷, ConfigMap
更新之后对应的文件不会被更新.
如果现在你需要挂载单个文件并且在修改源ConfigMap
的同时会自动修改这个文件, 一种方案是挂载完整卷至不同的文件夹并创建指向文件的符号链接. 符号链接可以原生创建在容器镜像中, 也可以在容器启动时创建.
Secret
给容器传递敏感数据Secret
与ConfigMap
相似, 均是键/
值对的映射, 但用于保存一些敏感数据.
Secret
介绍每个pod
都会默认自动挂载上一个Secret
卷, 引用一个默认的Secret
.
Secret
采用Base64
编码的原因很简单. Secret
的条目可以涵盖二进制数据, 而不仅仅是纯文本. Base64
编码可以将二进制数据转换为纯文本, 以YAML
或JSON
格式展示.
Secret
甚至可以被用来存储非敏感二进制数据, Secret
的大小限于1MB
.
stringData
字段介绍由于并非所有敏感数据都是二进制形式, Kubernetes
允许通过Secret
的stringData
字段设置条目的纯文本值.
1 | apiVersion: v1 |
通过kubectl get -o yaml
查看Secret
时, stringData
会出现在data
字段.
pod
中读取Secret
条目通过secret
卷将Secret
暴露给容器之后, Secret
条目的值会被解码并以真实形式(纯文本或二进制)写入对应的文件. 通过环境变量暴露Secret
条目也是如此. 应用程序无序主动解码, 可直接读取文件内容或查找环境变量.
Secret
卷存储于内存Secret
卷采用内存文件系统列出容器的挂载点, 由于使用的是tmpfs
, 存储在Secret
中的数据不会写入磁盘, 这就无法被窃取.
Secret
条目1 | env: |
kubernetes
允许通过环境变量暴露Secret
, 然而此特性的使用往往不是一个好注意. 应用程序通常会在错误报告时转储环境变量, 或者是在启动时打印在应用日志中, 无意中暴露了Secret
信息.