Introduction#
- Persistent volume and claim
- AWS EBS CSI
- Static provisioning
- Dyanmic provisioning
- NodePort, ClusterIP and LoadBalancer services
Host Path Volume#
Let's create a volume in a specified single EC2 host. First, let's remote access to the volume and add data.
sudo mkdir /mnt/datasudo sh -c "echo 'Hello from Kubernetes storage' > /mnt/data/index.html"
Second, follow this docs to manually create a persistent volume.
apiVersion: v1kind: PersistentVolumemetadata:name: task-pv-volumelabels:type: localspec:storageClassName: manualcapacity:storage: 5GiaccessModes:- ReadWriteOncehostPath:path: '/mnt/data'nodeAffinity:required:nodeSelectorTerms:- matchExpressions:- key: kubernetes.io/hostnameoperator: Invalues:- ip-10-0-0-106.us-west-2.compute.internal
Let's create the persistent volume.
kubectl apply -f yaml/pv-host-path.yaml
Then check it.
get pv task-pv-volume
Third, create a persistent volume claim.
apiVersion: v1kind: PersistentVolumeClaimmetadata:name: task-pv-claimspec:storageClassName: manualaccessModes:- ReadWriteOnceresources:requests:storage: 3Gi
Then describe the claim.
kubectl get pvc task-pv-claim
Finally, let's create a pod.
apiVersion: v1kind: Podmetadata:name: task-pv-podspec:volumes:- name: task-pv-storagepersistentVolumeClaim:claimName: task-pv-claimcontainers:- name: task-pv-containerimage: nginxports:- containerPort: 80name: 'http-server'volumeMounts:- mountPath: '/usr/share/nginx/html'name: task-pv-storage
Run the pod and exec into it to check, let's cat index.html and see it content.
kubectl exec -it task-pv-pod -- /bin/shcd /usr/share/nginx/htmlcat index.html
The index.html content should be the same as created from the beginging.
NodePort Service#
First, let port forward to check the web running.
kubectl port-forward pod/task-pv-pod 8080:80
Second, let's expose the service via NodePort type.
apiVersion: v1kind: Servicemetadata:name: nginx-servicespec:selector:name: task-pv-podports:- protocol: TCPport: 80targetPort: 80type: NodePort
Let's find the assigned port. In this case the NodePort service port is 32195.
kubectl get services -n default
Access the EC2 node and check the service.
curl http://localhost:32195
ClusterIP Service#
Let's create a ClusterIP service.
apiVersion: v1kind: Servicemetadata:name: nginx-service-clusteripspec:selector:name: task-pv-podports:- protocol: TCPport: 80targetPort: 80type: ClusterIP
Describe the service, note the allocated IP address.
kubectl describe service nginx-service-clusterip
Then go to an EC2 instance and curl to the assigned IP address.
curl http://10.0.0.40:80
How service ClusterIPs are allocated?
-
dynamically: the cluster's control plane automatically picks a free IP address from within the configured IP range for type: ClusterIP Services.
-
statically: you specify an IP address of your choice, from within the configured IP range for Services.
EBS CSI Driver#
- Static provisioning
- Dyanmic provisioning
Create a Persistent Volume from existing ebs volume. This method called static provisioning. Please make sure to create a EBS volume and provie its id and zone here.
apiVersion: v1kind: PersistentVolumemetadata:name: test-pvspec:accessModes:- ReadWriteOncecapacity:storage: 5Gicsi:driver: ebs.csi.aws.comfsType: ext4volumeHandle: vol-03c604538dd7d2f41nodeAffinity:required:nodeSelectorTerms:- matchExpressions:- key: topology.ebs.csi.aws.com/zoneoperator: Invalues:- us-east-2c---apiVersion: v1kind: PersistentVolumeClaimmetadata:name: ebs-claimspec:storageClassName: '' # Empty string must be explicitly set otherwise default StorageClass will be setvolumeName: test-pvaccessModes:- ReadWriteOnceresources:requests:storage: 5Gi---apiVersion: v1kind: Podmetadata:name: appspec:containers:- name: appimage: centoscommand: ['/bin/sh']args:['-c', 'while true; do echo $(date -u) >> /data/out.txt; sleep 5; done']volumeMounts:- name: persistent-storagemountPath: /datavolumes:- name: persistent-storagepersistentVolumeClaim:claimName: ebs-claim
Exec in to the pod and see the data output saved in /data/out.txt
kubectl exec -it app -- /bin/sh
Now let create a dynamic provisioning. This will create a EBS volume of 4GB, IOPS 3000, and a throughput of 125Mbps. Please note that for dynamic provisioning, when we delete the object yaml, the ebs volume will be deleted as well.
apiVersion: storage.k8s.io/v1kind: StorageClassmetadata:name: ebs-scprovisioner: ebs.csi.aws.comvolumeBindingMode: WaitForFirstConsumer---apiVersion: v1kind: PersistentVolumeClaimmetadata:name: ebs-claimspec:accessModes:- ReadWriteOncestorageClassName: ebs-scresources:requests:storage: 4Gi---apiVersion: v1kind: Podmetadata:name: appspec:containers:- name: appimage: centoscommand: ['/bin/sh']args:['-c', 'while true; do echo $(date -u) >> /data/out.txt; sleep 5; done']volumeMounts:- name: persistent-storagemountPath: /datavolumes:- name: persistent-storagepersistentVolumeClaim:claimName: ebs-claim
S3 CSI Driver#
Let's update the iam-role-stack to create a role which will be assumed by the s3 csi driver.
iam-role.yaml
AWSTemplateFormatVersion: '2010-09-09'Parameters:EksStackName:Type: StringDefault: eks-stack-eks-clusterResources:EksOIDCProvider:Type: AWS::IAM::OIDCProviderProperties:Url: !ImportValueFn::Sub: ${EksStackName}-oidc-urlClientIdList:- sts.amazonaws.comThumbprintList:- 6938fd4d98bab03faadb97b34396831e3780aea1- 9e99a48a9960b14926bb7f3b02e22da2b0ab7280# Role for eks csi driver add-onEksCsiDriverRole:Type: AWS::IAM::RoleProperties:RoleName: eks-csi-driver-roleAssumeRolePolicyDocument: !Sub- |{"Version": "2012-10-17","Statement": [{"Effect": "Allow","Principal": {"Federated": "arn:${PARTITION}:iam::${ACCOUNT_ID}:oidc-provider/${OIDC}"},"Action": "sts:AssumeRoleWithWebIdentity","Condition": {"StringEquals": {"${OIDC}:aud": "sts.amazonaws.com","${OIDC}:sub": "system:serviceaccount:kube-system:ebs-csi-controller-sa"}}}]}- PARTITION: !Ref AWS::PartitionACCOUNT_ID: !Ref AWS::AccountIdOIDC: !ImportValueFn::Sub: ${EksStackName}-oidc-idManagedPolicyArns:- !Sub arn:${AWS::Partition}:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy# Role for eks container insight add-onEksContainerInsightRole:Type: AWS::IAM::RoleProperties:RoleName: eks-container-insight-roleAssumeRolePolicyDocument: !Sub- |{"Version": "2012-10-17","Statement": [{"Effect": "Allow","Principal": {"Federated": "arn:${PARTITION}:iam::${ACCOUNT_ID}:oidc-provider/${OIDC}"},"Action": "sts:AssumeRoleWithWebIdentity","Condition": {"StringEquals": {"${OIDC}:aud": "sts.amazonaws.com","${OIDC}:sub": "system:serviceaccount:amazon-cloudwatch:cloudwatch-agent"}}}]}- PARTITION: !Ref AWS::PartitionACCOUNT_ID: !Ref AWS::AccountIdOIDC: !ImportValueFn::Sub: ${EksStackName}-oidc-id# Fn::ImportValue: !Sub ${EksStackName}-oidc-idManagedPolicyArns:- !Sub arn:${AWS::Partition}:iam::aws:policy/CloudWatchAgentServerPolicy# Role for eks csi s3 add-onEksS3CsiDriverRole:Type: AWS::IAM::RoleProperties:RoleName: eks-s3-csi-driver-roleAssumeRolePolicyDocument: !Sub- |{"Version": "2012-10-17","Statement": [{"Effect": "Allow","Principal": {"Federated": "arn:${PARTITION}:iam::${ACCOUNT_ID}:oidc-provider/${OIDC}"},"Action": "sts:AssumeRoleWithWebIdentity","Condition": {"StringEquals": {"${OIDC}:aud": "sts.amazonaws.com","${OIDC}:sub": "system:serviceaccount:kube-system:s3-csi-driver-sa"}}}]}- PARTITION: !Ref AWS::PartitionACCOUNT_ID: !Ref AWS::AccountIdOIDC: !ImportValueFn::Sub: ${EksStackName}-oidc-idManagedPolicyArns:- !Sub arn:${AWS::Partition}:iam::aws:policy/AmazonS3FullAccess
Then install the S3 CSI Driver from add-on. Below is an application example.
apiVersion: v1kind: PersistentVolumemetadata:name: s3-pvspec:capacity:storage: 1200Gi # ignored, requiredaccessModes:- ReadWriteMany # supported options: ReadWriteMany / ReadOnlyManymountOptions:- allow-delete- region us-west-2- prefix some-s3-prefix/csi:driver: s3.csi.aws.com # requiredvolumeHandle: s3-csi-driver-volumevolumeAttributes:bucketName: s3-csi-driver-14062024---apiVersion: v1kind: PersistentVolumeClaimmetadata:name: s3-claimspec:accessModes:- ReadWriteMany # supported options: ReadWriteMany / ReadOnlyManystorageClassName: '' # required for static provisioningresources:requests:storage: 1200Gi # ignored, requiredvolumeName: s3-pv---apiVersion: v1kind: Podmetadata:name: s3-appspec:containers:- name: appimage: centoscommand: ['/bin/sh']args:['-c',"echo 'Hello from the container!' >> /data/$(date -u).txt; tail -f /dev/null"]volumeMounts:- name: persistent-storagemountPath: /datavolumes:- name: persistent-storagepersistentVolumeClaim:claimName: s3-claim
Trobleshooting#
Delete persistent volume.
kubectl patch pv static-pv-ebs -p '{"metadata":{"finalizers":null}}'
eksctl utils associate-iam-oidc-provider --cluster eks-stack-eks-cluster --approveeksctl create iamserviceaccount \--name cloudwatch-agent \--namespace amazon-cloudwatch --cluster eks-stack-eks-cluster \--role-name RoleForEKSContainerInsights \--attach-policy-arn arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy \--role-only \--approveeksctl create iamserviceaccount \--name ebs-csi-controller-sa \--namespace kube-system \--cluster eks-stack-eks-cluster \--role-name AmazonEKS_EBS_CSI_DriverRoleDemo \--role-only \--attach-policy-arn arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy \--approve
aws eks delete-addon \--cluster-name eks-stack-eks-cluster \--addon-name amazon-cloudwatch-observability
ClusterName=eks-stack-eks-clusterRegionName=us-west-2curl https://raw.githubusercontent.com/aws-samples/amazon-cloudwatch-container-insights/main/k8s-quickstart/cwagent-operator-rendered.yaml | sed 's/{{cluster_name}}/'${ClusterName}'/g;s/{{region_name}}/'${RegionName}'/g' | kubectl delete -f -curl https://raw.githubusercontent.com/aws-samples/amazon-cloudwatch-container-insights/main/k8s-quickstart/cwagent-custom-resource-definitions.yaml | kubectl delete -f -
The OIDC thumbprint list per region.
{"us-east-2": "9E99A48A9960B14926BB7F3B02E22DA2B0AB7280"}{"us-east-1": "9E99A48A9960B14926BB7F3B02E22DA2B0AB7280"}{"us-west-1": "9E99A48A9960B14926BB7F3B02E22DA2B0AB7280"}{"us-west-2": "9E99A48A9960B14926BB7F3B02E22DA2B0AB7280"}{"ap-east-1": "9E99A48A9960B14926BB7F3B02E22DA2B0AB7280"}{"ap-northeast-2": "9E99A48A9960B14926BB7F3B02E22DA2B0AB7280"}{"ap-southeast-1": "9E99A48A9960B14926BB7F3B02E22DA2B0AB7280"}{"ap-southeast-2": "9E99A48A9960B14926BB7F3B02E22DA2B0AB7280"}{"ap-northeast-1": "9E99A48A9960B14926BB7F3B02E22DA2B0AB7280"}{"eu-central-1": "9E99A48A9960B14926BB7F3B02E22DA2B0AB7280"}{"eu-west-1": "9E99A48A9960B14926BB7F3B02E22DA2B0AB7280"}{"eu-west-2": "9E99A48A9960B14926BB7F3B02E22DA2B0AB7280"}{"eu-west-3": "9E99A48A9960B14926BB7F3B02E22DA2B0AB7280"}{"eu-north-1": "9E99A48A9960B14926BB7F3B02E22DA2B0AB7280"}{"me-south-1": "9E99A48A9960B14926BB7F3B02E22DA2B0AB7280"}{"sa-east-1": "9E99A48A9960B14926BB7F3B02E22DA2B0AB7280"}
The script to get the thumbprint.
#!/bin/bashset -eif [ ! -z "$DEBUG" ] ; thenset -xfiREGIONS="us-east-2us-east-1us-west-1us-west-2ap-east-1ap-northeast-2ap-southeast-1ap-southeast-2ap-northeast-1eu-central-1eu-west-1eu-west-2eu-west-3eu-north-1me-south-1sa-east-1"for REGION in $REGIONS ; doJWKS_URI="oidc.eks.${REGION}.amazonaws.com"# Extract all certificates in separate files# https://unix.stackexchange.com/questions/368123/how-to-extract-the-root-ca-and-subordinate-ca-from-a-certificate-chain-in-linuxTEMP=$(mktemp -d -t oidc-eks-XXXX)openssl s_client -servername $JWKS_URI -showcerts -connect $JWKS_URI:443 < /dev/null 2>/dev/null | awk -v dir="$TEMP" '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/{ if(/BEGIN/){a++}; out=dir"/cert00"a".crt"; print >out }'# Assume last found certificate in chain is the ROOT_CAROOT_CA=$(ls -1 $TEMP/* | tail -1)# Extract fingerprint in desired format (no header, no colons)THUMBPRINT=$(openssl x509 -fingerprint -noout -in $ROOT_CA | sed 's/^.*=//' | sed 's/://g')printf '{"%s": "%s"}\n' $REGION $THUMBPRINTrm -rf $TEMPdone
Obtain Thumbprint#
First open this URL in a browser.
https://oidc.eks.us-west-2.amazonaws.com/id/B0A30A17C98DD4693DEBFAEA162620B8/.well-known/openid-configuration
Then take note the jwks_uri without https as the following.
oidc.eks.us-west-2.amazonaws.com/id/B0A30A17C98DD4693DEBFAEA162620B8/keys
Use the OpenSSL command line tool to run the following command.
openssl s_client -servername oidc.eks.us-west-2.amazonaws.com -showcerts -connect oidc.eks.us-west-2.amazonaws.com:443
Then save the certificate to certificate.crt
-----BEGIN CERTIFICATE-----MIIEdTCCA12gAwIBAgIJAKcOSkw0grd/MA0GCSqGSIb3DQEBCwUAMGgxCzAJBgNVBAYTAlVTMSUwIwYDVQQKExxTdGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQLEylTdGFyZmllbGQgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wOTA5MDIwMDAwMDBaFw0zNDA2MjgxNzM5MTZaMIGYMQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjE7MDkGA1UEAxMyU3RhcmZpZWxkIFNlcnZpY2VzIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVDDrEKvlO4vW+GZdfjohTsR8/y8+fIBNtKTrID30892t2OGPZNmCom15cAICyL1l/9of5JUOG52kbUpqQ4XHj2C0NTm/2yEnZtvMaVq4rtnQU68/7JuMauh2WLmo7WJSJR1b/JaCTcFOD2oR0FMNnngRoOt+OQFodSk7PQ5E751bWAHDLUu57fa4657wx+UX2wmDPE1kCK4DMNEffud6QZW0CzyyRpqbn3oUYSXxmTqM6bam17jQuug0DuDPfR+uxa40l2ZvOgdFFRjKWcIfeAg5JQ4W2bHO7ZOphQazJ1FTfhy/HIrImzJ9ZVGif/L4qL8RVHHVAYBeFAlU5i38FAgMBAAGjgfAwge0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMB8GA1UdIwQYMBaAFL9ft9HO3R+G9FtVrNzXEMIOqYjnME8GCCsGAQUFBwEBBEMwQTAcBggrBgEFBQcwAYYQaHR0cDovL28uc3MyLnVzLzAhBggrBgEFBQcwAoYVaHR0cDovL3guc3MyLnVzL3guY2VyMCYGA1UdHwQfMB0wG6AZoBeGFWh0dHA6Ly9zLnNzMi51cy9yLmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQELBQADggEBACMd44pXyn3pF3lM8R5V/cxTbj5HD9/GVfKyBDbtgB9TxF00KGu+x1X8Z+rLP3+QsjPNG1gQggL4+C/1E2DUBc7xgQjB3ad1l08YuW3e95ORCLp+QCztweq7dp4zBncdDQh/U90bZKuCJ/Fp1U1ervShw3WnWEQt8jxwmKy6abaVd38PMV4s/KCHOkdp8Hlf9BRUpJVeEXgSYCfOn8J3/yNTd126/+pZ59vPr5KW7ySaNRB6nJHGDn2Z9j8Z3/VyVOEVqQdZe4O/Ui5GjLIAZHYcSNPYeehuVsyuLAOQ1xk4meTKCRlb/weWsKh/NEnfVqn3sF/tM+2MR7cwA130A4w=-----END CERTIFICATE-----
Next, run the following command.
openssl x509 -in certificate.crt -fingerprint -sha1 -noout
Then output should look like below.
sha1 Fingerprint=9E:99:A4:8A:99:60:B1:49:26:BB:7F:3B:02:E2:2D:A2:B0:AB:72:80
Just remove these colon character (:) to get the thumbprint.