張晉濤

張晉濤 查看完整檔案

北京編輯  |  填寫畢業院校網易有道  |  Engineer 編輯 moelove.info/ 編輯
編輯

微信公眾號:MoeLove
Container, Docker, Go, Kubernetes, Python, Vim;

為了準確篩選信息,可使用付費問答 http://www.tvxinternet.com/pay/...

個人動態

張晉濤 發布了文章 · 2月19日

從 Go 的二進制文件中獲取其依賴的模塊信息

大家好,我是張晉濤。

我們用 Go 構建的二進制文件中默認包含了很多有用的信息。例如,可以獲取構建用的 Go 版本:

(這里我使用我一直參與的一個開源項目 KIND 為例)

?  kind git:(master) ? go version ./bin/kind 
./bin/kind: go1.16

或者也可以獲取該二進制所依賴的模塊信息:

?  kind git:(master) ? go version -m ./bin/kind
./bin/kind: go1.16
        path    sigs.k8s.io/kind
        mod     sigs.k8s.io/kind        (devel)
        dep     github.com/BurntSushi/toml      v0.3.1
        dep     github.com/alessio/shellescape  v1.4.1
        dep     github.com/evanphx/json-patch/v5        v5.2.0
        dep     github.com/mattn/go-isatty      v0.0.12
        dep     github.com/pelletier/go-toml    v1.8.1  h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
        dep     github.com/pkg/errors   v0.9.1
        dep     github.com/spf13/cobra  v1.1.1
        dep     github.com/spf13/pflag  v1.0.5
        dep     golang.org/x/sys        v0.0.0-20210124154548-22da62e12c0c      h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
        dep     gopkg.in/yaml.v2        v2.2.8
        dep     gopkg.in/yaml.v3        v3.0.0-20210107192922-496545a6307b      h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
        dep     k8s.io/apimachinery     v0.20.2
        dep     sigs.k8s.io/yaml        v1.2.0

查看 KIND 代碼倉庫中的 go.mod文件,都包含在內了。

其實 Linux 系統中二進制文件包含額外的信息并非 Go 所特有的,下面我將具體介紹其內部原理和實現。當然,用 Go 構建的二進制文件仍是本文的主角。

Linux ELF 格式

ELF 是 Executable and Linkable Format 的縮寫,是一種用于可執行文件、目標文件、共享庫和核心轉儲(core dump)的標準文件格式。ELF 文件 通常 是編譯器之類的輸出,并且是二進制格式。以 Go 編譯出的可執行文件為例,我們使用 file 命令即可看到其具體類型 ELF 64-bit LSB executable

?  kind git:(master) ? file ./bin/kind 
./bin/kind: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped

本文中我們來具體看看 64 位可執行文件使用的 ELF 文件格式的結構和 Linux 內核源碼中對它的定義。

使用 ELF 文件格式的可執行文件是由 ELF 頭(ELF Header) 開始,后跟 程序頭(Program Header)節頭(Section Header) 或兩者均有組成的。

ELF 頭

ELF 頭始終位于文件的零偏移(zero offset)處(即:起點位置),同時在 ELF 頭中還定義了程序頭和節頭的偏移量。

我們可以通過 readelf 命令查看可執行文件的 ELF 頭,如下:

?  kind git:(master) ? readelf -h ./bin/kind 
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x46c460
  Start of program headers:          64 (bytes into file)
  Start of section headers:          400 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         6
  Size of section headers:           64 (bytes)
  Number of section headers:         15
  Section header string table index: 3

從上面的輸出我們可以看到,ELF 頭是以某個 Magic 開始的,此 Magic 標識了有關文件的信息,即:前四個 16 進制數,表示這是一個 ELF 文件。具體來說,將它們換算成其對應的 ASCII 碼即可:

45 = E

4c = L

46 = F

7f 是其前綴,當然,也可以直接在 Linux 內核源碼中拿到此處的具體定義:

// include/uapi/linux/elf.h#L340-L343
#define    ELFMAG0        0x7f        /* EI_MAG */
#define    ELFMAG1        'E'
#define    ELFMAG2        'L'
#define    ELFMAG3        'F'

接下來的數 02 是與 Class 字段相對應的,表示其體系結構,它可以是 32 位(=01) 或是 64 位(=02)的,此處顯示 02 表示是 64 位的,再有 readelf 將其轉換為 ELF64 進行展示。這里的取值同樣可以在 Linux 內核源碼中找到:

// include/uapi/linux/elf.h#L347-L349
#define    ELFCLASSNONE    0        /* EI_CLASS */
#define    ELFCLASS32    1
#define    ELFCLASS64    2

再后面的兩個 01 01 則是與 Data 字段和 Version 字段相對應的,Data 有兩個取值分別是 LSB(01)和 MSB(02),這里倒沒什么必要展開。另外就是 Version 當前只有一個取值,即 01 。

// include/uapi/linux/elf.h#L352-L358
#define ELFDATANONE    0        /* e_ident[EI_DATA] */
#define ELFDATA2LSB    1
#define ELFDATA2MSB    2

#define EV_NONE        0        /* e_version, EI_VERSION */
#define EV_CURRENT    1
#define EV_NUM        2

接下來需要注意的就是我前面提到的關于偏移量的內容,即輸出中的以下內容:

  Start of program headers:          64 (bytes into file)
  Start of section headers:          400 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         6
  Size of section headers:           64 (bytes)
  Number of section headers:         15

ELF 頭總是在起點,在此例中接下來是程序頭(Program Header),隨后是節頭(Section Header),這里的輸出顯示程序頭是從 64 開始的,所以節頭的位置就是:

64 + 56 * 6 = 400

與上述輸出符合,同理,節頭的結束位置是:

400 + 15 * 64 = 1360

下一節內容中將用到這部分知識。

程序頭

通過 readelf -l 可以看到其程序頭,包含了若干段(Segment),內核看到這些段時,將調用 mmap syscall 來使用它們映射到虛擬地址空間。這部分不是本文的重點,我們暫且跳過有個印象即可。

?  kind git:(master) ? readelf -l ./bin/kind 

Elf file type is EXEC (Executable file)
Entry point 0x46c460
There are 6 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
                 0x0000000000000150 0x0000000000000150  R      0x1000
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x0000000000333a75 0x0000000000333a75  R E    0x1000
  LOAD           0x0000000000334000 0x0000000000734000 0x0000000000734000
                 0x00000000002b3be8 0x00000000002b3be8  R      0x1000
  LOAD           0x00000000005e8000 0x00000000009e8000 0x00000000009e8000
                 0x0000000000020ac0 0x00000000000552d0  RW     0x1000
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x8
  LOOS+0x5041580 0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000         0x8

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .text 
   02     .rodata .typelink .itablink .gosymtab .gopclntab 
   03     .go.buildinfo .noptrdata .data .bss .noptrbss 
   04     
   05     

節頭

使用 readelf -S 即可查看其節頭,其結構如下:

// include/uapi/linux/elf.h#L317-L328
typedef struct elf64_shdr {
  Elf64_Word sh_name;        /* Section name, index in string tbl */
  Elf64_Word sh_type;        /* Type of section */
  Elf64_Xword sh_flags;        /* Miscellaneous section attributes */
  Elf64_Addr sh_addr;        /* Section virtual addr at execution */
  Elf64_Off sh_offset;        /* Section file offset */
  Elf64_Xword sh_size;        /* Size of section in bytes */
  Elf64_Word sh_link;        /* Index of another section */
  Elf64_Word sh_info;        /* Additional section information */
  Elf64_Xword sh_addralign;    /* Section alignment */
  Elf64_Xword sh_entsize;    /* Entry size if section holds table */
} Elf64_Shdr;

對照實際的命令輸出,含義就很明顯了。

?  kind git:(master) ? readelf -S ./bin/kind 
There are 15 section headers, starting at offset 0x190:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000401000  00001000
       0000000000332a75  0000000000000000  AX       0     0     32
  [ 2] .rodata           PROGBITS         0000000000734000  00334000
       000000000011f157  0000000000000000   A       0     0     32
  [ 3] .shstrtab         STRTAB           0000000000000000  00453160
       00000000000000a4  0000000000000000           0     0     1
  [ 4] .typelink         PROGBITS         0000000000853220  00453220
       00000000000022a0  0000000000000000   A       0     0     32
  [ 5] .itablink         PROGBITS         00000000008554c0  004554c0
       0000000000000978  0000000000000000   A       0     0     32
  [ 6] .gosymtab         PROGBITS         0000000000855e38  00455e38
       0000000000000000  0000000000000000   A       0     0     1
  [ 7] .gopclntab        PROGBITS         0000000000855e40  00455e40
       0000000000191da8  0000000000000000   A       0     0     32
  [ 8] .go.buildinfo     PROGBITS         00000000009e8000  005e8000
       0000000000000020  0000000000000000  WA       0     0     16
  [ 9] .noptrdata        PROGBITS         00000000009e8020  005e8020
       0000000000017240  0000000000000000  WA       0     0     32
  [10] .data             PROGBITS         00000000009ff260  005ff260
       0000000000009850  0000000000000000  WA       0     0     32
  [11] .bss              NOBITS           0000000000a08ac0  00608ac0
       000000000002f170  0000000000000000  WA       0     0     32
  [12] .noptrbss         NOBITS           0000000000a37c40  00637c40
       0000000000005690  0000000000000000  WA       0     0     32
  [13] .symtab           SYMTAB           0000000000000000  00609000
       0000000000030a20  0000000000000018          14   208     8
  [14] .strtab           STRTAB           0000000000000000  00639a20
       000000000004178d  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

