Es gibt s3cmd als Tool zum Verwalten von S3-Objekten. Sie können S3 ohne Installation der AWS CLI betreiben und es wird häufig zum Sichern und Wiederherstellen verwendet.
Um s3cmd unter EKS verwenden zu können, benötigt der Pod Zugriff auf S3. Zuvor erhielt ** Node eine IAM-Rolle **, um Zugriff auf S3 zu gewähren, und ** kube2iam wurde verwendet, um vorübergehend einen Berechtigungsnachweis zu erhalten **. Im Jahr 2019 wird IAM-Rolle für Dienstkonto (IRSA) in jeder Sprache angezeigt. SDK unterstützt es, aber s3cmd verwendet kein SDK, daher habe ich versucht, den Mechanismus selbst zu implementieren.
macOS Mojabe 10.14.6 Pulumi 2.1.0 AWS CLI 1.16.292 EKS 1.15 s3cmd 2.1.0
Ändern Sie den Quellcode von s3cmd und verschieben Sie das Docker-Image auf ECR.
Laden Sie s3cmd mit dem folgenden Befehl herunter.
$ 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
Die Verzeichnisstruktur von s3cmd-2.1.0 ist wie folgt.
├── INSTALL.md
├── LICENSE
├── MANIFEST.in
├── NEWS
├── PKG-INFO
├── README.md
├── S3/
├── s3cmd
├── s3cmd.1
├── s3cmd.egg-info/
├── setup.cfg
└── setup.py
Ändern Sie nur S3 / Config.py
. Der Ablauf zum Erhalten der S3-Zugriffsberechtigung ist wie folgt.
AWS_ROLE_ARN
und AWS_WEB_IDENTITY_TOKEN_FILE
aus den Umgebungsvariablen ab.Nur der zusätzliche Teil wird unten beschrieben. Nur die Funktion role_config
schreibt die vorhandene neu.
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'])
Schauen wir uns jeden an.
Die Funktion "_get_url" dient zum Erstellen einer URL für das POSTing an die STS-API.
Durch Anwenden von IRSA auf einen Pod werden die Umgebungsvariablen "AWS_ROLE_ARN", "AWS_WEB_IDENTITY_TOKEN_FILE" erstellt.
Letzteres ist der Dateipfad, über den das Token abgerufen und dem URL-Parameter hinzugefügt wird.
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'])
Komprimieren Sie die Code-modifizierte Version als "s3cmd-2.1.0.tar.gz" und legen Sie sie im selben Verzeichnis wie "Dockerfile" ab.
├── Dockerfile
└── s3cmd-2.1.0.tar.gz
Das Dockerfile sieht folgendermaßen aus:
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"]
Erstellen Sie das Image und senden Sie es an ECR. Ersetzen Sie "XXXXXXXXXXXX" durch Ihr AWS-Konto.
$ 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
Die gesamte Umgebung wird diesmal mit Pulumi erstellt.
Die Verzeichnisstruktur ist wie folgt. Bearbeiten Sie nur index.ts
und k8s / s3cmd.yaml
.
├── Pulumi.dev.yaml
├── Pulumi.yaml
├── index.ts *
├── k8s
│ └── s3cmd.yaml *
├── node_modules/
├── package-lock.json
├── package.json
├── stack.json
└── tsconfig.json
Beschreiben Sie eine andere als die Kubernetes-Manifestdatei in "index.ts". Der EKS-Cluster muss die OpenID Connect Provider-Einstellungen enthalten.
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
definiert ServiceAccount und Deployment.
Das Dienstkonto muss Anmerkungen hinzufügen.
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"]
Sie müssen lediglich den folgenden Befehl bereitstellen.
$ pulumi up
Stellen Sie sicher, dass Sie den Befehl s3cmd aus dem erstellten s3cmd-Pod eingeben können. Der diesmal erstellte S3-Bucket wird ordnungsgemäß angezeigt.
$ 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
Ich habe bestätigt, dass die IAM-Rolle von IRSA ohne Verwendung von kube2iam dem s3cmd Pod zugewiesen werden kann. In Anbetracht der Tatsache, dass DaemonSet in kube2iam bereitgestellt werden muss und die Verwaltungsressourcen zunehmen werden, denke ich, dass der Nutzen von IRSA großartig ist.