Il existe s3cmd comme outil de gestion des objets S3. Vous pouvez utiliser S3 sans installer l'AWS CLI, et il est souvent utilisé pour la sauvegarde et la restauration.
Pour utiliser s3cmd sur EKS, Pod doit accéder à S3. Dans le passé, pour donner accès à S3, ** le rôle IAM était attribué à Node ** et ** kube2iam était utilisé pour obtenir temporairement des informations d'identification **. En 2019, rôle IAM pour le compte de service (IRSA) apparaîtra dans chaque langue. Le SDK le prend en charge, mais s3cmd n'utilise pas le SDK, j'ai donc essayé d'implémenter le mécanisme moi-même.
macOS Mojabe 10.14.6 Pulumi 2.1.0 AWS CLI 1.16.292 EKS 1.15 s3cmd 2.1.0
Modifiez le code source de s3cmd et poussez l'image Docker vers ECR.
Téléchargez s3cmd avec la commande suivante.
$ wget --no-check-certificate https://github.com/s3tools/s3cmd/releases/download/v2.1.0/s3cmd-2.1.0.tar.gz
$ tar xzvf s3cmd-2.1.0.tar.gz
$ cd s3cmd-2.1.0
La structure de répertoires de s3cmd-2.1.0 est la suivante.
├── INSTALL.md
├── LICENSE
├── MANIFEST.in
├── NEWS
├── PKG-INFO
├── README.md
├── S3/
├── s3cmd
├── s3cmd.1
├── s3cmd.egg-info/
├── setup.cfg
└── setup.py
Modifiez uniquement S3 / Config.py
. Le flux pour obtenir l'autorisation d'accès S3 est le suivant.
et ʻAWS_WEB_IDENTITY_TOKEN_FILE
à partir des variables d'environnement.Seule la partie supplémentaire est décrite ci-dessous. Seule la fonction role_config
réécrira celle existante.
S3/Config.py
import urllib.request
import urllib.parse
import xml.etree.cElementTree
def _get_url():
stsUrl = "https://sts.amazonaws.com/"
roleArn = os.environ.get('AWS_ROLE_ARN')
path = os.environ.get('AWS_WEB_IDENTITY_TOKEN_FILE')
with open(path) as f:
webIdentityToken = f.read()
params = {
"Action": "AssumeRoleWithWebIdentity",
"Version": "2011-06-15",
"RoleArn": roleArn,
"RoleSessionName": "s3cmd",
"WebIdentityToken": webIdentityToken
}
url = '{}?{}'.format(stsUrl, urllib.parse.urlencode(params))
return url
def _build_name_to_xml_node(parent_node):
if isinstance(parent_node, list):
return build_name_to_xml_node(parent_node[0])
xml_dict = {}
for item in parent_node:
key = re.compile('{.*}').sub('',item.tag)
if key in xml_dict:
if isinstance(xml_dict[key], list):
xml_dict[key].append(item)
else:
xml_dict[key] = [xml_dict[key], item]
else:
xml_dict[key] = item
return xml_dict
def _replace_nodes(parsed):
for key, value in parsed.items():
if list(value):
sub_dict = _build_name_to_xml_node(value)
parsed[key] = _replace_nodes(sub_dict)
else:
parsed[key] = value.text
return parsed
def _parse_xml_to_dict(body):
parser = xml.etree.cElementTree.XMLParser(target=xml.etree.cElementTree.TreeBuilder(), encoding='utf-8')
parser.feed(body)
root = parser.close()
parsed = _build_name_to_xml_node(root)
_replace_nodes(parsed)
return parsed
class Config(object):
def role_config(self):
url = _get_url()
req = urllib.request.Request(url, method='POST')
with urllib.request.urlopen(req) as resp:
body = resp.read()
parsed = _parse_xml_to_dict(body)
Config().update_option('access_key', parsed['AssumeRoleWithWebIdentityResult']['Credentials']['AccessKeyId'])
Config().update_option('secret_key', parsed['AssumeRoleWithWebIdentityResult']['Credentials']['SecretAccessKey'])
Config().update_option('access_token', parsed['AssumeRoleWithWebIdentityResult']['Credentials']['SessionToken'])
Regardons chacun d'eux.
La fonction _get_url
sert à créer une URL pour le POST vers l'API STS.
L'application d'IRSA à un pod crée des variables d'environnement ʻAWS_ROLE_ARN, ʻAWS_WEB_IDENTITY_TOKEN_FILE
.
Ce dernier est le chemin du fichier, qui récupère le jeton à l'intérieur et l'ajoute au paramètre URL.
def _get_url():
stsUrl = "https://sts.amazonaws.com/"
roleArn = os.environ.get('AWS_ROLE_ARN')
path = os.environ.get('AWS_WEB_IDENTITY_TOKEN_FILE')
with open(path) as f:
webIdentityToken = f.read()
params = {
"Action": "AssumeRoleWithWebIdentity",
"Version": "2011-06-15",
"RoleArn": roleArn,
"RoleSessionName": "s3cmd",
"WebIdentityToken": webIdentityToken
}
url = '{}?{}'.format(stsUrl, urllib.parse.urlencode(params))
return url
def _build_name_to_xml_node(parent_node):
if isinstance(parent_node, list):
return build_name_to_xml_node(parent_node[0])
xml_dict = {}
for item in parent_node:
key = re.compile('{.*}').sub('',item.tag)
if key in xml_dict:
if isinstance(xml_dict[key], list):
xml_dict[key].append(item)
else:
xml_dict[key] = [xml_dict[key], item]
else:
xml_dict[key] = item
return xml_dict
def _replace_nodes(parsed):
for key, value in parsed.items():
if list(value):
sub_dict = _build_name_to_xml_node(value)
parsed[key] = _replace_nodes(sub_dict)
else:
parsed[key] = value.text
return parsed
def _parse_xml_to_dict(body):
parser = xml.etree.cElementTree.XMLParser(target=xml.etree.cElementTree.TreeBuilder(), encoding='utf-8')
parser.feed(body)
root = parser.close()
parsed = _build_name_to_xml_node(root)
_replace_nodes(parsed)
return parsed
class Config(object):
def role_config(self):
url = _get_url()
req = urllib.request.Request(url, method='POST')
with urllib.request.urlopen(req) as resp:
body = resp.read()
parsed = _parse_xml_to_dict(body)
Config().update_option('access_key', parsed['AssumeRoleWithWebIdentityResult']['Credentials']['AccessKeyId'])
Config().update_option('secret_key', parsed['AssumeRoleWithWebIdentityResult']['Credentials']['SecretAccessKey'])
Config().update_option('access_token', parsed['AssumeRoleWithWebIdentityResult']['Credentials']['SessionToken'])
Compressez la version modifiée par le code en tant que s3cmd-2.1.0.tar.gz
et placez-la dans le même répertoire que Dockerfile
.
├── Dockerfile
└── s3cmd-2.1.0.tar.gz
Le Dockerfile
ressemble à ceci:
Dockerfile
FROM python:3.8.2-alpine3.11
ARG VERSION=2.1.0
COPY s3cmd-${VERSION}.tar.gz /tmp/
RUN tar -zxf /tmp/s3cmd-${VERSION}.tar.gz -C /tmp && \
cd /tmp/s3cmd-${VERSION} && \
python setup.py install && \
mv s3cmd S3 /usr/local/bin && \
rm -rf /tmp/*
ENTRYPOINT ["s3cmd"]
CMD ["--help"]
Construisez l'image et envoyez-la à ECR. Remplacez «XXXXXXXXXXXX» par votre compte AWS.
$ docker build -t XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/s3cmd:2.1.0 .
$ docker push XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/s3cmd:2.1.0
Tout l'environnement cette fois-ci sera construit avec Pulumi. La structure des répertoires est la suivante. Modifiez uniquement «index.ts» et «k8s / s3cmd.yaml».
├── Pulumi.dev.yaml
├── Pulumi.yaml
├── index.ts *
├── k8s
│ └── s3cmd.yaml *
├── node_modules/
├── package-lock.json
├── package.json
├── stack.json
└── tsconfig.json
Écrivez autre chose que le fichier manifeste Kubernetes dans ʻindex.ts`. Le cluster EKS doit inclure les paramètres du fournisseur OpenID Connect.
index.ts
import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";
import * as eks from "@pulumi/eks";
import * as k8s from "@pulumi/kubernetes";
import * as pulumi from "@pulumi/pulumi";
const vpc = new awsx.ec2.Vpc("custom", {
cidrBlock: "10.0.0.0/16",
numberOfAvailabilityZones: 3,
});
const cluster = new eks.Cluster("pulumi-eks-cluster", {
vpcId: vpc.id,
subnetIds: vpc.publicSubnetIds,
deployDashboard: false,
createOidcProvider: true,
instanceType: aws.ec2.T3InstanceSmall,
});
const s3PolicyDocument = pulumi.all([cluster.core.oidcProvider?.arn, cluster.core.oidcProvider?.url]).apply(([arn, url]) => {
return aws.iam.getPolicyDocument({
statements: [{
effect: "Allow",
principals: [
{
type: "Federated",
identifiers: [arn]
},
],
actions: ["sts:AssumeRoleWithWebIdentity"],
conditions: [
{
test: "StringEquals",
variable: url.replace('http://', '') + ":sub",
values: [
"system:serviceaccount:default:s3-full-access"
]
},
],
}]
})
})
const s3FullAccessRole = new aws.iam.Role("s3FullAccessRole", {
name: "s3-full-access-role",
assumeRolePolicy: s3PolicyDocument.json,
})
new aws.s3.Bucket("pulumi-s3cmd-test", {
bucket: "pulumi-s3cmd-test"
});
const s3FullAccessRoleAttachment = new aws.iam.RolePolicyAttachment("s3FullAccessRoleAttachment", {
role: s3FullAccessRole,
policyArn: aws.iam.AmazonS3FullAccess,
})
const myk8s = new k8s.Provider("myk8s", {
kubeconfig: cluster.kubeconfig.apply(JSON.stringify),
});
const s3cmd = new k8s.yaml.ConfigFile("s3cmd", {
file: "./k8s/s3cmd.yaml"
}, { provider: myk8s })
k8s / s3cmd.yaml
définit ServiceAccount et Deployment.
Le compte de service doit ajouter des annotations.
s3cmd.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
namespace: default
name: s3-full-access
labels:
app: s3cmd
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::XXXXXXXXXXXX:role/s3-full-access-role
---
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: default
name: s3cmd
labels:
app: s3cmd
spec:
selector:
matchLabels:
app: s3cmd
replicas: 1
template:
metadata:
labels:
app: s3cmd
spec:
serviceAccountName: s3-full-access
containers:
- image: XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/s3cmd:2.1.0
name: s3cmd
command: ["/bin/sh"]
args: ["-c", "while true; do echo hello; sleep 10; done"]
Tout ce que vous avez à faire est de déployer avec la commande suivante.
$ pulumi up
Confirmez que vous pouvez taper la commande s3cmd à partir du pod s3cmd créé. Le compartiment S3 créé cette fois s'affiche correctement.
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
s3cmd-98985855f-h5lgl 1/1 Running 0 63s
$ kubectl exec -it s3cmd-98985855f-h5lgl -- s3cmd ls
2020-05-02 15:04 s3://pulumi-s3cmd-test
J'ai confirmé que le rôle IAM peut être attribué au pod s3cmd par l'IRSA sans utiliser kube2iam. Considérant qu'il est nécessaire de déployer DaemonSet dans kube2iam et que les ressources de gestion vont augmenter, je pense que le mérite d'IRSA est grand.