Go 二進制文件探秘

本文中,我們重點關注名為 .go.buildinfo 的部分。 使用 objdump 查看其具體內容:

?  kind git:(master) ? objdump -s -j .go.buildinfo ./bin/kind

./bin/kind:     file format elf64-x86-64

Contents of section .go.buildinfo:
 9e8000 ff20476f 20627569 6c64696e 663a0800  . Go buildinf:..
 9e8010 a0fc9f00 00000000 e0fc9f00 00000000  ................

這里我們按順序來,先看到第一行的 16 個字節。

image

  • 前 14 個字節是魔術字節,必須為 \xff Go buildinf: ;
  • 第 15 字節表示其指針大小,這里的值為 0x08,表示 8 個字節;
  • 第 16 字節用于判斷字節序是大端模式還是小端模式,非 0 為大端模式,0 為小端模式。

我們繼續看第 17 字節開始的內容。

Go 版本信息

前面我們也看到了當前使用的字節序是小端模式,這里的地址應該是 0x009ffca0 。

我們來取出 16 字節的內容:

?  kind git:(master) ? objdump -s --start-address 0x009ffca0 --stop-address 0x009ffcb0 ./bin/kind   

./bin/kind:     file format elf64-x86-64

Contents of section .data:
 9ffca0 f5027d00 00000000 06000000 00000000  ..}.............

這里前面的 8 個字節是 Go 版本的信息,后 8 個字節是版本所占的大?。ㄟ@里表示占 6 個字節)。

?  kind git:(master) ? objdump -s --start-address  0x007d02f5 --stop-address 0x007d02fb ./bin/kind

./bin/kind:     file format elf64-x86-64

Contents of section .rodata:
 7d02f5 676f31 2e3136                        go1.16

所以,如上所示,我們拿到了構建此二進制文件所用的 Go 版本的信息,是用 Go 1.16 進行構建的。

Go Module 信息

前面我們使用了 17~24 字節的信息,這次我們繼續往后使用。

?  kind git:(master) ? objdump -s --start-address  0x009ffce0 --stop-address 0x009ffcf0 ./bin/kind       

./bin/kind:     file format elf64-x86-64

Contents of section .data:
 9ffce0 5a567e00 00000000 e6020000 00000000  ZV~.............

與前面獲取 Go 版本信息時相同,前 8 個字節是指針,后 8 個字節是其大小。也就是說從 0x007e565a 開始,大小為 0x000002e6 ,所以我們可以拿到以下內容:

?  kind git:(master) ? objdump -s --start-address  0x007e565a --stop-address 0x7e5940 ./bin/kind

./bin/kind:     file format elf64-x86-64

Contents of section .rodata:
 7e565a 3077 af0c9274 080241e1 c107e6d6 18e6 0w...t..A.......
 7e566a 7061 74680973 6967732e 6b38732e 696f path.sigs.k8s.io
 7e567a 2f6b 696e640a 6d6f6409 73696773 2e6b /kind.mod.sigs.k
 7e568a 3873 2e696f2f 6b696e64 09286465 7665 8s.io/kind.(deve
 7e569a 6c29 090a6465 70096769 74687562 2e63 l)..dep.github.c
 7e56aa 6f6d 2f427572 6e745375 7368692f 746f om/BurntSushi/to
 7e56ba 6d6c 0976302e 332e3109 0a646570 0967 ml.v0.3.1..dep.g
 7e56ca 6974 6875622e 636f6d2f 616c6573 7369 ithub.com/alessi
 7e56da 6f2f 7368656c 6c657363 61706509 7631 o/shellescape.v1
 7e56ea 2e34 2e31090a 64657009 67697468 7562 .4.1..dep.github
 7e56fa 2e63 6f6d2f65 76616e70 68782f6a 736f .com/evanphx/jso
 7e570a 6e2d 70617463 682f7635 0976352e 322e n-patch/v5.v5.2.
 7e571a 3009 0a646570 09676974 6875622e 636f 0..dep.github.co
 7e572a 6d2f 6d617474 6e2f676f 2d697361 7474 m/mattn/go-isatt
 7e573a 7909 76302e30 2e313209 0a646570 0967 y.v0.0.12..dep.g
 7e574a 6974 6875622e 636f6d2f 70656c6c 6574 ithub.com/pellet
 7e575a 6965 722f676f 2d746f6d 6c097631 2e38 ier/go-toml.v1.8
 7e576a 2e31 0968313a 314e6638 336f7270 726b .1.h1:1Nf83orprk
 7e577a 4a79 6b6e5436 68377a62 75454755 456a JyknT6h7zbuEGUEj
 7e578a 6379 566c4378 53554754 454e6d4e 4352 cyVlCxSUGTENmNCR
 7e579a 4d3d 0a646570 09676974 6875622e 636f M=.dep.github.co
 7e57aa 6d2f 706b672f 6572726f 72730976 302e m/pkg/errors.v0.
 7e57ba 392e 31090a64 65700967 69746875 622e 9.1..dep.github.
 7e57ca 636f 6d2f7370 6631332f 636f6272 6109 com/spf13/cobra.
 7e57da 7631 2e312e31 090a6465 70096769 7468 v1.1.1..dep.gith
 7e57ea 7562 2e636f6d 2f737066 31332f70 666c ub.com/spf13/pfl
 7e57fa 6167 0976312e 302e3509 0a646570 0967 ag.v1.0.5..dep.g
 7e580a 6f6c 616e672e 6f72672f 782f7379 7309 olang.org/x/sys.
 7e581a 7630 2e302e30 2d323032 31303132 3431 v0.0.0-202101241
 7e582a 3534 3534382d 32326461 36326531 3263 54548-22da62e12c
 7e583a 3063 0968313a 56777967 55726e77 396a 0c.h1:VwygUrnw9j
 7e584a 6e38 38633475 38474433 725a5162 7172 n88c4u8GD3rZQbqr
 7e585a 502f 74676173 38387450 55624278 5172 P/tgas88tPUbBxQr
 7e586a 6b3d 0a646570 09676f70 6b672e69 6e2f k=.dep.gopkg.in/
 7e587a 7961 6d6c2e76 32097632 2e322e38 090a yaml.v2.v2.2.8..
 7e588a 6465 7009676f 706b672e 696e2f79 616d dep.gopkg.in/yam
 7e589a 6c2e 76330976 332e302e 302d3230 3231 l.v3.v3.0.0-2021
 7e58aa 3031 30373139 32393232 2d343936 3534 0107192922-49654
 7e58ba 3561 36333037 62096831 3a683871 446f 5a6307b.h1:h8qDo
 7e58ca 7461 4550754a 4154724d 6d573034 4e43 taEPuJATrMmW04NC
 7e58da 7767 37763232 61484832 38777770 6175 wg7v22aHH28wwpau
 7e58ea 5568 4b394f6f 3d0a6465 70096b38 732e UhK9Oo=.dep.k8s.
 7e58fa 696f 2f617069 6d616368 696e6572 7909 io/apimachinery.
 7e590a 7630 2e32302e 32090a64 65700973 6967 v0.20.2..dep.sig
 7e591a 732e 6b38732e 696f2f79 616d6c09 7631 s.k8s.io/yaml.v1
 7e592a 2e32 2e30090a f9324331 86182072 0082 .2.0...2C1.. r..
 7e593a 4210 4116d8f2                        B.A...          

我們成功的拿到了其所依賴的 Modules 相關的信息,
這與我們在文章開頭執行 go version -m ./bin/kind 是可以匹配上的,只不過這里的內容相當于是做了序列化。

具體實現

在前面的內容中,關于如何使用 readelf 和 objdump 命令獲取二進制文件的的 Go 版本和 Module 信息就已經涉及到了其具體的原理。這里我來介紹下 Go 代碼的實現。

節頭的名稱是硬編碼在代碼中的

//src/cmd/go/internal/version/exe.go#L106-L110
    for _, s := range x.f.Sections {
        if s.Name == ".go.buildinfo" {
            return s.Addr
        }
    }

同時,魔術字節也是通過如下定義:

var buildInfoMagic = []byte("\xff Go buildinf:")

獲取 Version 和 Module 相關信息的邏輯如下,在前面的內容中也已經基本介紹過了,這里需要注意的也就是字節序相關的部分了。

    ptrSize := int(data[14])
    bigEndian := data[15] != 0
    var bo binary.ByteOrder
    if bigEndian {
        bo = binary.BigEndian
    } else {
        bo = binary.LittleEndian
    }
    var readPtr func([]byte) uint64
    if ptrSize == 4 {
        readPtr = func(b []byte) uint64 { return uint64(bo.Uint32(b)) }
    } else {
        readPtr = bo.Uint64
    }
    vers = readString(x, ptrSize, readPtr, readPtr(data[16:]))
    if vers == "" {
        return
    }
    mod = readString(x, ptrSize, readPtr, readPtr(data[16+ptrSize:]))
    if len(mod) >= 33 && mod[len(mod)-17] == '\n' {
        // Strip module framing.
        mod = mod[16 : len(mod)-16]
    } else {
        mod = ""
    }

