k8s 的安全机制

1. 常规认证机制

1.1. 证书认证机制

k8s 中的证书认证采用双向认证,双方都需要持有 CA 根证书,用来验证对方的证书是否合法。下面使用 kubectl 与 API Server 之间的双向数字证书认证为例来了解这个过程。

可以通过 kubectl config view 命令来查看 kubectl 当前使用的证书相关数据:

$ kubectl config view --raw
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJV3BidUI1emx1eWd3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TlRBM01UUXdNVEkyTURoYUZ3MHpOVEEzTVRJd01UTXhNRGhhTUJVeApFekFSQmdOVkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUURHS0ZSeVcvR2YxTHFDd1lUaUF0bC9xZXlFd3FuSVlDd0k4U3VYcDF3MVJheWY2bkw1cW9SV1p0THoKM0EzaVlFeFVNdXdiOTZWTlRWVnFpL3NBM2dOWW5TSXV1WFdZM0hnUGVWZ0FXM3JxY2dPa3pmdWtCdzRrNWtpeApWdklOSEZCdjNZSXJjWWxaWW1GZ3VETlJmR1Y5QUFoY1dRSUE2ejQzN0pjY040U2x4MFhDTXY4bXlxcEd5WDNYCnFzRHpEb05Bc1ZuMlZLVUxDaks4ckFBUVBiTzZRdFVIbStjOWtHTEM0YUEyYkVqTy9rWGU3b00zUWNrMTkvbzQKd0pnTjVncWp6VHFNRzRyREtpaExJRWJFQjA0L2lvRVJ4WDJ5S25Sa0thcjc0YnFNMkZMMnA5U0hVdmVQOW9zYwpHQmNIQzBzVE0xNXlOcGQ3cEtRNmNkQ25uT0VwQWdNQkFBR2pXVEJYTUE0R0ExVWREd0VCL3dRRUF3SUNwREFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJUN284eFRJRUpnanMxaXlJNmhMNjVhcUpwVmZUQVYKQmdOVkhSRUVEakFNZ2dwcmRXSmxjbTVsZEdWek1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQmZtRUoxcGkyRwo1S1IvTXJHS0dJTHdjS1dMS1dHdHo1RmlGTXRVTjZYdDh6SHdtaHoxTnBTTGlIUG5hZ01kT2w4RytzMFgxMWVRClNQMEhNTkVGazJWa2RXd29EeW54RDgvN2M4d2htQVRkck44WXE1ZXFxbUhWdUlwOEM0ZWFlY1hyOE5DbGlQZ1gKdjNTMkdxbXZNMEovbTRVVDNBL2dEcTdzZElMdXhZb0dvckV2T05yME5PZDNYcmVGV3ZJRko1ckE3eWpoVjFCMgoyV0FoWDJzRkdRNFVOMFB3Q2daNXA1VURrQnljaXFPRFFlSnA5RFltbktwb3JJNTkrZGNMeHl1dmMrT3JIRmVOClNmTGdZRGN0MzVVMk5PNWpITG9wODdWOVZ5MWJhYzIvUmdrSjNldHRpQlY0eFMwYkRXTlpnOEJWdTNmdUpJbTIKalhxT29pN1ZsQzRJCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
    server: https://api-server:8443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: kubernetes-admin
  name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: kubernetes-admin
  user:
    client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURLVENDQWhHZ0F3SUJBZ0lJRDNmQ3JxZ3dCU0l3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TlRBM01UUXdNVEkyTURoYUZ3MHlOakEzTVRRd01UTXhNRGhhTUR3eApIekFkQmdOVkJBb1RGbXQxWW1WaFpHMDZZMngxYzNSbGNpMWhaRzFwYm5NeEdUQVhCZ05WQkFNVEVHdDFZbVZ5CmJtVjBaWE10WVdSdGFXNHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDWG1xaGcKNHV4MEFyaytwekUzcFE1aWhtbjUybXFnakpWM0hXK0wrZGcwRXljaDB0aExiYnFCSzBxQkRRQlB3VnZDVy96TgpEVEw5OWFLTlh0dUhZRjhLUWl2ZnpDZ1ZGOFREKzJmeGFQNzd6S1BYN28xQmZRZEdtSGc4UFFjL1B3WTVTT1g3ClZsdG1QYXZZR1RhdFpWU1RUVjdXcjNUSU9pVkh3RDlDOHowenZtWTROdGQ4anBSSnBlbklGVzhpQzBxOUp6SWwKbktpWjhzMW5tM1BBU3hRekJ6c2dsczVFZ3VCR2Z6VDhNT3hQeGNOdHNiM1JyVGV6RDZuMldQM0ZXUS85UEowegpCczBtU2JqMTcxcmpGeVp0d3pLNGNxdWdmR2RXblkxK0ZRSk9EMTloTXpSK0xySERwa2pvZUhKTVA3WmZON2ZtCnJDUHplajZMVGYwN25EMGhBZ01CQUFHalZqQlVNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUsKQmdnckJnRUZCUWNEQWpBTUJnTlZIUk1CQWY4RUFqQUFNQjhHQTFVZEl3UVlNQmFBRlB1anpGTWdRbUNPeldMSQpqcUV2cmxxb21sVjlNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUJjc1pkNFVSY1FWdUxEd1JST2ovQWQ1L2huCitSdUpjTEVYeU10QTVIbHRxTWpOcnlNS04weFptMnMrMDVGdmVBc0l6dDFJWFlQM2hsdGFoWUJXS00wYWRSbTUKcnpWZGxwK0V4eFhhOEpOdjBaUmJFcWduQUg0c1VBRUNVblpEZ3lBZ2JnVzNsK3ZpNDdML3FJY1BCZy9CVjFQYgpIYURjcnRXWXE0UGEyMVNiZk9MUURRaHVra1ZFaWVmL2p5N0N5OXNsT2wvZFducWRkQ29OWndKemJ5T1ovbmVtCnNacEVMS09RSGx2amZmSnl6N3orMWhrOTFuaU85Z1lCbW9Zd0JMUTJibnkyVllRelBMamtaMlZxcklodFE3RW4KeXJTczFOdTIrRHVxeDMveHNUaVhxSkg2RklqTVcxNXFmWWFDYlJUeTIwWjZ4dExmUVcrWFBnZVQ3Nm5qCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
    client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBbDVxb1lPTHNkQUs1UHFjeE42VU9Zb1pwK2RwcW9JeVZkeDF2aS9uWU5CTW5JZExZClMyMjZnU3RLZ1EwQVQ4RmJ3bHY4elEweS9mV2lqVjdiaDJCZkNrSXIzOHdvRlJmRXcvdG44V2orKzh5ajErNk4KUVgwSFJwaDRQRDBIUHo4R09VamwrMVpiWmoycjJCazJyV1ZVazAxZTFxOTB5RG9sUjhBL1F2TTlNNzVtT0RiWApmSTZVU2FYcHlCVnZJZ3RLdlNjeUpaeW9tZkxOWjV0endFc1VNd2M3SUpiT1JJTGdSbjgwL0REc1Q4WERiYkc5CjBhMDNzdytwOWxqOXhWa1AvVHlkTXdiTkprbTQ5ZTlhNHhjbWJjTXl1SEtyb0h4blZwMk5maFVDVGc5ZllUTTAKZmk2eHc2Wkk2SGh5VEQrMlh6ZTM1cXdqODNvK2kwMzlPNXc5SVFJREFRQUJBb0lCQURTWlVpWWJWVktDeDJhMwpkMW9KQ0U2aXVBNForN2lzVGdjL3pUM1JkM1BKMlYvZzJXNkNLWjA3T2VSQWNJVTdYdXkySWFXN0dLQUlJMWZOCk8zbGl0RmJJMHBRWkx5YnlVakIwLzRGQW5vY2FYeFpDQy94V3RybUZtT0c5Z3RBc3U2b1FZc3FyRVpjTmVwdkYKVTdVaFhSQ21YV3M3QU5lZlpPUGFEWmphNzdjTnBaRC9iMXNqZFBRekl2UlUwc1JYL2UxR3BnRDBnbUZMeWtPZwpWSFFkaDFkSHI3U3lZZGdXTnhGVks3ekRWUjVTMWI3dStaeTcyczJtaTFZZVVHaEtpRHhEMzdZMnJlVmY3K05oCnR4SGQzdEZJWUhiVXAyeXMxekp3SklrSEM3d0dmdzlhdndqWFAwa0F6cmxwRHhBNlZGVm5CNjVUdEJMbzdjSGoKMTRaVjFZRUNnWUVBeGlSclc2b0VTRW1SNFJpdy9vbnZWMk0vbjR1WHdTeDc4OXVXdWVuMnJXRjY0WFFtc2F3Nwp0YVRESEphUFhxc2dXM2s4aDRaeW5CajVvaUpkc3lMMW1GNDB5T05yaVVJREh5aDJaSm05Y2VCM1NLOGxBMEVCCklHemlHMjBqcXBEOFk0OS9XeEZVbWJXMkQ0anh3NHRqTWVtWWR0dmtaTC9wdmRZSjhna0kvR2tDZ1lFQXc5OW8KZmtyNSsyUlkyMFlKTzJyaXdPQWZ5THpramRWS09hNGdKclFtQlovZEJOaXBPM0E0QzF3NGp5MUgwcW1yQXhvcQpsVmZNdEt0WGVqczZTZ3JFdklQdzRtZmwrZ0w0QmdHYXpQTUgzcFF5S0R6OW13Y1pEc05IckR6bXdFQ003Ty9WClBQcXB4VEFOR0M0YVdLSG9KdzkzeXB1a05iK2NlWGlFdUdCdGcva0NnWUIxRmZwVzNWM1FkYUQ5bWdDbE9VODAKblpKd0xpWUw0Y1NSY3BaYjREU0RlaElKL2dBTGg5SjA0UnJtM3RWenhMM0hNQm9qbjhCRUc4SVFIQ3l3ZVowVgpYNzNqWS9nYzBPUlZXaXl6LytGaDFKbklXcThOZ0RFeFB3WlIzS2x4MW1FK1dBS1RCdWV1T1NHUGhvWU8zbVJNCmoxUW9FSUtqUW9EV1RBL3VkWVpZMlFLQmdFSE15MDJoNGxpUlhVaE1QczIzR25XdzFQeWFlQzB0ZWNIbXZPWkQKbm9KRk1mcHV6bXNwUjZxSlVIYkl2MWdGcDIxalhPck9rL1lkbVVWMm9CNC9mOWZVZXhlVHo2NHRVU1N6WlUxMApWekJ1bUdyc0VrWUNIR2paTVRxck90bGExZnpDbE4vblZTRHBBMzBiLzczR3BqWjQrOFVVaEJXS1NRYUZkaEI2ClZGQUJBb0dCQUpHeFdoYi9ZaitscHNNOFhtQVk1TDBpNUR3NERUazJxeHlTdVk4M0ZMSTlDcW9jKzBxQ2p0Zi8KVlozdGRBR0lFRGw1UU01TEZFbU5FZlM0NXpDYU55L3NwdGUydG56SHUzNE1DVkIzOWg5MEJzemNhYjA2Y3cyRApIcVppY3NVSVY2ay9SOUxUZnRLN2dYT3JiMG5GNUNYV00yVHFmZjBlYzZpRlhxd2NkSjFMCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==
