所以,机器需要具备向该存储桶写入数据的权限,但是我不希望它有覆盖或删除该存储桶中任何文件(包括我要写入的文件)的能力。
基本上,我只想让我的机器能够将数据追加到该日志文件中,而不覆盖或下载它。
有没有办法配置我的S3使其按照我想要的方式工作?也许有一些IAM策略可以附加到它上面,以便按照我的意愿工作?
很遗憾,你不能。
S3没有"append"操作。* 一旦对象被上传,就无法在原地修改它;你唯一的选择是上传一个新对象来替换它,这不符合你的需求。
*:是的,我知道这篇帖子已经几年了。但它仍然准确无误。
如接受的答案所述,您无法使用 Linux 命令行中的 >>
。我所知道的最佳解决方案是使用:
https://aws.amazon.com/kinesis/firehose/
他们的代码示例看起来很复杂,但你可以非常简单地执行 PUT(或批量PUT)操作到你的应用程序中的 Kinesis Firehose 提交流(使用 AWS SDK),并且在 AWS Kinesis Firehose 控制台中配置 Kinesis Firehose 提交流将你的流数据发送到你选择的 AWS S3 存储桶。
这仍然不像在 Linux 命令行中那样便捷,因为一旦你在 S3 上创建了一个文件,你需要再次下载、追加和上传新文件。但你每批行只需执行一次而不是每行都需要执行一次,因此你不必担心由于追加操作的数量而产生巨额费用。也许可以做到,但我无法从控制台上看出如何做到。
S3上的对象不支持追加。在这种情况下,您有两个解决方案:
function writeToS3(input) {
var content;
var getParams = {
Bucket: 'myBucket',
Key: "myKey"
};
s3.getObject(getParams, function(err, data) {
if (err) console.log(err, err.stack);
else {
content = new Buffer(data.Body).toString("utf8");
content = content + '\n' + new Date() + '\t' + input;
var putParams = {
Body: content,
Bucket: 'myBucket',
Key: "myKey",
ACL: "public-read"
};
s3.putObject(putParams, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else {
console.log(data); // successful response
}
});
}
});
}
function writeToS3(input) {
var content = "\n" + new Date() + "\t" + input;
var params = {
DeliveryStreamName: 'myDeliveryStream', /* required */
Record: { /* required */
Data: new Buffer(content) || 'STRING_VALUE' /* Strings will be Base-64 encoded on your behalf */ /* required */
}
};
firehose.putRecord(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
}
有一些限制,例如您的现有对象必须大于5MB(但如果较小,则将其复制到客户端应该足够快速地满足大多数情况)。 这不如直接追加来得好,但至少您不需要在AWS和本地计算机之间来回复制数据。
如果有人想要在类似S3的服务中向对象附加数据,阿里云 OSS(对象存储服务)原生支持此功能。
OSS提供附加上传(通过AppendObject API),允许您直接将内容附加到对象的末尾。使用此方法上传的对象是可附加对象,而使用其他方法上传的对象是普通对象。附加的数据可以立即读取。
CREATE EXTERNAL TABLE IF NOT EXISTS `TrainingDB`.`TrainingTable` (`Data` string)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'
WITH SERDEPROPERTIES ('collection.delim' = '\n')
STORED AS INPUTFORMAT 'org.apache.hadoop.mapred.TextInputFormat' OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION 's3://your-bucket-name/TrainingTesting/';
UNLOAD (SELECT * FROM "TrainingDB"."TrainingTable")
TO 's3://your-bucket/TrainingResults/results5'
WITH ( format = 'TEXTFILE', compression='none' )
正如其他人之前所述,S3对象不可附加。
然而,另一个解决方案是将内容写入CloudWatch日志,然后导出您想要的日志到S3。这也可以防止任何攻击者从您的服务器删除S3存储桶中的内容,因为Lambda不需要任何S3权限。
Original Answer翻译成“最初的回答”
我曾经遇到过类似的问题,需要在长时间运行的过程中将错误写入S3日志文件。因此,我没有本地文件来创建一次性流,而是必须在运行时将错误附加到一个文件中。
所以你可以保持与特定文件的开放连接,并在需要时向该文件写入:
const { S3 } = require('aws-sdk')
const { PassThrough } = require('stream')
// append to open connection
const append = (stream, data ) => new Promise(resolve => {
stream.write(`${data}\n`, resolve)
})
const openConnectionWithS3 = async () => {
const s3 = new S3({
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
endpoint: process.env.AWS_S3_ENDPOINT,
region: process.env.AWS_DEFAULT_REGION,
})
const fileName = 'test.log'
const bucketName = 'my-bucket'
// create pass through stream. This stream we use to write data to
// but this stream we also use to pass the same data to aws
const pass = new PassThrough()
// dont resolve the promise, but keep it open and await for the result when the long running process is done
const promise = s3
.upload({
Bucket: bucketName,
Key: fileName,
// pass the stream as body, aws will handle the stream from now
Body: pass,
})
.promise()
// write data to our open connection.
// we can even write it on different places
for (let i = 0; i < 100000; i++) {
await append(pass, `foo${i}`)
}
// here we resolve the promise and close the connection
await Promise.all([
// push null to the stream, the stream now knows after the
// 1000 foo's it should stop writing
pass.push(null),
promise,
])
}
openConnectionWithS3()
S3存储桶不允许您追加现有对象,可以使用的方法是,首先使用获取方法从S3存储桶中获取数据,然后在本地添加要追加的新数据,最后将其推回S3存储桶。
由于不可能向现有的S3对象追加内容,因此您需要用一个包含追加数据的新对象替换它。这意味着每次追加新条目时,您都需要上传整个对象(日志文件)。这样做效率不高。
您可以将日志条目发送到SQS队列中,当队列大小达到设定值时,您可以将日志消息合并为一起,并添加为S3存储桶中的一个对象。但这仍然无法满足您追加到单个对象的要求。
import s3fs
s3 = s3fs.S3FileSystem(anon=False)
# Create a file just like you do on a local system
path_to_your_file = "s3://my-bucket/my-key/my_file.txt
with s3.open('path_to_your_file, 'w') as f:
f.write(f"This is a new QA file!\n")
# Now append to the file just like you do on a local system.
with s3.open('path_to_your_file, 'a') as f:
f.write(f"----------------------------------------------------------!\n")