總結

我在這篇文章中分享了如何從 Go 的二進制文件中獲取構建它時所用的 Go 版本及它依賴的模塊信息。如果對原理不感興趣的話,直接通過 go version -m 二進制文件 即可獲取相關的信息。

具體實現還是依賴于 ELF 文件格式中的相關信息,同時也介紹了 readelf 和 objdump 工具的基本使用,ELF 格式除了本文介紹的這種場景外,還有很多有趣的場景可用,比如為了安全進行逆向之類的。

另外,你可能會好奇從 Go 的二進制文件獲取這些信息有什么作用。最直接的來說,可以用于安全漏洞掃描,比如檢查其依賴項是否有安全漏洞;或是可以對依賴進行分析(主要指:接觸不到源代碼的場景下)會比較有用。


歡迎訂閱我的文章公眾號【MoeLove】

TheMoeLove

查看原文

贊 6 收藏 3 評論 2

張晉濤 發布了文章 · 1月7日

2020 小回顧 | 新晉程序員奶爸的云原生之路

一眨眼已經 2021 年了,雖然我每年也都會慣例的做個小回顧,但 2020 年對我意義更加不同。我打算換個方式來聊。

生活

很多人說 2020 年過于魔幻,這一年確實發生了很多事情,疫情、山火、蝗災、洪水等。

而這一年對我來說意義更加非凡。正如我在去年的總結文分享的那樣,我是在 2019 年和我的小可愛結婚的,2020 年我倆的寶寶出生啦!

我現在對那天仍記憶猶新。那天晚上我總覺得好像需要做點什么,但其實那種感覺也描述不出來。最終我是打開電腦看了幾遍孕婦學校老師給的關于臨產前準備工作視頻后睡覺的。當時小可愛還笑我是不是太緊張了。凌晨時我們一起去了醫院,經過小可愛的一番努力,從此我也就增加了新的角色, 成為了一名奶爸,謝謝我的小可愛!

有了孩子之后,生活自然也就不像之前了,很多時候要考慮孩子的情況,比如半夜需要哄他,每天為他洗澡之類的。等稍微大點之后好了不少,也變得更加有趣了。周末會帶他去早教中心,其實主要就是帶他去玩,能看見其他小朋友,跟其他小朋友打招呼之類的,每次他都很開心。

在作為“奶爸”這個角色上,我沒有經驗,也還在學習和摸索中。(很早以前我和幾個好朋友一起聊過,是不是大家應該組群一起聊聊奶爸心得之類的,交流下經驗~ 哈哈哈)

正如我 7 月份在接受思否社區訪談時說的那樣, 思否有約丨張晉濤:一直在學習,包括更好的工作和如何成為合格的父親。對于后者,這里我就不聊太多了,剛剛起步,以后的路還長著呢~

個人學習和成長

接下來就聊聊個人學習和成長相關的部分吧。

今年的主要研究方向仍然專注于容器運行時,Kubernetes 以及 Prometheus 等云原生相關的技術。

在容器運行時方面參與開源社區最多,Docker v20.10 也是我花時間精力最多的一個版本, 關于 Docker v20.10 相關的介紹請參考我之前的文章。在此過程中,我對 containerd, runc,firecracker 等技術也都在源碼層有了更深的理解以及做了一些相關實踐和嘗試。

對于 Kubernetes 來說,今年代碼提交方面表現一般。多數時間花在了理清其某些功能的設計及演進過程等方面,積累了一些奇奇怪怪的知識,如果大家感興趣的話,以后我可以考慮寫一些文章來聊聊,以及對 Kubernetes 一些周邊技術進行了探索和實踐。

至于 Prometheus 的話,有幾篇規劃中的文章沒來得及寫。反倒是給 Prometheus云原生監控: 運維與開發實戰 這本書寫了個推薦語。
同時 Prometheus 在我司也正逐步落地推進,但受資源和人力等因素的限制,遠沒達到我預期的效果,還有很長的路要走。
2021 年我計劃要把它整體架構及周邊基礎設施完善下,希望能邁入一個新的階段。

除此之外,還有一些值得記錄的內容。

2020 年年初,更新完了我在 GitChat 上的專欄 Docker 核心知識必知必會 ,按照 GitChat 上的字數統計,這個專欄有將近 10w 字,涉及到了 Docker 原理的方方面面。從開始策劃到全部更新完成,這期間一共換了 3 位編輯(前 2 位離職了),耗時半年,感謝各位編輯和讀者們的支持!感謝我的小可愛催稿!讓我終于能按時完稿。
更新專欄的那些天,要么是寫到凌晨,要么是凌晨起床就開始寫,感覺還是蠻辛苦的。但完稿之后,回頭來看,好像也還好。果然一個人的上限是需要逼自己一下的。

此外,自 2019 年 3 月份開始,我也一直在更新著 「K8S 生態周報」 在 2020 年推送了 44 篇,中間有幾篇斷更,感謝各位新老讀者朋友們的支持!未來會繼續保持更新。

我的小可愛在 2020 年也開始了視頻版的輸出。小可愛比我要認真,視頻版比文字版要詳細的多,包括具體的操作演示之類的。此外,視頻中也會有些彩蛋,喜歡看視頻的小伙伴可以來一波關注~

除了「K8S 生態周報」外,2020 年我一共寫了 15 篇博客,有個別幾篇未公開,今年修改潤色后會公開發布,此外也把博客重新整理了下,換了個主題,清爽了一些,歡迎大家訪問 https://moelove.info/

image

2020 年另外的幾件事情:

  • 收到了公司第一份非合同制的聘書 TM599

網易TM599聘書

上半年其實還有一個 RedHat 的 Open TestCon ,不過受疫情影響取消了。下半年基本保持每月 1 次的節奏。根據主辦方數據和參會者的反饋來看,效果還不錯,感謝大家的支持!這些分享的 PPT ,可以直接在我的 GitHub 倉庫中下載。

這幾次分享,大家其實也可以看到,每次都是不一樣的,有各自不同的側重,包括容器化/Kubernetes,容器運行時,eBPF 等技術。 PS: 我堅信,近五年 eBPF 技術會有更大規模的生產實踐,它也是一個很好的突破口。 除 Cilium 外,我也計劃之后分享更多好玩和實用的 eBPF 實踐,當然,我也計劃在我司的環境中,做一些其他的嘗試。

既然是年度的總結和回顧,我也來聊一些不那么滿意和做的不好的地方吧。

就我個人而言,2020 年上半年有過一小段時間的迷茫,或者說“注意力分散”。各類技術層出不窮,更新也很頻繁,唯有持續學習和跟進。關注太多東西,雖然能讓自己保持敏銳的判斷 & 更輕松的解決問題,然而個人精力有限,持續這樣會分散很多注意力。

所以我主動的屏蔽掉了一些信息 & 對相關內容做了分級,將自己的注意力更好的集中在幾個主要的領域內,其他內容周期性的回顧下,也未嘗不可。這也就是我在開頭提到的主要研究方向。

另一方面則是對團隊/系統相關的,當前面臨較大的問題是信息孤島(information island),雖說信息孤島是技術產業發展中不可避免的一個問題,但現在可能出現的早了點,也嚴重了點。導致了很多孤立的系統 & 信息閉塞。希望明年通過平臺建設能改善一些這種問題。

flag

2019 年,在 GitChat 的訪談中 讓我聊 2020 年容器的技術趨勢。我當時的主要觀點如下:

作為云原生技術的基石,Kubernetes 在 2020 年的熱度將會持續上升。而各個公司的集群規模,以及對容器技術的推進都將會持續加大。在經歷了初步容器化后,更多的公司將面臨的問題是穩定性和性能優化問題。與此同時,service mesh,serverless 等技術也都會逐步得到普遍應用。
從底層次技術的角度來看,cgroups v2 將逐步普及,進而取代 cgroups v1,但這個過程可能需要兩三年左右。
整體而言,穩定性和性能優化將會是未來的主旋律。

回過頭來看,確實 2020 年 Kubernetes 熱度是在持續上升。關于穩定性方面,看看 Chaos Engineering 去年在各公司 Kubernetes 中的相關實踐,基本也得到印證。

至于性能優化,無論是阿里,騰訊,網易等各家也都對外公開分享了基于 eBPF 等技術的相關優化和實踐,這點也沒問題。

cgroups v2 2020 年無論是 runc, containerd, Docker 等均已經增加了相關的支持,也基本得到了印證。

唯一沒有提到的,可能是安全性相關的部分。 在 2020 年,Kubernetes/容器化技術方面的安全性,也進一步得到了重視。(這和各種前期沒有太在意的安全漏洞被發現也有很大關系)。