Bash

其中,certificate-authority-data 的内容是 CA 根证书,client-certificate-data 是 kubectl 的客户端证书,client-key-data 是客户端私钥,均通过 Base64 编码。

将证书通过 base64 -d 命令解码并保存为文件:

# certificate-authority-data
echo "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJV3BidUI1emx1eWd3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TlRBM01UUXdNVEkyTURoYUZ3MHpOVEEzTVRJd01UTXhNRGhhTUJVeApFekFSQmdOVkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUURHS0ZSeVcvR2YxTHFDd1lUaUF0bC9xZXlFd3FuSVlDd0k4U3VYcDF3MVJheWY2bkw1cW9SV1p0THoKM0EzaVlFeFVNdXdiOTZWTlRWVnFpL3NBM2dOWW5TSXV1WFdZM0hnUGVWZ0FXM3JxY2dPa3pmdWtCdzRrNWtpeApWdklOSEZCdjNZSXJjWWxaWW1GZ3VETlJmR1Y5QUFoY1dRSUE2ejQzN0pjY040U2x4MFhDTXY4bXlxcEd5WDNYCnFzRHpEb05Bc1ZuMlZLVUxDaks4ckFBUVBiTzZRdFVIbStjOWtHTEM0YUEyYkVqTy9rWGU3b00zUWNrMTkvbzQKd0pnTjVncWp6VHFNRzRyREtpaExJRWJFQjA0L2lvRVJ4WDJ5S25Sa0thcjc0YnFNMkZMMnA5U0hVdmVQOW9zYwpHQmNIQzBzVE0xNXlOcGQ3cEtRNmNkQ25uT0VwQWdNQkFBR2pXVEJYTUE0R0ExVWREd0VCL3dRRUF3SUNwREFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJUN284eFRJRUpnanMxaXlJNmhMNjVhcUpwVmZUQVYKQmdOVkhSRUVEakFNZ2dwcmRXSmxjbTVsZEdWek1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQmZtRUoxcGkyRwo1S1IvTXJHS0dJTHdjS1dMS1dHdHo1RmlGTXRVTjZYdDh6SHdtaHoxTnBTTGlIUG5hZ01kT2w4RytzMFgxMWVRClNQMEhNTkVGazJWa2RXd29EeW54RDgvN2M4d2htQVRkck44WXE1ZXFxbUhWdUlwOEM0ZWFlY1hyOE5DbGlQZ1gKdjNTMkdxbXZNMEovbTRVVDNBL2dEcTdzZElMdXhZb0dvckV2T05yME5PZDNYcmVGV3ZJRko1ckE3eWpoVjFCMgoyV0FoWDJzRkdRNFVOMFB3Q2daNXA1VURrQnljaXFPRFFlSnA5RFltbktwb3JJNTkrZGNMeHl1dmMrT3JIRmVOClNmTGdZRGN0MzVVMk5PNWpITG9wODdWOVZ5MWJhYzIvUmdrSjNldHRpQlY0eFMwYkRXTlpnOEJWdTNmdUpJbTIKalhxT29pN1ZsQzRJCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K" | base64 -d > ca.crt