再來看看年初立的 flag 吧:

  • 工作方面希望能推動更多業務的改造和接入,算是個長線計劃;
  • 社區活動方面,今年希望多參與一些,把一些理念和實踐經驗 push 到社區,也能從中學習到社區的一些經驗;
  • 社區貢獻方面,會繼續投入更多精力來做,但與 2019 年的重點可能會稍有不同;
  • 生活方面,希望寶寶健康的出生,和我的小可愛共同經營好我們的家庭。

基本符合預期。工作方面業務方和同事們都比較配合,推進也較為順利。社區活動方面,謝謝每個參與者!生活方面,謝謝我的小可愛!感謝每個家人!

慣例貼一張圖:

image

至于 2021 年的小目標,這次就不寫了。愿:平安喜樂!

還有文章前的你,感謝你的關注和支持,希望我們能各有收獲!


歡迎訂閱我的文章公眾號【MoeLove】

本文參與了 SegmentFault 思否征文「2020 總結」,歡迎正在閱讀的你也加入。
查看原文

贊 4 收藏 0 評論 0

張晉濤 發布了文章 · 1月1日

K8S 生態周報| 年終大放送!Docker v20.10

「K8S 生態周報」內容主要包含我所接觸到的 K8S 生態相關的每周值得推薦的一些信息。歡迎訂閱知乎專欄「k8s生態」。

Docker v20.10 主要特性一覽

在之前的 K8S 生態周報| Docker v20.10.0-beta1 發布 一文中,我曾為你介紹過 Docker v20.10.0-beta1 發布相關的信息,但是并沒有具體介紹 Docker v20.10 版本的具體功能特性等細節。

Docker v20.10 版本,變化非常的大。提供了 CGroup v2 的支持,增強了 rootless 模式的支持,雙棧日志,更靈活的內置 DNS 等,我在這個版本中也花費了很多時間。

詳細的變更,我會在 v20.10 正式發布后再進行介紹。歡迎大家進行測試和反饋,目前已經收到了一些反饋的建議。我們會盡快修正并發布下個版本。

正如我當時承諾的那樣,作為 2020 年最后一篇周報,我來在本篇周報中詳細介紹下 Docker v20.10 版本。

安裝

Docker v20.10 發布于 2020 年 12 月 8 日,是自 Docker v19.03 (2019 年 7 月)后發布的首個大版本。你可以直接通過以下命令來自動化的安裝 Docker v20.10。

?  ~ curl -fsSL https://get.docker.com |sh

cgroup v2 支持

Docker v19.03 是沒有 cgroup v2 支持的,但自從 Fedora 31 開始,這成為了 Fedora 上的默認 cgroup 版本。在 Docker v20.10 發布之前,Fedora 31 及以上用戶,需要將系統的 cgroup 設置為 v1 才能正常運行 Docker 。

其實這里面涉及到了大量的修改,包括 runc, containerd 直到 Docker 都添加了 cgroup v2 的支持,此功能才能真正給用戶使用。

在此過程中,我也發現了 runc v1.0-rc91 中隱藏的 bug ,會導致 Docker 無法正常通過增加 --privileged 參數以特權模式運行容器。后來對 runc 進行了修改,發布了 runc v1.0-rc92 ,至此才使得 Docker 中的 cgroup v2 特性得以順利完成。

關于此處更詳細的內容,我在 《K8S 生態周報| runc v1.0-rc92 發布》 中已做了詳細介紹,感興趣的小伙伴可以看看。

rootless mode GA

Docker 有個一直被人詬病的點,就在于 docker daemon 必須是以 root 權限來啟動,這樣才可以使用它的全部特性。這也就意味著,凡是可以操作 docker daemon 的用戶,也就有機會獲取操作系統的 root 權限。

我們在 Docker v19.03 版本中提供了實驗性的 rootless mode 的支持,允許用戶無需 root 權限即可運行 docker daemon 。這大大提升了系統的安全性。但當時還處于實驗階段,有部分功能缺失。

我想趁此次 Docker v20.10 發布,正式為你介紹下 Docker 的 rootless mode ,并且,它已經達到 GA 從實驗性畢業了,并且也提供了很多功能。

rootless mode 是在用戶名稱空間中運行 docker daemon 和容器,完全不需要 root 權限。

你可以通過以下命令安裝 Docker rootless 模式:

?  ~ curl -fsSL https://get.docker.com/rootless | sh

或者直接通過 Docker 官方的 RPM/DEB 包進行安裝。安裝完之后,通過 systemctl 管理 Docker 服務即可。

?  ~ systemctl --user start docker

更多關于 rootless mode 的詳細用法及最佳實踐,請參考 Docker 官方文檔:以非 root 用戶運行 docker daemon

Dockerfile: RUN --mount=type=(ssh|secret|cache) 等特性達到 GA

Docker v18.06 開始實驗性的添加了 RUN --mount=type=cache 該特性可用于在構建過程中,保留包管理器的緩存文件。

之后在 v18.09 中又增加了 RUN --mount=type=sshRUN --mount=type=secret 用于在構建過程中,傳遞密鑰或者用戶憑證等私密信息,以防止泄漏等。這些高級特性,在我之前的文章 《進階:Dockerfile 高階使用指南及鏡像優化》 曾詳細介紹過。

這些特性非常有用,基本涵蓋了你構建鏡像時關于安全性方面的絕大多數的需求。大家如果感興趣,歡迎隨時與我交流,后續我也可能會再寫相關的文章進行介紹。

其他

  • 此次還優化了內置 DNS 相關的邏輯,可以提供更好的性能;
  • docker build 可支持直接 ssh 遠程構建私有倉庫的鏡像了;

更多關于此版本的變更,可以參考其 ReleaseNote

Rook v1.5.4 發布

關于此版本有兩個格外需要注意的信息:

  • #6849 最新版的 Ceph 不支持使用分區的 OSD 了。就我個人經驗,我倒是基本沒有把 OSD 裝到過某個分區中,一般都直接使用整塊盤;
  • #6769 Ceph-CSI 默認更新到了 v3.2.0;

更多關于此版本的信息,請參考其 ReleaseNote 。

etcd 從 CNCF 正式畢業

etcd 想必大家不會陌生,我在「K8S 生態周報」中也曾多次介紹它,這里就不再贅述了。

感興趣的小伙伴可查看官方公告: CNCF 宣布 etcd 正式畢業

再次恭喜 etcd !

題外話

今年是 2020 年陽歷的最后一天了, 感謝大家的關注和支持!

接下來,我將會發布一份 “2020 年 K8S 生態演進報告” 與你分享在 2020 年 K8S 生態相關不容錯誤的信息,以及 2021 年 K8S 生態相關的展望及發展方向,敬請期待!


歡迎訂閱我的文章公眾號【MoeLove】

TheMoeLove

查看原文

贊 3 收藏 1 評論 0

張晉濤 發布了文章 · 2020-12-24

Docker 新發布的 hub-tool 可直接查看賬戶配額

Docker Desktop v3.0 已于前兩周正式發布,從這個版本起,Docker 官方承諾每次的更新將以增量更新的方式來提供,以便減少下載包的體積,提升效率。

除了將 Docker Engine 更新至 v20.10.0 外,也對其他的依賴做了更新,如下圖:

image

最吸引我的是本次新增的 Docker Hub Tool v0.2.0 ,它是 Docker 官方提供的 Docker Hub CLI 工具,具備管理 DockerHub 上的帳號,鏡像等相關資源的能力。

以下,我來為你介紹下 Hub Tool 的主要功能。

(MoeLove) ?  hub-tool -h
A tool to manage your Docker Hub images

Usage:
  hub-tool
  hub-tool [command]

Available Commands:
  account     Manage your account
  help        Help about any command
  login       Login to the Hub
  logout      Logout of the Hub
  org         Manage organizations
  repo        Manage repositories
  tag         Manage tags
  token       Manage Personal Access Tokens
  version     Version information about this tool

Flags:
  -h, --help      help for hub-tool
      --verbose   Print logs
      --version   Display the version of this tool

Use "hub-tool [command] --help" for more information about a command.

從一級菜單來看,主要功能包括:

  • 登錄/登出 DockerHub;
  • 賬戶相關管理功能;
  • 組織相關管理功能;
  • 倉庫和 tag 的相關管理功能;
  • token 的相關管理功能;

當前我使用的是最新版本 v0.2.0 。

(MoeLove) ?  hub-tool version
Version:    v0.2.0
Git commit: 0edf43ac9091e7cac892cbc4cbc6efbafb665aa4

登錄/退出

登錄/退出只要執行 hub-tool login 或者 hub-tool logout 即可。

但這里需要注意的是 Hub Tool 并沒有使用 Docker Desktop 默認的用戶憑證,也就是說,即使你在 Docker Desktop 中已經登錄了帳號,你同樣還是需要再次在終端下執行 login 操作。

關于為何沒有共用用戶憑證的問題,我跟 Docker Inc. 的產品經理聊過,是因為當前 Hub Tool 還是一個獨立的 CLI 工具,并沒有與 docker CLI 進行集成,也暫時沒想好要如何集成。等真正要集成進 docker CLI 的時候,就會直接共用用戶憑證了。

(MoeLove) ?  hub-tool login
Username: moelove
Password:

賬戶管理

賬戶管理的兩個功能:

  • 查看賬戶信息;
  • 查看當前賬戶下的流量限制 , 這是我個人覺得比較有用的一個功能;
(MoeLove) ?  hub-tool account
Manage your account

Usage:
  hub-tool account
  hub-tool account [command]

Available Commands:
  info          Print the account information
  rate-limiting Print the rate limiting information

Flags:
  -h, --help   help for account

Global Flags:
      --verbose   Print logs

Use "hub-tool account [command] --help" for more information about a command.
(MoeLove) ?  hub-tool account rate-limiting
Limit:     200, 6 hours window
Remaining: 200, 6 hours window
(MoeLove) ?  hub-tool account info
Username:    moelove.info
Full name:    Jintao Zhang
Company:
Location:
Joined:        6 years ago
Plan:        free
Limits:
  Seats:        1
  Private repositories:    1
  Parallel builds:    1
  Collaborators:    unlimited
  Teams:        unlimited

組織管理

可以看到,hub-tool org 的功能就是展示一些相關信息了。

(MoeLove) ?  hub-tool org
Manage organizations

Usage:
  hub-tool org
  hub-tool org [command]

Available Commands:
  ls          List all the organizations
  members     List all the members in an organization
  teams       List all the teams in an organization

Flags:
  -h, --help   help for org

Global Flags:
      --verbose   Print logs

Use "hub-tool org [command] --help" for more information about a command.
(MoeLove) ?  hub-tool org ls
NAMESPACE    NAME    MY ROLE    TEAMS    MEMBERS

倉庫和 tag 管理

由于這兩個都和鏡像有直接的關系,我就聚合到一起介紹了。

  • 對 repo 的查詢和刪除功能:
(MoeLove) ?  hub-tool repo -h
Manage repositories

Usage:
  hub-tool repo
  hub-tool repo [command]

Available Commands:
  ls          List all the repositories from your account or an organization
  rm          Delete a repository

Flags:
  -h, --help   help for repo

Global Flags:
      --verbose   Print logs

Use "hub-tool repo [command] --help" for more information about a command.
(MoeLove) ?  hub-tool repo ls
REPOSITORY                             DESCRIPTION       LAST UPDATE      PULLS    STARS    PRIVATE
taobeier/saythx-work                                     2 years ago      56989    0        false
...
(MoeLove) ?  hub-tool repo ls -h
List all the repositories from your account or an organization

Usage:
  hub-tool repo ls [ORGANIZATION]

Aliases:
  ls, list

Flags:
      --all             Fetch all available repositories
      --format string   Print values using a custom format ("json")
  -h, --help            help for ls

Global Flags:
      --verbose   Print logs
  • 對 tag 的列表,查詢,查看詳細等功能。 這里 可以看到 tag 最近一次的 Push/Pull 操作,如果明年 Docker 開始實行鏡像保留策略的話,我建議你關注一下 ;
(MoeLove) ?  hub-tool tag
Manage tags

Usage:
  hub-tool tag [flags]
  hub-tool tag [command]

Available Commands:
  inspect     Show the details of an image in the registry
  ls          List all the images in a repository
  rm          Delete a tag in a repository

Flags:
  -h, --help   help for tag

Global Flags:
      --verbose   Print logs

Use "hub-tool tag [command] --help" for more information about a command.
See 'hub-tool tag ls --help'.

Usage:  hub-tool tag ls [OPTION] REPOSITORY

List all the images in a repository
(MoeLove) ?  hub-tool tag ls taobeier/saythx-work
TAG                            DIGEST                                                                     STATUS    LAST UPDATE    LAST PUSHED    LAST PULLED    SIZE
taobeier/saythx-work:latest    sha256:3133a607d062dd3a8b46f38c8271099c258f5e59cecd652bebddf6e15789cb32    active    2 years ago    2 years        6 days         52.94MB
taobeier/saythx-work:1.0       sha256:3133a607d062dd3a8b46f38c8271099c258f5e59cecd652bebddf6e15789cb32    active    2 years ago    2 years        6 days         52.94MB

Token 相關管理功能

對個人 Token 的創建/刪除,激活/失效,列表,查詢詳細等功能。

(MoeLove) ?  hub-tool token -h
Manage Personal Access Tokens

Usage:
  hub-tool token [flags]
  hub-tool token [command]

Available Commands:
  activate    Activate a Personal Access Token
  create      Create a Personal Access Token
  deactivate  Deactivate a Personal Access Token
  inspect     Inspect a Personal Access Token
  ls          List all the Personal Access Tokens
  rm          Delete a Personal Access Token

Flags:
  -h, --help   help for token

Global Flags:
      --verbose   Print logs