# client-certificate-data
echo "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURLVENDQWhHZ0F3SUJBZ0lJRDNmQ3JxZ3dCU0l3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TlRBM01UUXdNVEkyTURoYUZ3MHlOakEzTVRRd01UTXhNRGhhTUR3eApIekFkQmdOVkJBb1RGbXQxWW1WaFpHMDZZMngxYzNSbGNpMWhaRzFwYm5NeEdUQVhCZ05WQkFNVEVHdDFZbVZ5CmJtVjBaWE10WVdSdGFXNHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDWG1xaGcKNHV4MEFyaytwekUzcFE1aWhtbjUybXFnakpWM0hXK0wrZGcwRXljaDB0aExiYnFCSzBxQkRRQlB3VnZDVy96TgpEVEw5OWFLTlh0dUhZRjhLUWl2ZnpDZ1ZGOFREKzJmeGFQNzd6S1BYN28xQmZRZEdtSGc4UFFjL1B3WTVTT1g3ClZsdG1QYXZZR1RhdFpWU1RUVjdXcjNUSU9pVkh3RDlDOHowenZtWTROdGQ4anBSSnBlbklGVzhpQzBxOUp6SWwKbktpWjhzMW5tM1BBU3hRekJ6c2dsczVFZ3VCR2Z6VDhNT3hQeGNOdHNiM1JyVGV6RDZuMldQM0ZXUS85UEowegpCczBtU2JqMTcxcmpGeVp0d3pLNGNxdWdmR2RXblkxK0ZRSk9EMTloTXpSK0xySERwa2pvZUhKTVA3WmZON2ZtCnJDUHplajZMVGYwN25EMGhBZ01CQUFHalZqQlVNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUsKQmdnckJnRUZCUWNEQWpBTUJnTlZIUk1CQWY4RUFqQUFNQjhHQTFVZEl3UVlNQmFBRlB1anpGTWdRbUNPeldMSQpqcUV2cmxxb21sVjlNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUJjc1pkNFVSY1FWdUxEd1JST2ovQWQ1L2huCitSdUpjTEVYeU10QTVIbHRxTWpOcnlNS04weFptMnMrMDVGdmVBc0l6dDFJWFlQM2hsdGFoWUJXS00wYWRSbTUKcnpWZGxwK0V4eFhhOEpOdjBaUmJFcWduQUg0c1VBRUNVblpEZ3lBZ2JnVzNsK3ZpNDdML3FJY1BCZy9CVjFQYgpIYURjcnRXWXE0UGEyMVNiZk9MUURRaHVra1ZFaWVmL2p5N0N5OXNsT2wvZFducWRkQ29OWndKemJ5T1ovbmVtCnNacEVMS09RSGx2amZmSnl6N3orMWhrOTFuaU85Z1lCbW9Zd0JMUTJibnkyVllRelBMamtaMlZxcklodFE3RW4KeXJTczFOdTIrRHVxeDMveHNUaVhxSkg2RklqTVcxNXFmWWFDYlJUeTIwWjZ4dExmUVcrWFBnZVQ3Nm5qCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K" | base64 -d > client.crt
Bash

查看证书的明文内容:

$ openssl x509 -in ca.crt -text -noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 6527666426384595752 (0x5a96ee079ce5bb28)
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=kubernetes
        Validity
            Not Before: Jul 14 01:26:08 2025 GMT
            Not After : Jul 12 01:31:08 2035 GMT
        Subject: CN=kubernetes
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
...

$ openssl x509 -in client.crt -text -noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 1114573488199304482 (0xf77c2aea8300522)
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=kubernetes
        Validity
            Not Before: Jul 14 01:26:08 2025 GMT
            Not After : Jul 14 01:31:08 2026 GMT
        Subject: O=kubeadm:cluster-admins, CN=kubernetes-admin
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
...

# 证书的签发者(CA):Issuer: CN=kubernetes
# Subject 字段包含用户的身份信息:
## CN (Common Name) 字段被用作用户名。
## O (Organization) 字段被用作用户所属的组。
Bash

认证过程

  • kubectl 向 API Server 发起 HTTPS 请求,并提供其客户端证书。
  • API Server 使用配置的 CA 根证书来验证该客户端证书的有效性和真实性(是否由受信任的 CA 签发、是否在有效期内等)。并将自己的证书返回给 kubectl。
  • kubectl 使用配置的 CA 根证书来验证 API Server。注意:kubectl 与 API Server 使用的 CA 根证书相同。

手动验证示例:

$ openssl verify -CAfile ca.crt /etc/kubernetes/pki/apiserver.crt 
/etc/kubernetes/pki/apiserver.crt: OK
Bash

1.2. Service Account 认证机制

1.2.1. Service Account 概述

这是 Kubernetes 内部 Pod 与 API Server 通信的默认和主要方式。每个 Pod 都会自动挂载一个 ServiceAccount Token。特点如下:

  • 在创建新的命名空间时,会生成一个默认的名为 default 的 Service Account。
  • 创建 Pod 时,如果没有通过 serviceAccountName 属性指定使用哪个 Service Account,则会使用 Pod 所在 Namespace 的 default ServiceAccount。
  • Service Account Token 是以 Projected Volume 方式映射到 Pod 容器的,文件全路径为 var/run/secrets/kubernetes.io/serviceaccount/token,容器中的用户进程可以从中读取 Token 数据用于身份认证。
  • Service Account Token 具有有效期,默认是1小时。
  • CA 根证书也以 Projected Volume 方式映射到 Pod 容器,用来验证 API Server 和 Kubernetes 其他服务组件的证书。

示例:

创建一个 Pod :

# ubuntu-pod.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: ubuntu-pod
  labels:
    app: ubuntu-container
spec:
  containers:
  - name: ubuntu-container
    image: ubuntu:latest
    command: ["/bin/bash", "-c", "--"]
    args: ["while true; do sleep 30; done;"]
    resources:
      limits:
        memory: "512Mi"
        cpu: "500m"
  restartPolicy: Never
YAML

查看 Service Account 相关信息:

# kubectl get pod ubuntu-pod -o yaml
apiVersion: v1
kind: Pod
...
spec:
  containers:
    ...
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: kube-api-access-pbf6d
      readOnly: true
  ...
  serviceAccount: default
  serviceAccountName: default
  ...
  volumes:
  - name: kube-api-access-pbf6d
    projected:
      defaultMode: 420
      sources:
      - serviceAccountToken:
          expirationSeconds: 3607
          path: token
      - configMap:
          items:
          - key: ca.crt
            path: ca.crt
          name: kube-root-ca.crt
      - downwardAPI:
          items:
          - fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace
            path: namespace
YAML

进入容器查看:

kubectl exec -it ubuntu-pod -- bash

root@ubuntu-pod:/# ll var/run/secrets/kubernetes.io/serviceaccount/
total 0
drwxrwxrwt 3 root root 140 Aug 22 08:18 ./
drwxr-xr-x 3 root root  28 Aug 22 08:18 ../
drwxr-xr-x 2 root root 100 Aug 22 08:18 ..2025_08_22_08_18_20.1358454188/
lrwxrwxrwx 1 root root  32 Aug 22 08:18 ..data -> ..2025_08_22_08_18_20.1358454188/
lrwxrwxrwx 1 root root  13 Aug 22 08:18 ca.crt -> ..data/ca.crt
lrwxrwxrwx 1 root root  16 Aug 22 08:18 namespace -> ..data/namespace
lrwxrwxrwx 1 root root  12 Aug 22 08:18 token -> ..data/token
Bash

1.2.2. Service Account Token 令牌

在 Kubernetes v1.22 版本之前,当创建一个 ServiceAccount(SA)时,会自动为其创建一个对应的、类型为 kubernetes.io/service-account-token 的 Secret。这个 Secret 包含了永不过期的令牌(Token)。然而,这种静态的、永久的 Token 带来了很大的安全风险:

  • 令牌泄露风险: 如果 Secret 被泄露,攻击者可以永久使用这个令牌,除非手动删除它。
  • 权限回收困难: 无法轻松地使单个令牌失效。

为了解决这个问题,Kubernetes 引入了 TokenRequest API 并推广使用投射卷(Projected Volume)。新版本的默认配置下:

  • 不再自动创建: 创建 ServiceAccount 时,Kubernetes 默认不会再自动创建 对应的 Secret 对象。
  • 按需签发: 当 Pod 被创建并使用某个 ServiceAccount 时,Kubelet 会代表 Pod 向 API Server 的 TokenRequest API 发起请求,动态地申请一个有时间限制的、可绑定的令牌。
  • 临时令牌: 这个令牌会被自动投射(Projected)到 Pod 的容器中。这个令牌通常是有效的(例如 1 小时),kubelet 会定期更新。

1.2.3. Service Account Token 的绑定与更新

在默认情况下,Pod 会绑定 Service Account Token,并且映射到容器内部的指定位置,如果不需要 Token,则可以通过 Service Account 或者 Pod 的automountServiceAccountToken 属性来取消绑定。下面是对应的例子:

apiVersion: v1
kind: ServiceAccount 
metadata:
  name:build-robot
automountServiceAccountToken: false 


apiVersion: v1
kind: Pod 
metadata:
  name: my-pod 
spec:
  serviceAccountName: build-robot
  automountServiceAccountToken: false
  
# 如果 Service和 Pod 都声明了这个参数,则以 Pod 的参数值作为最终结果。
YAML

Service Account Token 由 kubelet 自动更新,如果想要主动立即更新需要重建 Pod。

1.3. 总结与对比

认证机制主要用途
X.509 证书集群组件、人类用户
ServiceAccountPod 访问 API

2. RBAC 授权机制

RBAC 授权模式是 Kubernetes 最主要的授权模式,该授权模式具有以下优势:

  • 对集群中的资源型 API 和非资源型 API 的权限均有完整的覆盖。
  • RBAC通过 Role 和 RoleBinding 两种 API 资源对象即可完成授权设置。
  • 可以在运行时进行调整,无须重新启动 API Server。

要使用 RBAC 授权模式,首先需要在 kube-apiserver 服务的启动参数 authorization-mode (授权模式)的列表中加上 RBAC,例如--authorization-mode=…,RBAC

2.1. RBAC 的 API 资源对象

在 RBAC 管理体系中,Kubernetes 引入了4个资源对象:Role、ClusterRole、RoleBinding 和 ClusterRoleBinding。

2.1.1. Role 和 ClusterRole

一个 Role 就是一组权限的集合,在 Role 中设置的权限都是许可(Permissive)形式的,不可以设置拒绝(Deny)形式的权限。Role 设置的权限将会局限于命名空间范围内,如果需要跨越多个命名空间,在集群级别设置权限,则需要使用 ClusterRole。