Use "hub-tool token [command] --help" for more information about a command.
(MoeLove) ?  hub-tool token ls
DESCRIPTION               UUID                                    LAST USED       CREATED      ACTIVE
test-docker-token         xxxxxxxx-xxxx-xxxx-xxxx-moelove.info    9 months ago    9 months     true
(MoeLove) ?  hub-tool token inspect xxxxxxxx-xxxx-xxxx-xxxx-moelove.info
Token:
UUID:    xxxxxxxx-xxxx-xxxx-xxxx-moelove.info
Description:    test-docker-token
Is Active:    true
Created:    9 months ago
Last Used:    9 months ago
Creator User Agent:    Mozilla/5.0 (X11; Fedora; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.4103.116 Safari/537.36
Creator IP:    100.000.000.00
Generated:    By user via Web UI

總結

以上就是關于 Docker 新發布的 Hub Tool 的全部功能介紹了。
當前它是隨著 Docker Desktop 一起發行的,所以 Linux 下暫時沒有。但是計劃會盡快開源。敬請期待!


歡迎訂閱我的文章公眾號【MoeLove】

TheMoeLove

查看原文

贊 3 收藏 0 評論 0

張晉濤 關注了標簽 · 2020-12-19

golang

Go語言是谷歌2009發布的第二款開源編程語言。Go語言專門針對多處理器系統應用程序的編程進行了優化,使用Go編譯的程序可以媲美C或C++代碼的速度,而且更加安全、支持并行進程。
Go語言是谷歌推出的一種全新的編程語言,可以在不損失應用程序性能的情況下降低代碼的復雜性。谷歌首席軟件工程師羅布派克(Rob Pike)說:我們之所以開發Go,是因為過去10多年間軟件開發的難度令人沮喪。Go是谷歌2009發布的第二款編程語言。

七牛云存儲CEO許式偉出版《Go語言編程
go語言翻譯項目 http://code.google.com/p/gola...
《go編程導讀》 http://code.google.com/p/ac-m...
golang的官方文檔 http://golang.org/doc/docs.html
golang windows上安裝 http://code.google.com/p/gomi...

關注 26192

張晉濤 發布了文章 · 2020-12-19

Go 1.16 中關于 go get 和 go install 你需要注意的地方

Go (golang) 已于 18 日發布了 1.16 beta1 版本,至此其主體功能已經基本確定。我看大多數人都在關注 Go 在蘋果(Apple) M1 上的支持,甚至 Go 官方博客中也有一篇專門的說明 Go on ARM and Beyond ,來介紹 Go 在此方面的支持。

我就不湊熱鬧了,我來聊聊 Go 1.16 中關于 go getgo install 你需要注意的地方。

目前 Docker 官方鏡像尚未發布,我是本地構建了個鏡像來使用。

(MoeLove) ?  go version
go version go1.16beta1 linux/amd64

概覽

Go 1.16 中包含著大量的 Modules 相關的更新,詳細內容可直接查看其 Release Note。整體而言,包含以下要點:

  • GO111MODULE 默認為 on ,如果要恢復到之前的行為,則需要將 GO111MODULE 設置為 auto ,這樣差不多意味著 GOPATH 模式要逐步淡出人們的視野了;
  • go install 命令可以接受一個版本后綴了,(例如,go install sigs.k8s.io/kind@v0.9.0),并且它是在模塊感知的模式下運行,可忽略當前目錄或上層目錄的 go.mod 文件。這對于在不影響主模塊依賴的情況下,安裝二進制很方便;
  • 在將來,go install 被設計為“用于構建和安裝二進制文件”, go get 則被設計為 “用于編輯 go.mod 變更依賴”,并且使用時,應該與 -d 參數共用,在將來版本中 -d 可能會默認啟用;
  • go buildgo test 默認情況下不再修改 go.modgo.sum??赏ㄟ^ go mod tidy ,go get 或者手動完成;

總結而言,關于 go installgo get 必須要注意的是:

  • 基本上 go install <package>@<version> 是用于命令的全局安裝:

    • 例如:go install sigs.k8s.io/kind@v0.9.0;
  • go get 安裝二進制的功能,后續版本將會刪除;
  • go get 主要被設計為修改 go.mod 追加依賴之類的,但還存在類似 go mod tidy 之類的命令,所以使用頻率可能不會很高;

Go 1.16 中已解決的工具安裝問題

到目前為止,Go 一直使用 go get 命令,將我們需要的工具安裝到 $GOPATH/bin 目錄下,但這種方式存在一個很嚴重的問題。go get 由于具備更改 go.mod 文件的能力,因此我們 必須要避免執行 go get 命令時,讓它接觸到我們的 go.mod 文件 ,否則它會將我們安裝的工具作為一個依賴。

目前的解決方案通常是:

(MoeLove) ?  cd $(mktemp -d); GO111MODULE=on go get sigs.k8s.io/kind@v0.9.0

自 1.16 開始,我們可以直接使用下面的方式:

(MoeLove) ?  go install sigs.k8s.io/kind@v0.9.0

非常的簡單直觀。需要注意的是 go install <package>@<version> 是從 1.16 開始增加的,無論你當前是否在一個模塊下,此命令都會在 $GOPATH/bin 下安裝指定版本的工具。

此外由于 Go 1.16 中 GO111MODULE 默認是打開的,go install 不會修改 go.mod 之類的文件,不會造成任何意外。

注意:

@version 只能安裝主軟件包。非主程序包不受此格式約束。

關于不帶 @versiongo install

  • 在模塊外,不帶 @version 是無法安裝的,會有如下錯誤:
(MoeLove) ?  go install  -v sigs.k8s.io/kind
go install: version is required when current directory is not in a module
        Try 'go install sigs.k8s.io/kind@latest' to install the latest version
  • 如果你在模塊目錄中,并且你不帶 @version 執行安裝的話,只能安裝 go.mod 中已經包含的版本。并且不能安裝未出現在 go.mod 中的包。
(MoeLove) ?  mkdir -p /go/src/github.com/moelove/iris
(MoeLove) ?  cd /go/src/github.com/moelove/iris
# 初始化模塊
(MoeLove) ?  /go/src/github.com/moelove/iris go mod init
go: creating new go.mod: module github.com/moelove/iris
(MoeLove) ?  /go/src/github.com/moelove/iris cat go.mod 
module github.com/moelove/iris

go 1.16


# 不帶 @version 無法安裝
(MoeLove) ?  /go/src/github.com/moelove/iris go install -v sigs.k8s.io/kind
no required module provides package sigs.k8s.io/kind; try 'go get -d sigs.k8s.io/kind' to add it

# 用 go get -d 下載
(MoeLove) ?  /go/src/github.com/moelove/iris go get -d sigs.k8s.io/kind
go get: added sigs.k8s.io/kind v0.9.0

# 可以看到已經被添加到了模塊依賴中
(MoeLove) ?  /go/src/github.com/moelove/iris cat go.mod 
module github.com/moelove/iris

go 1.16

require sigs.k8s.io/kind v0.9.0 // indirect

# 刪除本地的 kind 工具
(MoeLove) ?  /go/src/github.com/moelove/iris which kind
/go/bin/kind
(MoeLove) ?  /go/src/github.com/moelove/iris rm /go/bin/kind
(MoeLove) ?  /go/src/github.com/moelove/iris which kind

# 不帶 @version 進行安裝
(MoeLove) ?  /go/src/github.com/moelove/iris go install -v sigs.k8s.io/kind
(MoeLove) ?  /go/src/github.com/moelove/iris which kind
/go/bin/kind
(MoeLove) ?  /go/src/github.com/moelove/iris kind version
kind v0.9.0 go1.16beta1 linux/amd64

關于 go getgo.mod

go get 將二進制安裝相關的功能都轉移到了 go install, 僅作為用于編輯 go.mod 文件的命令存在。在后續版本(計劃是 Go 1.17)中刪掉 go get 安裝二進制的功能,接下來 go get 的行為就等同于我們現在執行 go get -d 命令了,僅需下載源碼,并將依賴添加至 go.mod 即可。

go.mod 如何編輯

在 Go 1.16 中,另一個行為變更是 go buildgo test 不會自動編輯 go.mod 了,基于以上信息,Go 1.16 中將進行如下處理:

  • 通過在代碼中修改 import 語句,來修改 go.mod

    • go get 可用于添加新模塊;
    • go mod tidy 刪除掉無用的模塊;
  • 將未導入的模塊寫入 go.mod:

    • go get <package>[@<version>];
    • go mod tidy 也可以;
    • 手動編輯;

從 1.15 升級需要注意什么?

由于 go buildgo test 不會自動編輯 go.mod 了,所以可以將原本的行為通過 go mod tidy 共同處理。

總結

Go 1.16 中 go installgo get 方面有些不兼容的變更,但是 1.16 中模塊更加簡潔,減少了使用時的心智負擔,我還是很期待這個版本的。


歡迎訂閱我的文章公眾號【MoeLove】

TheMoeLove

查看原文

贊 6 收藏 1 評論 0

張晉濤 發布了文章 · 2020-12-09

K8S 棄用 Docker 了?Docker 不能用了?別逗了!

Docker 大概沒想到,2020 年,它在技術圈內的兩次成為(輿論的)焦點,竟然都是因為信息差(說是“標題黨”也不為過)。

概覽

2013 年

Docker 是在 2013 年的 PyCon 上首次正式對外公布的。
它帶來了一種先進的軟件交付方式,即,通過容器鏡像進行軟件的交付。
工程師們只需要簡單的 docker build 命令即可制作出自己的鏡像,并通過 docker push 將其發布至 DockerHub 上。
通過簡單的 docker run 命令即可快速的使用指定鏡像啟動自己的服務。

通過這種辦法,可以有效的解決軟件運行時環境差異帶來的問題,達到其 Build once, Run anywhere 的目標。

從此 Docker 也基本成為了容器的代名詞,并成為容器時代的引領者。

2014 年

2014 年 Google 推出 Kubernetes 用于解決大規模場景下 Docker 容器編排的問題。

這是一個邏輯選擇,在當時 Docker 是最流行也是唯一的運行時。 Kubernetes 通過對 Docker 容器運行時的支持,迎來了大量的用戶。

同時,Google 及 Kubernetes 社區與 Docker 也在進行著密切的合作,在其官方博客上有如下內容:

We’ll continue to build out the feature set, while collaborating with the Docker community to incorporate the best ideas from Kubernetes into Docker.

An update on container support on Google Cloud Platform

Kubernetes is an open source manager for Docker containers, based on Google’s years of experience using containers at Internet scale.
Docker is delivering the full container stack that Kubernetes schedules into, and is looking to move critical capabilities upstream and align the Kubernetes framework with Libswarm.

Welcome Microsoft, RedHat, IBM, Docker and more to the Kubernetes community

并在同一個月的 DockerCon 上發布演講,介紹了 Kubernetes 并受到了廣泛的關注。

此時 Docker Inc. 也發布了其容器編排工具, libswarm (也就是后來的 swarmkit) 。

2015 年

2015 年 OCI (Open Container Initiative)由 Docker 和其他容器行業領導者共同成立(它也是 Linux 基金會旗下項目)

OCI 主要包含兩個規范:

  • 運行時規范(runtime-spec):容器運行時,如何運行指定的 文件系統上的包
  • 容器鏡像規范(image-spec):如何創建一個 OCI 運行時可運行的文件系統上的包

Docker 把它自己的容器鏡像格式和 runtime ( 現在的 runc ) 都捐給了 OCI 作為初始工作。

2016 年

2016 年 6 月,Docker v1.12 發布,帶來了 Docker 在多主機多容器的編排解決方案,Docker Swarm 。
這里也需要注意的是,Docker v1.12 的設計原則:

  • Simple Yet Powerful (簡單而強大)
  • Resilient(彈性)
  • Secure(安全)
  • Optional Features and Backward Compatibility(可選功能及向后兼容)

所以你可以通過配置自行選擇是否需要使用 Docker Swarm ,而無需擔心有什么副作用。

2016 年 12 月, Kubernetes 發布 CRI (Container Runtime Interface) ,這當中一部分原因是由于 Kubernetes 嘗試支持另一個由 CoreOS 領導的容器運行時項目 rkt ,但是需要寫很多兼容的代碼之類的,為了避免后續兼容其他運行時帶來的維護工作,所以發布了統一的 CRI 接口,凡是支持 CRI 的運行時,皆可直接作為 Kubernetes 的底層運行時;

當然, Kubernetes 也是在 2016 年逐步取得那場容器編排戰爭的勝利的。

2017 年

2017 年, Docker 將自身從 v1.11 起開始引入的容器運行時 containerd 捐給了 CNCF

2017 年,Docker 的網絡組件 libnetwork 增加了 CNI 的支持;
同時通過使用 Docker 為 Docker Swarm 提供的 ipvs 相關的代碼,也在 Kubernetes 中實現了基于 IPvs 的 service 負載均衡。不過在 v1.18 中開始移除了相關的依賴。

同年 11 月,Kubernetes 中新增了 containerd 的支持

image

2018 年

2018 年, Kubernetes 的 containerd 集成,正式 GA

之前版本的架構:

image

新的架構:
image

2019 年

2019 年,上文中提到的另一個容器運行時項目 rkt 被 CNCF 歸檔,終止使命了;
2019 年 Mirantis 收購 Docker 的企業服務。

2020 年

時間回到今年,Docker 主要被誤會的兩件事:

  • Docker Inc. 修改 DockerHub 的定價和 TOS 。國內爭論較多的主要是關于合規性的問題(但是被標題黨帶歪了,免不了恐慌);
  • Kubernetes 宣布開始進入廢棄 dockershim 支持的倒計時,被人誤以為 Docker 不能再用了;

說明

關于 DockerHub 修改定價和 TOS 的事情,這里就不再多說了,畢竟 DockerHub 目前大家仍然用的很歡樂,遠不像當初那些標題黨宣稱的那樣。

重點來說一下第二件事情吧。

Kubernetes 當初選擇 Docker 作為其容器運行時,本身就是因為當時它沒有其他的選擇,并且選擇 Docker 可為它帶來眾多的用戶。
所以,開始時,它便提供了內置的對 Docker 運行時的支持。

而 Docker 其實創建之初,并沒有考慮到“編排”的這個功能,當然也沒有考慮到 Kubernetes 的存在(因為當時還沒有)。

dockershim 一直都是 Kubernetes 社區為了能讓 Docker 成為其支持的容器運行時,所維護的一個兼容程序。 本次所謂的廢棄,也僅僅是 Kubernetes 要放棄對現在 Kubernetes 代碼倉庫中的 dockershim 的維護支持。 以便其可以像開始時計劃的那樣,僅負責維護其 CRI ,任何兼容 CRI 的運行時,皆可作為 Kubernetes 的 runtime 。

在 Kubernetes 提出 CRI 時,有人建議在 Docker 中實現它。但是這種方式也會帶來一個問題,即使 Docker 實現了 CRI,但它仍然不是一個單純的容器運行時,它本身包含了大量的非 “純底層容器運行時” 所具備的功能。

所以后來 自 Docker 中分離出來的 containerd 項目,作為一個底層容器運行時出現了,它是 Kubernetes 容器運行時更好的選擇。

Docker 使用 containerd 作為其底層容器運行時以及眾多的云廠商及公司在生產環境中使用 containerd 作為其 Kubernetes 的運行時,這也從側面驗證了 containerd 的穩定性。

現在 Kubernetes 和 Docker 社區都相信 containerd 已經足夠成熟可直接作為 Kubernetes 的運行時了,而無需再通過 dockershim 使用 Docker 作為 Kubernetes 的運行時了。這也標志著 Docker 為 Kubernetes 提供一個現代化的容器運行時的承諾最終兌現了。

而本次事件中,重點的 dockershim 之后的方向如何呢?Kubernetes 代碼倉庫中的 dockershim 將會在未來版本中移除,但是 Mirantis 公司已經和 Docker 達成合作,在未來會共同維護一份 dockershim 組件,以便支持 Docker 作為 Kubernetes 的容器運行時。

Otherwise, if you’re using the open source Docker Engine, the dockershim project will be available as an open source component, and you will be able to continue to use it with Kubernetes; it will just require a small configuration change, which we will document.

Mirantis 公司宣布將維護 dockershim

Q&A

Q:本次 Kubernetes 放棄對 dockershim 的維護,到底有什么影響?
A:對于普通用戶而言,沒有任何影響;對于在 Kubernetes 之上進行開發的工程師,沒什么太大影響;對于集群管理員,需要考慮是否要在未來版本中,將容器運行時,升級為支持 CRI 的運行時,比如 containerd 。
當然,如果你并不想切換容器運行時,那也沒關系,Mirantis 公司未來會和 Docker 共同維護 dockershim , 并作為一個開源組件提供。

Q: Docker 不能用了嗎?
A:Docker 仍然是本地開發,或者單機部署最佳的容器工具,它提供了更為人性化的用戶體驗,并且也有豐富的特性。目前 Docker 已經和 AWS 達成合作,可直接通過 Docker CLI 與 AWS 集成。另外,Docker 也仍然可以作為 Kubernetes 的容器運行時,并沒有立即中止對其支持。

Q:聽說 Podman 可以借機上位了?
A:想太多。Podman 也并不兼容 CRI ,并且它也不具備作為 Kubernetes 容器運行時的條件。我個人也偶爾有在用 Podman, 并且我們在 KIND 項目中也提供了對 Podman 的支持, 但實話講,它也就是只是一個 CLI 工具,某些情況下會有些作用,比如如果你的 Kubernetes 容器運行時使用 cri-o 的情況下,可以用來本地做下調試。

總結

本文主要介紹了 Docker 和 Kubernetes 的發展歷程,也解釋了本次 Kubernetes 僅僅是放棄其對 dockershim 組件的支持。未來更推薦的 Kubernetes 運行時是 兼容 CRI 的 containerd 之類的底層運行時。

Mirantis 公司將會和 Docker 共同維護 dockershim 并作為開源組件提供。

Docker 仍然是一款最佳的本地開發測試和部署的工具。


歡迎訂閱我的文章公眾號【MoeLove】

image

查看原文

贊 10 收藏 5 評論 0

張晉濤 發布了文章 · 2020-10-31

K8S 生態周報| Istio v1.7.1 發布

「K8S 生態周報」內容主要包含我所接觸到的 K8S 生態相關的每周值得推薦的一些信息。歡迎訂閱知乎專欄「k8s生態」。

Istio v1.7.1 發布

這是 Istio v1.7 系列的第一個 patch 版本。此次更新有些值得注意的內容:

  • #26625 修復了 istioctl x authz check 使其能更好的兼容 v1beta1 AuthorizationPolicy ;
  • #26617 修復了 headless services endpoint 更新不會觸發任何 xds pushes 的問題;
  • ##26938 修復了當使用 IstioCNIremove-from-mesh 未移除 init container 的問題;

Rook v1.4.3 發布

這是個 patch 版本,主要修復了一些和 Ceph 有關的問題, 以及引入了一些小功能:

修復:

  • #6232 由于 Ceph-CSI driver 在某些集群中會把垃圾回收清理掉,所以創建 csidriver 對象時不再為它設置 ownerRef 了。主要是因為 csidriver 是集群級別的對象,不應該將 namespace 級別的對象設置為它的 ownerRef;

修改:

  • #6225 為 OSD pod 添加 storageClassDeviceSet 標簽
  • #6145 為 Ceph 集群增加 uninstall 模式,如果 UninstallMode CR spec 設置為 yes-really-uninstall-even-if-in-use, 那么集群會直接全部刪除,而不會等待 PVC 的等待;
  • ##6198 僅在 Dashboard 設置為 true 的時候,才會啟動 init 容器;
  • #6127 如果一個 OSD down 了,并且需要從 cluster 中移除,則會啟動一個 job 去清理它。如果 OSD 仍然 up, 則該 job 會被拒絕;

Thanos v0.15.0 發布

本次新增了一個組件 Query Frontend 這是基于 Cortex Query Frontend 的,所以它們有些相同的特性,比如 SplittingResults Caching

更多關于此版本的信息,請參考其 ReleaseNote

上游進展

  • #94398 kubeadm 將 kube-schedulerkube-controller-manager 的 kubeconfig 配置文件中 API Server 的地址指向了本地的 API Server 地址。主要是為了避免升級過程中,當這些組件與 API Server 版本不一致時,向 API Server 請求不存在的 API 地址;
  • #94395kubeadm upgrade 時,無論 etcd 版本是否有變化,將確保 etcd manifest 重新生成;

歡迎訂閱我的文章公眾號【MoeLove】

TheMoeLove

查看原文

贊 0 收藏 0 評論 0

張晉濤 發布了文章 · 2020-09-22

突破 DockerHub 限制,全鏡像加速服務

最近 DockerHub 修改了定價,對于免費帳號會限制 200 pulls/6小時,對于匿名帳號則限制 100 pulls/6小時。 本文我來介紹下如何使用 Cache 來應對此問題。

背景

DockerHub 是全世界最早也是最大的容器鏡像倉庫,托管著眾多操作系統發行版及各類軟件的 Docker 鏡像。

在推進業務容器化的過程中,不可避免的,我們會需要使用來自 DockerHub 上的容器鏡像。 無論是在個人本地環境中使用,還是用于跑測試服務

以下是兩種主要的解決方案:

  • 構建一些公共基礎鏡像,存放在企業的私有鏡像倉庫中給業務方使用:

    這種方案下,如果業務方偶爾需要一些小眾的/非基礎的鏡像,可能只是臨時測試使用,那通常情況下是沒必要將此類鏡像作為基礎鏡像維護的。

    結果可能是:

    • 使用中直接從 DockerHub pull 鏡像,網絡狀況不佳時,就是無盡的等待;
    • 先 pull 鏡像,然后 docker tag 重 tag 后, push 到企業的私有鏡像倉庫中。這種情況下,如果沒有較好的鏡像管理規則,那么鏡像倉庫中就會存在各種無意義的鏡像,造成存儲資源的浪費。
  • 為 docker daemon 配置 Proxy 進行加速:

    • 眾多國內鏡像加速服務,僅提供 Docker 官方鏡像的加速服務,個人/組織下的鏡像不提供加速服務;
    • 即使在不同節點上,下載相同的鏡像,仍然需要通過網絡加速,會產生額外的海外帶寬成本;

并且近期 DockerHub 修改了其服務價格, 對于免費用戶,進行了如下限制:

  • 未登錄用戶,每 6 小時只允許 pull 100 次
  • 已登錄用戶,每 6 小時只允許 pull 200 次

如果我們繼續使用上述兩種模式的話,由于出口 IP 是相對固定的,所以很容易觸發 DockerHub 的配額限制。 此限制將于 11 月 1 日正式全面實施。

為了能 提升效率 ,以及 節約加速帶寬成本 ,企業內部/個人就非常需要一個 DockerHub 全鏡像加速服務了,也就是我們常說的 pull through cache。

下面我來介紹下,如何利用 Docker 開源的項目 registry:2 來實現這一需求。

啟動服務

使用 registry:2 部署鏡像緩存服務很簡單,這里先執行 docker pull registry:2 下載所需的鏡像:

(MoeLove) ?  docker pull registry:2
2: Pulling from library/registry
cbdbe7a5bc2a: Pull complete 
47112e65547d: Pull complete 
46bcb632e506: Pull complete 
c1cc712bcecd: Pull complete 
3db6272dcbfa: Pull complete 
Digest: sha256:8be26f81ffea54106bae012c6f349df70f4d5e7e2ec01b143c46e2c03b9e551d
Status: Downloaded newer image for registry:2
docker.io/library/registry:2

最小化配置的 DockerHub 鏡像緩存服務,只需要使用一個配置項 REGISTRY_PROXY_REMOTEURL 即可:

這里我順便為它單獨創建了一個名為 hub-cache 的 network ,以及創建了對應的 volume 。

(MoeLove) ?  ~ docker network create hub-cache
19a39f873a23150d3bdaf021e040ccccb092ee3071884d64d52a92df0397b220
(MoeLove) ?  ~ docker volume create hub-cache
hub-cache
(MoeLove) ?  ~ docker run --name=cache -d --restart=always --network=hub-cache -v hub-cache:/var/lib/registry -p 5000:5000  -e REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io registry:2 
6cbdcbdcc2d62ec781479901c20be43184a48b2d73e06f04bd4693253c0c5a73
(MoeLove) ?  ~ docker ps -l
CONTAINER ID   IMAGE        COMMAND                  CREATED         STATUS         PORTS                    NAMES
6cbdcbdcc2d6   registry:2   "/entrypoint.sh /etc…"   8 seconds ago   Up 6 seconds   0.0.0.0:5000->5000/tcp   cache

驗證加速效果

啟動一個全新的 Docker In Docker 容器進行驗證,避免受到本地環境的影響。

通過傳遞 --registry-mirror http://cache:5000 ,將剛才啟動的 registry 設置為 mirror 。

(MoeLove) ?  ~ docker run  --network=hub-cache -d --privileged docker:dind --registry-mirror http://cache:5000 
73c56ac25d68927c9f5b0e458f2babc0699cf8595df0d1e86c021fd03d477384
(MoeLove) ?  ~ docker exec -it $(docker ps -ql) sh
/ # 檢查配置是否生效
/ # docker info --format '{{ .RegistryConfig.Mirrors }}' 
[http://cache:5000/]

/ # time docker pull prom/prometheus
Using default tag: latest
latest: Pulling from prom/prometheus
76df9210b28c: Pull complete 
559be8e06c14: Pull complete 
6a4bb3319487: Pull complete 
2cca90a64593: Pull complete 
d2014e464a99: Pull complete 
70b42590e4a2: Pull complete 
54645fcbd6cc: Pull complete 
67d9943de656: Pull complete 
b9c749b1af90: Pull complete 
9723d8eb5323: Pull complete 
7d20502d5322: Pull complete 
3e519cce6f63: Pull complete 
Digest: sha256:d43417c260e516508eed1f1d59c10c49d96bbea93eafb4955b0df3aea5908971
Status: Downloaded newer image for prom/prometheus:latest
docker.io/prom/prometheus:latest
real    0m 42.71s
user    0m 0.12s
sys     0m 0.09s


/ # docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
prom/prometheus     latest              cdfc440228d0        8 days ago          168MB

/ # docker rmi prom/prometheus                                     
Untagged: prom/prometheus:latest
Untagged: prom/prometheus@sha256:d43417c260e516508eed1f1d59c10c49d96bbea93eafb4955b0df3aea5908971
Deleted: sha256:cdfc440228d01d7a94937d7a047fa6461efc1b1806bb20677043fee032810830
Deleted: sha256:c72f348fd2f923996ea80222feb77e34aba9de397bd96206ddc3c8651adc306d
Deleted: sha256:e34df2c00334266a67bb846b958ba6eae3b1d5cdfe9d763707027a23e7c85100
Deleted: sha256:d2cb38310ada122064b7333bbfc12c67dc58acb30e29146b3ba1e24adc27a950
Deleted: sha256:7a87cd520d19a83b3582541aac4d95098ae5016b092e72eaf80dc54f587bf51e
Deleted: sha256:f84c79dceed6b5a27234c1291d0bdccab5c459d587f13934d74db9b9e79471c6
Deleted: sha256:f542b0cffe0fe16c31c98e7eed934d5fea5e598c03b53b4efd308a62e0e9c6a9
Deleted: sha256:f746b4a525727bcb79367d009d707ef45d75bac09aaa18a68c20a19046df0897
Deleted: sha256:09b45653ee7062c7cd754885bf46ebe554d0794573fb2e200acea8644e64670f
Deleted: sha256:867526c56b30e67493341ef33890aa242c1131e4bb4151e60011b4d450892d59
Deleted: sha256:86d629b358ee70bdb0f0a11c10915b8551e904fe337f9a8bfcad476977329532
Deleted: sha256:842455c528af7383ba4a0de424fc63664a0248581a191516d6dbf45195c69426
Deleted: sha256:1be74353c3d0fd55fb5638a52953e6f1bc441e5b1710921db9ec2aa202725569

/ # time docker pull prom/prometheus
Using default tag: latest
latest: Pulling from prom/prometheus
76df9210b28c: Pull complete 
559be8e06c14: Pull complete 
6a4bb3319487: Pull complete 
2cca90a64593: Pull complete 
d2014e464a99: Pull complete 
70b42590e4a2: Pull complete 
54645fcbd6cc: Pull complete 
67d9943de656: Pull complete 
b9c749b1af90: Pull complete 
9723d8eb5323: Pull complete 
7d20502d5322: Pull complete 
3e519cce6f63: Pull complete 
Digest: sha256:d43417c260e516508eed1f1d59c10c49d96bbea93eafb4955b0df3aea5908971
Status: Downloaded newer image for prom/prometheus:latest
docker.io/prom/prometheus:latest
real    0m 5.27s
user    0m 0.06s
sys     0m 0.03s

可以看到,在首次 pull prom/prometheus 鏡像時,耗費了 42+s 的時間,而刪除掉已下載的鏡像后,再次 pull, 則只需要耗費 5+s 的時間。速度提升非常的明顯。鏡像加速效果達成

使用配置

對于 Linux 系統而言,僅需要在 /etc/docker/daemon.json 文件(如果沒有此文件,直接創建即可)中寫入你的鏡像加速服務的域名,重啟 docker daemon 即可(也可選擇 reload 配置)。

{
        "registry-mirrors": [
                "https://hub-cache.moelove.info"
        ]
}

或者是在 docker daemon 的啟動參數中加入 registry-mirror 配置項。

對于 Mac 和 Windows 用戶,直接在 Docker Desktop 系統設置中,配置 registry-mirrors 即可。

注意 如果 Docker daemon 中配置了 HTTP_PROXYHTTPS_PROXY ,那么需要將加速域名配置在 NO_PROXY 中,避免被代理。

總結

本文介紹了如何使用 Docker 開源的 registry:2 搭建 DockerHub 的鏡像加速服務。這里只介紹了最簡單的配置。

但如果在企業環境中部署的話,需要有更多的配置。比如,可以通過配置 REGISTRY_HTTP_DEBUG_PROMETHEUS_ENABLED 暴露 Prometheus metrics ,用于監控服務可用性及查看 cache 的效果;可以對日志及相關字段進行配置;
為了避免在 11 月后,觸發到 DockerHub 的流量限制,可以橫向進行擴容,準備多出口 IP,以及配置賬戶等。

最近新發布的 Harbor v2.1 貌似多了一個作為 proxy cache 的特性,但它與本文介紹的 pull through cache 并不相同,使用 Harbor 的 proxy cache 特性,需要將待 pull 的鏡像,設置成 <harbor_servername>/<proxy_project_name>/repo/name:tag 的形式,這樣子只是省去了本文一開始介紹的那種手動重 tag 的操作,不夠方便,但也是個很不錯的特性了。


歡迎訂閱我的文章公眾號【MoeLove】

TheMoeLove

查看原文

贊 7 收藏 4 評論 0

認證與成就

  • 獲得 140 次點贊
  • 獲得 6 枚徽章 獲得 0 枚金徽章, 獲得 1 枚銀徽章, 獲得 5 枚銅徽章

擅長技能
編輯

開源項目 & 著作
編輯

注冊于 2014-03-17
個人主頁被 7.9k 人瀏覽

一本到在线是免费观看_亚洲2020天天堂在线观看_国产欧美亚洲精品第一页_最好看的2018中文字幕 <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <文本链> <文本链> <文本链> <文本链> <文本链> <文本链>