Role 示例

# 定义一个Role:在default命名空间中有pod的读权限
apiVersion: rbac.authorization.kubernetes.io/v1
kind: Role
metadata:
  namespace: default
  name: pod-reader
rules:
- apiGroups: [""] # "" 空字符串,代表核心API组
  resources: ["pods"]
  verbs: ["get", "list", "watch"]
YAML

Role资源对象的主要配置参数都在rules字段中进行设置,如下所述:

  • apiGroups:资源对象 API 组列表,例如 ””、”extensions”、”apps”、”batch”等。
  • resources:需要操作的资源对象类型列表,例如 “pods”、”deployments”、”jobs”等。
  • verbs:设置允许对资源对象进行操作的方法列表,例如 “get”、”watch”、”list”、”delete”、”replace”、”patch”等。

ClusterRole 示例

# 定义一个ClusterRole,有权权访问一个或所有命名空间的 secrets 的权限
apiVersion: rbac,authorization.k8s.io/v1 
kind: ClusterRole 
metadata:
  # ClusterRole 不受限于命名空间,所以无须设置 Namespace 
  name: secret-reader 
rules:
- apiGroups: [""]
  resources: ["secrets")
  verbs: ["get", "watch", "list"]
YAML

ClusterRole 和 Role 类似,但它是集群范围的,可以授权访问:

  • 集群范围资源(如 Node)。
  • 非资源端点(如 /healthz)。
  • 所有命名空间中的资源。

2.1.2. RoleBinding 和 ClusterRoleBinding

RoleBinding 和 ClusterRoleBinding 用来把一个 Role 绑定到一个目标主体上,绑定目标可以是 User(用户)、Group(组)或者 Service Account。RoleBinding 用于某个命名空间中的用户角色绑定,ClusterRoleBinding 用于集群范围内的用户角色授权。

RoleBinding 示例

# 将一个个Role绑定给一个ServiceAccount
apiVersion: rbac.authorization.kubernetes.io/v1
kind: RoleBinding
metadata:
  namespace: default
  name: read-pods
subjects:
- kind: ServiceAccount
  name: my-app-sa    # ServiceAccount名
  namespace: default # ServiceAccount 所在的命名空间,需与 RoleBinding 在相同名称空间。
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.kubernetes.io
YAML

RoleBinding 可以将一个 Role 或 ClusterRole 绑定到相同名称空间的某个主体(Subject)。

ClusterRoleBinding 示例

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: kubeadm:cluster-admins
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: Group
  name: kubeadm:cluster-admins
YAML

注意:在集群角色绑定中引用的角色只能是 ClusterRole,而不能是 Role。

2.2. Role 规则参考示例

核心工作负载权限

  • 基本 Pod 操作员
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list", "watch", "create", "delete"]
# 说明:可以查看、创建、删除 Pod,但无法在任何控制器(如Deployment)范围内操作它们。
YAML
  • 完整的应用部署者
rules:
- apiGroups: ["", "apps"]
  resources: ["pods", "deployments", "replicasets", "statefulsets", "daemonsets"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# 说明:可以全生命周期管理主流的无状态和有状态工作负载。
YAML
  • 批量任务处理员
rules:
- apiGroups: ["batch"]
  resources: ["jobs", "cronjobs"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# 说明:可以创建和管理定时任务和一次性任务。
YAML

网络与服务发现权限

  • 服务管理员
rules:
- apiGroups: [""]
  resources: ["services", "endpoints"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# 说明:可以管理集群内的服务发现和负载均衡。
YAML
  • 网络策略管理员(需要CNI插件支持,如Calico)
rules:
- apiGroups: ["networking.k8s.io"]
  resources: ["networkpolicies"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# 说明:可以定义Pod之间的网络隔离策略。
YAML
  •  Ingress 管理员
rules:
- apiGroups: ["networking.k8s.io"]
  resources: ["ingresses"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
  resources: ["services"] # 通常创建Ingress需要关联Service
  verbs: ["get", "list", "watch"]
YAML

配置与存储权限

  • 配置管理员
rules:
- apiGroups: [""]
  resources: ["configmaps", "secrets"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# 说明:可以管理应用的配置信息和敏感数据。注意:Secrets权限应谨慎分配!
YAML
  • 存储管理员
rules:
- apiGroups: [""]
  resources: ["persistentvolumeclaims"] # 申请存储
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["storage.k8s.io"]
  resources: ["storageclasses"] # 查看可用的存储类型
  verbs: ["get", "list", "watch"]
YAML

观察与监控权限(只读)

  • 日志查看员
rules:
- apiGroups: [""]
  resources: ["pods", "pods/log"] # 关键:需要 pods/log 子资源权限
  verbs: ["get", "list", "watch"]
# 说明:可以查看Pod列表及其日志。用于故障排查。
YAML
  • 系统监控员
rules:
- apiGroups: ["", "apps", "batch"]
  resources: ["*"] # 对所有资源生效
  verbs: ["get", "list", "watch"] # 但只有读取权限
- apiGroups: ["metrics.k8s.io"] # 访问资源指标API(需部署Metrics Server)
  resources: ["pods", "nodes"]
  verbs: ["get", "list", "watch"]
YAML

命名空间管理员

rules:
- apiGroups: ["*"] # 所有API组
  resources: ["*"] # 所有资源
  verbs: ["*"]     # 所有操作
# 拥有在某一个命名空间内的几乎所有权限(除了修改资源配额和命名空间本身)。
# 注意:通常需要通过 RoleBinding 将其绑定到某个命名空间,而不是 ClusterRoleBinding。
YAML

3. 准入控制机制

3.1. 概述

准入控制器(Admission Controller)是 Kubernetes API 服务器中的一个拦截器/钩子(Hook)链。当一个请求通过了认证(Authentication)和授权(Authorization)之后,在真正操作资源对象(如创建 Pod、部署 Deployment 等)之前,准入控制器会对其进行处理。准入控制器决定了请求是被允许、拒绝,还是被修改

准入控制的两个阶段

  • 变更阶段 (Mutating)
    • 任务:修改传入的请求对象。
    • 作用:可以给请求“打补丁”,例如:自动注入 Sidecar 容器(如 Istio)、添加默认标签、附加存储卷、修改资源请求等。
  • 验证阶段 (Validating)
    • 任务:验证传入的请求对象,但不修改它。
    • 作用:执行更严格的校验,确保请求符合集群的策略。例如:要求所有 Pod 必须有“app”标签、禁止容器以 root 用户运行、校验镜像来自可信仓库等。

执行顺序Mutating → Validating。这样能确保所有修改完成后,再对最终版本的对象进行合法性验证。

API Server 提供了很多预置的 AdmissionController,如果这些默认的插件无法满足业务需求,则可以通过 Webhook 外挂的方式让 API Server 对接编程实现的外部Admission Webhook,从而实现与 Admission Controller 一样的功能。

3.2. 内置 Admission Controller

  • 与 Namespace 相关的 Admission Controller
准入控制器类型描述
NamespaceAutoProvisionMutating该插件会检测所有进入的具备命名空间的资源请求,如果其中引用的命名空间不存在,则自动创建命名空间。
NamespaceExistsValidating对于有 Namespace 的资源对象,要求其必须设置 namespace 属性,并且该Namespace 存在,否则拒绝创建。
NamespaceLifecycleValidating如果尝试在一个不存在的命名空间中创建资源对象,则该创建请求将被拒绝。在删除一个命名空间时,系统将删除该命名空间中的所有对象,包括Pod、Service等,并阻止删除 default、kube-system 和 kube-public 这3个命名空间。
  • 与 Pod 相关的 Admission Controller

注意:为 API Server 设置启动参数即可定制需要的 Admission Control Flow,在Kubernetes v1.10 及以上版本中,使用的参数名为“–enable-admission-plugins”(设置启用的 Admission 插件列表)和“–disable-admission-plugins”(设置禁用的 Admission 插件列表),可以配置多个插件,以逗号隔开,顺序是不重要的,系统默认启用的插件也可以通过 –disable- admission-plugins 参数进行禁用。

可以通过下面的命令来查看哪些插件是系统默认启用的:

kube-apiserver -h | grep enable-admission-plugins
Bash

如果 API Server 运行在 Pod 中,则可以通过以下命令进行查看:

kubectl -n kube-system exec kube-apiserver-k8s-master-01 -- \
  kube-apiserver -h | grep "enable-admission-plugins"
Bash

3.3. Admission Webhook 准入控制器

Admission Webhook 是用户自行编写的 WebServer 外挂程序,是 Kubernetes 内置 Admission Controller 的扩展机制。Admission Webhook 需要处理 APIServer 发来的包含 AdmissionReview 数据的请求,并完成用户需要的准入控制逻辑。
在 AdmissionReview 数据中包含用户访问 APIServer 的一些访问控制属性,主要数据结构如下:

apiVersion: admission.k8s.io/v1
kind: AdmissionReview
request:
  # 唯一标识此次准入回调的随机UID
  uid: 705ab4f5-6393-11e8-b7cc-42010a800002
  resource:
    group: apps
    version: v1
    resource: deployments
  # 被修改的资源的名称
  name: my-deployment
  # 如果资源属于命名空间(或者命名空间对象),则这是被修改的资源的命名空间
  namespace: my-namespace
  # 操作可以是CREATE、UPDATE、DELETE 或 CONNECT
  operation: UPDATE
  userInfo:
    # 向 API Server发出请求的经过身份验证的用户的用户名
    username: admin
    # 向 API Server发出请求的经过身份验证的用户的 UID
    uid: 014fbff9a07c
    # 向 API Server发出请求的经过身份验证的用户的组成员身份
    groups:
      - system:authenticated
      - my-admin-group
    # 向 API Server发出请求的用户相关的任意附加信息
    # 该字段由 API Server身份验证层填充,并且如果webhook 执行了任何
    # SubjectAccessReview检查,则应将其包括在内
    extra:
      some-key:
        - some-value1
        - some-value2
  # object是被接纳的新对象
  # 对于 DELETE 操作,它为 null
  object:
    apiVersion: autoscaling/v1
    kind: Scale
  # oldObject 是现有对象
  # 对于 CREATE 和 CONNECT操作,它为 null
  oldObject:
    apiVersion: autoscaling/v1
    kind: Scale
  # options 包含要接受的操作的选项
  # 例如 meta.k8s.io/v1 CreateOptions、UpdateOptions或 DeleteOptions
  # 对于 CONNECT 操作,它为 null
  options:
    apiVersion: meta.k8s.io/v1
    kind: UpdateOptions
YAML

APIServer 将这些信息发送给 AdmissionWebhook,由它来完成准入控制的处理逻辑并返回响应结果。响应结果返回具有同样数据结构的 AdmissionReview 对象给APIServer,将准入控制的结果写在 response 字段中。示例:

{
  "apiversion": "admission.k8s.io/v1",
  "kind": "AdmissionReview",
  "response": {
    "uid": "<705ab4f5-6393-11e8-b7cc-42010a800002>",
    "allowed": true
  }
}
JSON
  • uid:从发送到 Webhook 请求的 request.uid 中复制。
  • allowed:设置为 true 或 false,表示结果是允许还是拒绝。

要将自行开发的 Admission Webhook 配置到 k8s 集群中需要创建 MutatingWebhookConfiguration 或 ValidatingWebhookConfiguration 资源对象,可以包含一个或多个 Webhook,详细的配置流程见下面的示例。

3.4. Admission Webhook 使用示例

3.4.1. Mutating Webhook

需求:为所有新创建的 pod 打上标签:environment: production

先准备 webhook 程序
# encoding: UTF-8

from flask import Flask, request, jsonify  # 导入 Flask 框架、请求处理和 JSON 响应模块
import json
import ssl
import base64

app = Flask(__name__)  # 创建一个 Flask 应用实例


def create_patch(metadata):
    """
    创建 JSON Patch 以添加 'environment' 标签。
    """
    patches = []
    
    # 检查是否存在 labels
    if 'labels' not in metadata:
        # 如果不存在 labels,先创建空的 labels
        patches.append({'op': 'add', 'path': '/metadata/labels', 'value': {}})
    
    # 添加或替换 environment 标签
    patches.append({
        'op': 'add', 
        'path': '/metadata/labels/environment', 
        'value': 'production'
    })
    
    return patches


@app.route('/mutate', methods=['POST'])  # https://webhook-service.default.svc:443/mutate
def mutate():
    """
    处理 Mutating Webhook 的请求,对 Pod 对象应用 JSON Patch。
    """
    try:
        admission_review = request.get_json()  # 从请求中提取 AdmissionReview 对象
        
        # 验证 AdmissionReview 格式是否正确
        if (not admission_review or 
            'request' not in admission_review or 
            'uid' not in admission_review['request'] or
            'object' not in admission_review['request'] or
            'metadata' not in admission_review['request']['object']):
            
            return jsonify({
                'apiVersion': 'admission.k8s.io/v1',
                'kind': 'AdmissionReview',
                'response': {
                    'uid': admission_review.get('request', {}).get('uid', 'unknown'),
                    'allowed': False,
                    'status': {
                        'code': 400,
                        'message': 'Invalid AdmissionReview format: missing required fields'
                    }
                }
            })

        req = admission_review['request']  # 提取请求对象
        print('Received request with UID:', req['uid'])
        
        # 生成 JSON Patch
        metadata = req['object']['metadata']
        patches = create_patch(metadata)
        
        # 将 patch 列表转换为 JSON 字符串并进行 base64 编码
        patch_json = json.dumps(patches)
        patch_base64 = base64.b64encode(patch_json.encode('utf-8')).decode('utf-8')

        # 准备 AdmissionResponse 响应
        admission_response = {
            'apiVersion': 'admission.k8s.io/v1',
            'kind': 'AdmissionReview',
            'response': {
                'uid': req['uid'],
                'allowed': True,
                'patchType': 'JSONPatch',
                'patch': patch_base64  # base64 编码的 JSON patch
            }
        }

        print('Response:', json.dumps(admission_response, indent=2))
        return jsonify(admission_response)

    except Exception as e:
        print(f"Error processing request: {str(e)}")
        return jsonify({
            'apiVersion': 'admission.k8s.io/v1',
            'kind': 'AdmissionReview',
            'response': {
                'uid': admission_review.get('request', {}).get('uid', 'unknown') if 'admission_review' in locals() else 'unknown',
                'allowed': False,
                'status': {
                    'code': 500,
                    'message': f'Internal server error: {str(e)}'
                }
            }
        })


@app.route('/healthz', methods=['GET'])
def health_check():
    """健康检查端点"""
    return jsonify({'status': 'healthy'})


if __name__ == '__main__':
    # 加载 SSL 证书和私钥
    try:
        context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
        context.load_cert_chain('/certs/tls.crt', '/certs/tls.key')
        
        print("Starting mutating webhook server on https://0.0.0.0:443")
        app.run(host='0.0.0.0', port=443, ssl_context=context, debug=False)
    
    except Exception as e:
        print(f"Failed to start server: {str(e)}")
        # 开发环境下可以降级到 HTTP
        print("Falling back to HTTP for development...")
        app.run(host='0.0.0.0', port=8080, debug=True)
Python
制作镜像

包含 webhook.py 的运行环境,依赖python3解释器、依赖flask框架。文件Dockerfile 内容如下:

# 使用官方 Python 镜像作为基础镜像
FROM python:3.9-slim

# 设置工作目录
WORKDIR /app

# 将当前目录的 webhook.py 文件复制到容器的 /app 目录
COPY webhook.py .

# 安装 Flask 及其依赖
RUN pip install Flask

# 启动 Flask 应用
CMD ["python", "webhook.py"]
Dockerfile

然后构建镜像上传到镜像仓库。

配置 Webhook 的 Secret

准备 TLS 相关文件:

# 生成 CA 私钥
openssl genrsa -out ca.key 2048

# 生成自签名 CA 证书,有效期为 100 年
openssl req -x509 -new -nodes -key ca.key -subj "/CN=webhook-service.default.svc" -days 36500 -out ca.crt

# 创建证书请求的配置文件
cat > webhook-openssl.cnf << 'EOF'
[req]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = req_ext
distinguished_name = dn

[ dn ]
C = CN
ST = Shanghai
L = Shanghai
O = pingk
OU = pingk
CN = webhook-service.default.svc

[ req_ext ]
subjectAltName = @alt_names

[alt_names]
DNS.1 = webhook-service
DNS.2 = webhook-service.default
DNS.3 = webhook-service.default.svc
DNS.4 = webhook-service.default.svc.cluster.local


[req_distinguished_name]
CN = webhook-service.default.svc

[v3_req]
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[ v3_ext ]
authorityKeyIdentifier=keyid,issuer:always
basicConstraints=CA:FALSE
keyUsage=keyEncipherment,dataEncipherment
extendedKeyUsage=serverAuth,clientAuth
subjectAltName=@alt_names
EOF


# 使用 webhook-openssl.cnf 这个配置文件生成证书文件
# 生成 Webhook 服务的私钥
openssl genrsa -out webhook.key 2048

# 生成证书签名请求 (CSR)
openssl req -new -key webhook.key -out webhook.csr -config webhook-openssl.cnf

# 使用 CA 签发服务器证书
openssl x509 -req -in webhook.csr \
  -CA ca.crt \
  -CAkey ca.key \
  -CAcreateserial \
  -out webhook.crt \
  -days 36500 \
  -extensions v3_ext \
  -extfile webhook-openssl.cnf
Bash

将生成的证书和私钥存储在 Kubernetes Secret 中:

kubectl create secret tls webhook-certs \
--cert=webhook.crt \
--key=webhook.key \
--namespace=default --dry-run=client -o yaml | kubectl apply -f -
Bash
创建 deployment 来部署 webhook 服务
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webhook-deployment
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: webhook
  template:
    metadata:
      labels:
        app: webhook
    spec:
      containers:
      - name: webhook
        image: crpi-sozjkv641zbs4m9x.cn-shenzhen.personal.cr.aliyuncs.com/pingk-k8s-test/mute-webhook:v1.0
        ports:
        - containerPort: 443  # 明确声明容器端口
        volumeMounts:
        - name: webhook-certs
          mountPath: /certs
          readOnly: true
        resources:
          requests:
            memory: "64Mi"
            cpu: "250m"
          limits:
            memory: "128Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /healthz
            port: 443
            scheme: HTTPS
          initialDelaySeconds: 10
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /healthz
            port: 443
            scheme: HTTPS
          initialDelaySeconds: 5
          periodSeconds: 5
      volumes:
      - name: webhook-certs
        secret:
          secretName: webhook-certs
---
apiVersion: v1
kind: Service
metadata:
  name: webhook-service
  namespace: default
spec:
  ports:
  - port: 443
    targetPort: 443
    protocol: TCP
  selector:
    app: webhook
  type: ClusterIP  # 明确指定服务类型
YAML
创建 MutatingWebhookConfiguration
#!/bin/bash
# 生成 yaml 文件脚本

base64 -w 0 ca.crt > ca.crt.base64

# 定义文件路径
ca_base64_file="ca.crt.base64"
yaml_file="m-w-c.yaml"

# 读取 ca.crt.base64 的内容
ca_base64_content=$(cat "$ca_base64_file" | tr -d '\n')

# 生成替换后的 YAML 文件内容
# 将 base64 内容插入到 YAML 文件中
cat <<EOF > "$yaml_file"
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: example-mutating-webhook
webhooks:
- name: example.webhook.com
  clientConfig:
    service:
      name: webhook-service
      namespace: default
      path: "/mutate"
      port: 443
    # 替换为 cat ca.crt.base64的内容
    caBundle: $ca_base64_content
  rules:
  - operations: ["CREATE"]
    apiGroups: [""]
    apiVersions: ["v1"]
    resources: ["pods"]
  admissionReviewVersions: ["v1"]
  sideEffects: None
  timeoutSeconds: 30
EOF

echo "YAML 文件已更新:$yaml_file"
Bash
创建 Pod 进行测试
# test.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: test-pod
spec:
  containers:
  - name: nginx
    image: nginx:1.18
YAML

查看标签:

kubectl get pod test-pod -o json | jq '.metadata.labels'

# 可以看到已经自动打上标签
{
  "environment": "production"
}
Bash

3.4.2. Validating Webhook

webhook 程序示例:

# encoding: UTF-8

from flask import Flask, request, jsonify
import ssl
import logging

app = Flask(__name__)
logging.basicConfig(level=logging.INFO)

@app.route('/validate', methods=['POST'])  # 修复:添加了路由路径
def validate():
    """
    Validating Webhook 端点,用于验证 Pod 是否包含 environment 标签
    """
    try:
        admission_review = request.get_json()
        logging.info(f"Received admission review request")

        # 验证 AdmissionReview 格式
        if (not admission_review or 
            'request' not in admission_review or 
            'object' not in admission_review['request'] or
            'uid' not in admission_review['request'] or
            'kind' not in admission_review['request']):
            
            logging.error("Invalid AdmissionReview format")
            return jsonify({
                'apiVersion': 'admission.k8s.io/v1',
                'kind': 'AdmissionReview',
                'response': {
                    'uid': admission_review.get('request', {}).get('uid', 'unknown'),
                    'allowed': False,
                    'status': {
                        'code': 400,
                        'message': 'Invalid AdmissionReview format: missing required fields'
                    }
                }
            })

        req = admission_review['request']
        uid = req['uid']
        resource_kind = req['kind'].get('kind', '')
        resource_name = req['object'].get('metadata', {}).get('name', 'unknown')

        logging.info(f"Processing request {uid} for {resource_kind}/{resource_name}")

        # 只处理 Pod 资源
        if resource_kind == 'Pod':
            pod = req['object']
            labels = pod.get('metadata', {}).get('labels', {})

            # 检查是否有 'environment' 标签
            if 'environment' not in labels:
                logging.warning(f"Pod {resource_name} rejected: missing environment label")
                return jsonify({
                    'apiVersion': 'admission.k8s.io/v1',
                    'kind': 'AdmissionReview',
                    'response': {
                        'uid': uid,
                        'allowed': False,
                        'status': {
                            'code': 400,
                            'message': 'Pod must have an "environment" label'
                        }
                    }
                })

            logging.info(f"Pod {resource_name} validated successfully")
            return jsonify({
                'apiVersion': 'admission.k8s.io/v1',
                'kind': 'AdmissionReview',
                'response': {
                    'uid': uid,
                    'allowed': True,
                    'status': {
                        'code': 200,
                        'message': 'Validation passed'
                    }
                }
            })

        # 对于非 Pod 资源,直接允许
        logging.info(f"Allowing non-Pod resource: {resource_kind}")
        return jsonify({
            'apiVersion': 'admission.k8s.io/v1',
            'kind': 'AdmissionReview',
            'response': {
                'uid': uid,
                'allowed': True,
                'status': {
                    'code': 200,
                    'message': f'{resource_kind} resource allowed'
                }
            }
        })

    except Exception as e:
        logging.error(f"Error processing request: {str(e)}")
        return jsonify({
            'apiVersion': 'admission.k8s.io/v1',
            'kind': 'AdmissionReview',
            'response': {
                'uid': admission_review.get('request', {}).get('uid', 'unknown') if 'admission_review' in locals() else 'unknown',
                'allowed': False,
                'status': {
                    'code': 500,
                    'message': f'Internal server error: {str(e)}'
                }
            }
        })


@app.route('/healthz', methods=['GET'])
def health_check():
    """健康检查端点"""
    return jsonify({'status': 'healthy', 'service': 'validating-webhook'})


if __name__ == '__main__':
    # 加载 SSL 证书和私钥
    try:
        context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
        context.load_cert_chain('/certs/tls.crt', '/certs/tls.key')
        
        logging.info("Starting validating webhook server on https://0.0.0.0:443/validate")
        app.run(host='0.0.0.0', port=443, ssl_context=context, debug=False)
    
    except Exception as e:
        logging.error(f"Failed to start server: {str(e)}")
        # 开发环境下可以降级到 HTTP
        logging.info("Falling back to HTTP for development...")
        app.run(host='0.0.0.0', port=8080, debug=True)
Python

这是一个 Validating Webhook 服务器,主要功能是:

  • 验证 Pod 标签:检查所有新创建的 Pod 是否包含 environment 标签。
  • 准入控制:如果 Pod 没有 environment 标签,则拒绝创建。

相应的 ValidatingWebhookConfiguration

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: pod-environment-validator
webhooks:
- name: environment-validator.default.svc
  clientConfig:
    service:
      name: webhook-service
      namespace: default
      path: "/validate"  # 对应这里的路由
      port: 443
    caBundle: <ca-bundle>
  rules:
  - operations: ["CREATE"]
    apiGroups: [""]
    apiVersions: ["v1"]
    resources: ["pods"]
  admissionReviewVersions: ["v1"]
  sideEffects: None
  failurePolicy: Fail
YAML
上一篇
下一篇